1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: ToXMLStream.java 469359 2006-10-31 03:43:19Z minchau $
20 */
21 package org.apache.xml.serializer;
22
23import java.io.IOException;
24
25import javax.xml.transform.ErrorListener;
26import javax.xml.transform.Result;
27import javax.xml.transform.Transformer;
28import javax.xml.transform.TransformerException;
29
30import org.apache.xml.serializer.utils.MsgKey;
31import org.apache.xml.serializer.utils.Utils;
32import org.xml.sax.SAXException;
33
34/**
35 * This class converts SAX or SAX-like calls to a
36 * serialized xml document.  The xsl:output method is "xml".
37 *
38 * This class is used explicitly in code generated by XSLTC,
39 * so it is "public", but it should
40 * be viewed as internal or package private, this is not an API.
41 *
42 * @xsl.usage internal
43 */
44public class ToXMLStream extends ToStream
45{
46    /**
47     * Map that tells which XML characters should have special treatment, and it
48     *  provides character to entity name lookup.
49     */
50    private CharInfo m_xmlcharInfo =
51        CharInfo.getCharInfo(CharInfo.XML_ENTITIES_RESOURCE, Method.XML);
52
53    /**
54     * Default constructor.
55     */
56    public ToXMLStream()
57    {
58        m_charInfo = m_xmlcharInfo;
59
60        initCDATA();
61        // initialize namespaces
62        m_prefixMap = new NamespaceMappings();
63
64    }
65
66    /**
67     * Copy properties from another SerializerToXML.
68     *
69     * @param xmlListener non-null reference to a SerializerToXML object.
70     */
71    public void CopyFrom(ToXMLStream xmlListener)
72    {
73
74        setWriter(xmlListener.m_writer);
75
76
77        // m_outputStream = xmlListener.m_outputStream;
78        String encoding = xmlListener.getEncoding();
79        setEncoding(encoding);
80
81        setOmitXMLDeclaration(xmlListener.getOmitXMLDeclaration());
82
83        m_ispreserve = xmlListener.m_ispreserve;
84        m_preserves = xmlListener.m_preserves;
85        m_isprevtext = xmlListener.m_isprevtext;
86        m_doIndent = xmlListener.m_doIndent;
87        setIndentAmount(xmlListener.getIndentAmount());
88        m_startNewLine = xmlListener.m_startNewLine;
89        m_needToOutputDocTypeDecl = xmlListener.m_needToOutputDocTypeDecl;
90        setDoctypeSystem(xmlListener.getDoctypeSystem());
91        setDoctypePublic(xmlListener.getDoctypePublic());
92        setStandalone(xmlListener.getStandalone());
93        setMediaType(xmlListener.getMediaType());
94        m_encodingInfo = xmlListener.m_encodingInfo;
95        m_spaceBeforeClose = xmlListener.m_spaceBeforeClose;
96        m_cdataStartCalled = xmlListener.m_cdataStartCalled;
97
98    }
99
100    /**
101     * Receive notification of the beginning of a document.
102     *
103     * @throws org.xml.sax.SAXException Any SAX exception, possibly
104     *            wrapping another exception.
105     *
106     * @throws org.xml.sax.SAXException
107     */
108    public void startDocumentInternal() throws org.xml.sax.SAXException
109    {
110
111        if (m_needToCallStartDocument)
112        {
113            super.startDocumentInternal();
114            m_needToCallStartDocument = false;
115
116            if (m_inEntityRef)
117                return;
118
119            m_needToOutputDocTypeDecl = true;
120            m_startNewLine = false;
121            /* The call to getXMLVersion() might emit an error message
122             * and we should emit this message regardless of if we are
123             * writing out an XML header or not.
124             */
125            final String version = getXMLVersion();
126            if (getOmitXMLDeclaration() == false)
127            {
128                String encoding = Encodings.getMimeEncoding(getEncoding());
129                String standalone;
130
131                if (m_standaloneWasSpecified)
132                {
133                    standalone = " standalone=\"" + getStandalone() + "\"";
134                }
135                else
136                {
137                    standalone = "";
138                }
139
140                try
141                {
142                    final java.io.Writer writer = m_writer;
143                    writer.write("<?xml version=\"");
144                    writer.write(version);
145                    writer.write("\" encoding=\"");
146                    writer.write(encoding);
147                    writer.write('\"');
148                    writer.write(standalone);
149                    writer.write("?>");
150                    if (m_doIndent) {
151                        if (m_standaloneWasSpecified
152                                || getDoctypePublic() != null
153                                || getDoctypeSystem() != null) {
154                            // We almost never put a newline after the XML
155                            // header because this XML could be used as
156                            // an extenal general parsed entity
157                            // and we don't know the context into which it
158                            // will be used in the future.  Only when
159                            // standalone, or a doctype system or public is
160                            // specified are we free to insert a new line
161                            // after the header.  Is it even worth bothering
162                            // in these rare cases?
163                            writer.write(m_lineSep, 0, m_lineSepLen);
164                        }
165                    }
166                }
167                catch(IOException e)
168                {
169                    throw new SAXException(e);
170                }
171
172            }
173        }
174    }
175
176    /**
177     * Receive notification of the end of a document.
178     *
179     * @throws org.xml.sax.SAXException Any SAX exception, possibly
180     *            wrapping another exception.
181     *
182     * @throws org.xml.sax.SAXException
183     */
184    public void endDocument() throws org.xml.sax.SAXException
185    {
186        flushPending();
187        if (m_doIndent && !m_isprevtext)
188        {
189            try
190            {
191            outputLineSep();
192            }
193            catch(IOException e)
194            {
195                throw new SAXException(e);
196            }
197        }
198
199        flushWriter();
200
201        if (m_tracer != null)
202            super.fireEndDoc();
203    }
204
205    /**
206     * Starts a whitespace preserving section. All characters printed
207     * within a preserving section are printed without indentation and
208     * without consolidating multiple spaces. This is equivalent to
209     * the <tt>xml:space=&quot;preserve&quot;</tt> attribute. Only XML
210     * and HTML serializers need to support this method.
211     * <p>
212     * The contents of the whitespace preserving section will be delivered
213     * through the regular <tt>characters</tt> event.
214     *
215     * @throws org.xml.sax.SAXException
216     */
217    public void startPreserving() throws org.xml.sax.SAXException
218    {
219
220        // Not sure this is really what we want.  -sb
221        m_preserves.push(true);
222
223        m_ispreserve = true;
224    }
225
226    /**
227     * Ends a whitespace preserving section.
228     *
229     * @see #startPreserving
230     *
231     * @throws org.xml.sax.SAXException
232     */
233    public void endPreserving() throws org.xml.sax.SAXException
234    {
235
236        // Not sure this is really what we want.  -sb
237        m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
238    }
239
240    /**
241     * Receive notification of a processing instruction.
242     *
243     * @param target The processing instruction target.
244     * @param data The processing instruction data, or null if
245     *        none was supplied.
246     * @throws org.xml.sax.SAXException Any SAX exception, possibly
247     *            wrapping another exception.
248     *
249     * @throws org.xml.sax.SAXException
250     */
251    public void processingInstruction(String target, String data)
252        throws org.xml.sax.SAXException
253    {
254        if (m_inEntityRef)
255            return;
256
257        flushPending();
258
259        if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING))
260        {
261            startNonEscaping();
262        }
263        else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING))
264        {
265            endNonEscaping();
266        }
267        else
268        {
269            try
270            {
271                if (m_elemContext.m_startTagOpen)
272                {
273                    closeStartTag();
274                    m_elemContext.m_startTagOpen = false;
275                }
276                else if (m_needToCallStartDocument)
277                    startDocumentInternal();
278
279                if (shouldIndent())
280                    indent();
281
282                final java.io.Writer writer = m_writer;
283                writer.write("<?");
284                writer.write(target);
285
286                if (data.length() > 0
287                    && !Character.isSpaceChar(data.charAt(0)))
288                    writer.write(' ');
289
290                int indexOfQLT = data.indexOf("?>");
291
292                if (indexOfQLT >= 0)
293                {
294
295                    // See XSLT spec on error recovery of "?>" in PIs.
296                    if (indexOfQLT > 0)
297                    {
298                        writer.write(data.substring(0, indexOfQLT));
299                    }
300
301                    writer.write("? >"); // add space between.
302
303                    if ((indexOfQLT + 2) < data.length())
304                    {
305                        writer.write(data.substring(indexOfQLT + 2));
306                    }
307                }
308                else
309                {
310                    writer.write(data);
311                }
312
313                writer.write('?');
314                writer.write('>');
315
316                /*
317                 * Don't write out any indentation whitespace now,
318                 * because there may be non-whitespace text after this.
319                 *
320                 * Simply mark that at this point if we do decide
321                 * to indent that we should
322                 * add a newline on the end of the current line before
323                 * the indentation at the start of the next line.
324                 */
325                m_startNewLine = true;
326            }
327            catch(IOException e)
328            {
329                throw new SAXException(e);
330            }
331        }
332
333        if (m_tracer != null)
334            super.fireEscapingEvent(target, data);
335    }
336
337    /**
338     * Receive notivication of a entityReference.
339     *
340     * @param name The name of the entity.
341     *
342     * @throws org.xml.sax.SAXException
343     */
344    public void entityReference(String name) throws org.xml.sax.SAXException
345    {
346        if (m_elemContext.m_startTagOpen)
347        {
348            closeStartTag();
349            m_elemContext.m_startTagOpen = false;
350        }
351
352        try
353        {
354            if (shouldIndent())
355                indent();
356
357            final java.io.Writer writer = m_writer;
358            writer.write('&');
359            writer.write(name);
360            writer.write(';');
361        }
362        catch(IOException e)
363        {
364            throw new SAXException(e);
365        }
366
367        if (m_tracer != null)
368            super.fireEntityReference(name);
369    }
370
371    /**
372     * This method is used to add an attribute to the currently open element.
373     * The caller has guaranted that this attribute is unique, which means that it
374     * not been seen before and will not be seen again.
375     *
376     * @param name the qualified name of the attribute
377     * @param value the value of the attribute which can contain only
378     * ASCII printable characters characters in the range 32 to 127 inclusive.
379     * @param flags the bit values of this integer give optimization information.
380     */
381    public void addUniqueAttribute(String name, String value, int flags)
382        throws SAXException
383    {
384        if (m_elemContext.m_startTagOpen)
385        {
386
387            try
388            {
389                final String patchedName = patchName(name);
390                final java.io.Writer writer = m_writer;
391                if ((flags & NO_BAD_CHARS) > 0 && m_xmlcharInfo.onlyQuotAmpLtGt)
392                {
393                    // "flags" has indicated that the characters
394                    // '>'  '<'   '&'  and '"' are not in the value and
395                    // m_htmlcharInfo has recorded that there are no other
396                    // entities in the range 32 to 127 so we write out the
397                    // value directly
398
399                    writer.write(' ');
400                    writer.write(patchedName);
401                    writer.write("=\"");
402                    writer.write(value);
403                    writer.write('"');
404                }
405                else
406                {
407                    writer.write(' ');
408                    writer.write(patchedName);
409                    writer.write("=\"");
410                    writeAttrString(writer, value, this.getEncoding());
411                    writer.write('"');
412                }
413            } catch (IOException e) {
414                throw new SAXException(e);
415            }
416        }
417    }
418
419    /**
420     * Add an attribute to the current element.
421     * @param uri the URI associated with the element name
422     * @param localName local part of the attribute name
423     * @param rawName   prefix:localName
424     * @param type
425     * @param value the value of the attribute
426     * @param xslAttribute true if this attribute is from an xsl:attribute,
427     * false if declared within the elements opening tag.
428     * @throws SAXException
429     */
430    public void addAttribute(
431        String uri,
432        String localName,
433        String rawName,
434        String type,
435        String value,
436        boolean xslAttribute)
437        throws SAXException
438    {
439        if (m_elemContext.m_startTagOpen)
440        {
441            boolean was_added = addAttributeAlways(uri, localName, rawName, type, value, xslAttribute);
442
443
444            /*
445             * We don't run this block of code if:
446             * 1. The attribute value was only replaced (was_added is false).
447             * 2. The attribute is from an xsl:attribute element (that is handled
448             *    in the addAttributeAlways() call just above.
449             * 3. The name starts with "xmlns", i.e. it is a namespace declaration.
450             */
451            if (was_added && !xslAttribute && !rawName.startsWith("xmlns"))
452            {
453                String prefixUsed =
454                    ensureAttributesNamespaceIsDeclared(
455                        uri,
456                        localName,
457                        rawName);
458                if (prefixUsed != null
459                    && rawName != null
460                    && !rawName.startsWith(prefixUsed))
461                {
462                    // use a different raw name, with the prefix used in the
463                    // generated namespace declaration
464                    rawName = prefixUsed + ":" + localName;
465
466                }
467            }
468            addAttributeAlways(uri, localName, rawName, type, value, xslAttribute);
469        }
470        else
471        {
472            /*
473             * The startTag is closed, yet we are adding an attribute?
474             *
475             * Section: 7.1.3 Creating Attributes Adding an attribute to an
476             * element after a PI (for example) has been added to it is an
477             * error. The attributes can be ignored. The spec doesn't explicitly
478             * say this is disallowed, as it does for child elements, but it
479             * makes sense to have the same treatment.
480             *
481             * We choose to ignore the attribute which is added too late.
482             */
483            // Generate a warning of the ignored attributes
484
485            // Create the warning message
486            String msg = Utils.messages.createMessage(
487                    MsgKey.ER_ILLEGAL_ATTRIBUTE_POSITION,new Object[]{ localName });
488
489            try {
490                // Prepare to issue the warning message
491                Transformer tran = super.getTransformer();
492                ErrorListener errHandler = tran.getErrorListener();
493
494
495                // Issue the warning message
496                if (null != errHandler && m_sourceLocator != null)
497                  errHandler.warning(new TransformerException(msg, m_sourceLocator));
498                else
499                  System.out.println(msg);
500                }
501            catch (TransformerException e){
502                // A user defined error handler, errHandler, may throw
503                // a TransformerException if it chooses to, and if it does
504                // we will wrap it with a SAXException and re-throw.
505                // Of course if the handler throws another type of
506                // exception, like a RuntimeException, then that is OK too.
507                SAXException se = new SAXException(e);
508                throw se;
509            }
510        }
511    }
512
513    /**
514     * @see ExtendedContentHandler#endElement(String)
515     */
516    public void endElement(String elemName) throws SAXException
517    {
518        endElement(null, null, elemName);
519    }
520
521    /**
522     * This method is used to notify the serializer of a namespace mapping (or node)
523     * that applies to the current element whose startElement() call has already been seen.
524     * The official SAX startPrefixMapping(prefix,uri) is to define a mapping for a child
525     * element that is soon to be seen with a startElement() call. The official SAX call
526     * does not apply to the current element, hence the reason for this method.
527     */
528    public void namespaceAfterStartElement(
529        final String prefix,
530        final String uri)
531        throws SAXException
532    {
533
534        // hack for XSLTC with finding URI for default namespace
535        if (m_elemContext.m_elementURI == null)
536        {
537            String prefix1 = getPrefixPart(m_elemContext.m_elementName);
538            if (prefix1 == null && EMPTYSTRING.equals(prefix))
539            {
540                // the elements URI is not known yet, and it
541                // doesn't have a prefix, and we are currently
542                // setting the uri for prefix "", so we have
543                // the uri for the element... lets remember it
544                m_elemContext.m_elementURI = uri;
545            }
546        }
547        startPrefixMapping(prefix,uri,false);
548        return;
549
550    }
551
552    /**
553     * From XSLTC
554     * Declare a prefix to point to a namespace URI. Inform SAX handler
555     * if this is a new prefix mapping.
556     */
557    protected boolean pushNamespace(String prefix, String uri)
558    {
559        try
560        {
561            if (m_prefixMap.pushNamespace(
562                prefix, uri, m_elemContext.m_currentElemDepth))
563            {
564                startPrefixMapping(prefix, uri);
565                return true;
566            }
567        }
568        catch (SAXException e)
569        {
570            // falls through
571        }
572        return false;
573    }
574    /**
575     * Try's to reset the super class and reset this class for
576     * re-use, so that you don't need to create a new serializer
577     * (mostly for performance reasons).
578     *
579     * @return true if the class was successfuly reset.
580     */
581    public boolean reset()
582    {
583        boolean wasReset = false;
584        if (super.reset())
585        {
586            // Make this call when resetToXMLStream does
587            // something.
588            // resetToXMLStream();
589            wasReset = true;
590        }
591        return wasReset;
592    }
593
594    /**
595     * Reset all of the fields owned by ToStream class
596     *
597     */
598    private void resetToXMLStream()
599    {
600        // This is an empty method, but is kept for future use
601        // as a place holder for a location to reset fields
602        // defined within this class
603        return;
604    }
605
606    /**
607     * This method checks for the XML version of output document.
608     * If XML version of output document is not specified, then output
609     * document is of version XML 1.0.
610     * If XML version of output doucment is specified, but it is not either
611     * XML 1.0 or XML 1.1, a warning message is generated, the XML Version of
612     * output document is set to XML 1.0 and processing continues.
613     * @return string (XML version)
614     */
615    private String getXMLVersion()
616    {
617        String xmlVersion = getVersion();
618        if(xmlVersion == null || xmlVersion.equals(XMLVERSION10))
619        {
620            xmlVersion = XMLVERSION10;
621        }
622        else if(xmlVersion.equals(XMLVERSION11))
623        {
624            xmlVersion = XMLVERSION11;
625        }
626        else
627        {
628            String msg = Utils.messages.createMessage(
629                               MsgKey.ER_XML_VERSION_NOT_SUPPORTED,new Object[]{ xmlVersion });
630            try
631            {
632                // Prepare to issue the warning message
633                Transformer tran = super.getTransformer();
634                ErrorListener errHandler = tran.getErrorListener();
635                // Issue the warning message
636                if (null != errHandler && m_sourceLocator != null)
637                    errHandler.warning(new TransformerException(msg, m_sourceLocator));
638                else
639                    System.out.println(msg);
640            }
641            catch (Exception e){}
642            xmlVersion = XMLVERSION10;
643        }
644        return xmlVersion;
645    }
646}
647