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 
23 import java.io.IOException;
24 
25 import javax.xml.transform.ErrorListener;
26 import javax.xml.transform.Result;
27 import javax.xml.transform.Transformer;
28 import javax.xml.transform.TransformerException;
29 
30 import org.apache.xml.serializer.utils.MsgKey;
31 import org.apache.xml.serializer.utils.Utils;
32 import 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  */
44 public 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      */
ToXMLStream()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      */
CopyFrom(ToXMLStream xmlListener)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      */
startDocumentInternal()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      */
endDocument()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      */
startPreserving()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      */
endPreserving()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      */
processingInstruction(String target, String data)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      */
entityReference(String name)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      */
addUniqueAttribute(String name, String value, int flags)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      */
addAttribute( String uri, String localName, String rawName, String type, String value, boolean xslAttribute)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      */
endElement(String elemName)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      */
namespaceAfterStartElement( final String prefix, final String uri)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      */
pushNamespace(String prefix, String uri)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      */
reset()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      */
resetToXMLStream()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      */
getXMLVersion()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