001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: ToXMLStream.java 469359 2006-10-31 03:43:19Z minchau $
020     */
021     package org.apache.xml.serializer;
022    
023    import java.io.IOException;
024    
025    import javax.xml.transform.ErrorListener;
026    import javax.xml.transform.Result;
027    import javax.xml.transform.Transformer;
028    import javax.xml.transform.TransformerException;
029    
030    import org.apache.xml.serializer.utils.MsgKey;
031    import org.apache.xml.serializer.utils.Utils;
032    import org.xml.sax.SAXException;
033    
034    /**
035     * This class converts SAX or SAX-like calls to a 
036     * serialized xml document.  The xsl:output method is "xml".
037     * 
038     * This class is used explicitly in code generated by XSLTC, 
039     * so it is "public", but it should 
040     * be viewed as internal or package private, this is not an API.
041     * 
042     * @xsl.usage internal
043     */
044    public class ToXMLStream extends ToStream
045    {
046        /**
047         * Map that tells which XML characters should have special treatment, and it
048         *  provides character to entity name lookup.
049         */
050        private CharInfo m_xmlcharInfo =
051            CharInfo.getCharInfo(CharInfo.XML_ENTITIES_RESOURCE, Method.XML);
052    
053        /**
054         * Default constructor.
055         */
056        public ToXMLStream()
057        {
058            m_charInfo = m_xmlcharInfo;
059    
060            initCDATA();
061            // initialize namespaces
062            m_prefixMap = new NamespaceMappings();
063    
064        }
065    
066        /**
067         * Copy properties from another SerializerToXML.
068         *
069         * @param xmlListener non-null reference to a SerializerToXML object.
070         */
071        public void CopyFrom(ToXMLStream xmlListener)
072        {
073    
074            setWriter(xmlListener.m_writer);
075    
076    
077            // m_outputStream = xmlListener.m_outputStream;
078            String encoding = xmlListener.getEncoding();
079            setEncoding(encoding);
080    
081            setOmitXMLDeclaration(xmlListener.getOmitXMLDeclaration());
082    
083            m_ispreserve = xmlListener.m_ispreserve;
084            m_preserves = xmlListener.m_preserves;
085            m_isprevtext = xmlListener.m_isprevtext;
086            m_doIndent = xmlListener.m_doIndent;
087            setIndentAmount(xmlListener.getIndentAmount());
088            m_startNewLine = xmlListener.m_startNewLine;
089            m_needToOutputDocTypeDecl = xmlListener.m_needToOutputDocTypeDecl;
090            setDoctypeSystem(xmlListener.getDoctypeSystem());
091            setDoctypePublic(xmlListener.getDoctypePublic());        
092            setStandalone(xmlListener.getStandalone());
093            setMediaType(xmlListener.getMediaType());
094            m_encodingInfo = xmlListener.m_encodingInfo;
095            m_spaceBeforeClose = xmlListener.m_spaceBeforeClose;
096            m_cdataStartCalled = xmlListener.m_cdataStartCalled;
097    
098        }
099    
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    }