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