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: ToXMLSAXHandler.java 468654 2006-10-28 07:09:23Z minchau $
20  */
21  package org.apache.xml.serializer;
22 
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.io.Writer;
26 import java.util.Properties;
27 
28 import javax.xml.transform.Result;
29 
30 import org.w3c.dom.Node;
31 import org.xml.sax.Attributes;
32 import org.xml.sax.ContentHandler;
33 import org.xml.sax.Locator;
34 import org.xml.sax.SAXException;
35 import org.xml.sax.ext.LexicalHandler;
36 
37 /**
38  * This class receives notification of SAX-like events, and with gathered
39  * information over these calls it will invoke the equivalent SAX methods
40  * on a handler, the ultimate xsl:output method is known to be "xml".
41  *
42  * This class is not a public API.
43  * @xsl.usage internal
44  */
45 public final class ToXMLSAXHandler extends ToSAXHandler
46 {
47 
48     /**
49      * Keeps track of whether output escaping is currently enabled
50      */
51     protected boolean m_escapeSetting = true;
52 
ToXMLSAXHandler()53     public ToXMLSAXHandler()
54     {
55         // default constructor (need to set content handler ASAP !)
56         m_prefixMap = new NamespaceMappings();
57         initCDATA();
58     }
59 
60     /**
61      * @see Serializer#getOutputFormat()
62      */
getOutputFormat()63     public Properties getOutputFormat()
64     {
65         return null;
66     }
67 
68     /**
69      * @see Serializer#getOutputStream()
70      */
getOutputStream()71     public OutputStream getOutputStream()
72     {
73         return null;
74     }
75 
76     /**
77      * @see Serializer#getWriter()
78      */
getWriter()79     public Writer getWriter()
80     {
81         return null;
82     }
83 
84     /**
85      * Do nothing for SAX.
86      */
indent(int n)87     public void indent(int n) throws SAXException
88     {
89     }
90 
91 
92     /**
93      * @see DOMSerializer#serialize(Node)
94      */
serialize(Node node)95     public void serialize(Node node) throws IOException
96     {
97     }
98 
99     /**
100      * @see SerializationHandler#setEscaping(boolean)
101      */
setEscaping(boolean escape)102     public boolean setEscaping(boolean escape) throws SAXException
103     {
104         boolean oldEscapeSetting = m_escapeSetting;
105         m_escapeSetting = escape;
106 
107         if (escape) {
108             processingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, "");
109         } else {
110             processingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, "");
111         }
112 
113         return oldEscapeSetting;
114     }
115 
116     /**
117      * @see Serializer#setOutputFormat(Properties)
118      */
setOutputFormat(Properties format)119     public void setOutputFormat(Properties format)
120     {
121     }
122 
123     /**
124      * @see Serializer#setOutputStream(OutputStream)
125      */
setOutputStream(OutputStream output)126     public void setOutputStream(OutputStream output)
127     {
128     }
129 
130     /**
131      * @see Serializer#setWriter(Writer)
132      */
setWriter(Writer writer)133     public void setWriter(Writer writer)
134     {
135     }
136 
137     /**
138      * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
139      */
attributeDecl( String arg0, String arg1, String arg2, String arg3, String arg4)140     public void attributeDecl(
141         String arg0,
142         String arg1,
143         String arg2,
144         String arg3,
145         String arg4)
146         throws SAXException
147     {
148     }
149 
150     /**
151      * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
152      */
elementDecl(String arg0, String arg1)153     public void elementDecl(String arg0, String arg1) throws SAXException
154     {
155     }
156 
157     /**
158      * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
159      */
externalEntityDecl(String arg0, String arg1, String arg2)160     public void externalEntityDecl(String arg0, String arg1, String arg2)
161         throws SAXException
162     {
163     }
164 
165     /**
166      * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(String, String)
167      */
internalEntityDecl(String arg0, String arg1)168     public void internalEntityDecl(String arg0, String arg1)
169         throws SAXException
170     {
171     }
172 
173     /**
174      * Receives notification of the end of the document.
175      * @see org.xml.sax.ContentHandler#endDocument()
176      */
endDocument()177     public void endDocument() throws SAXException
178     {
179 
180         flushPending();
181 
182         // Close output document
183         m_saxHandler.endDocument();
184 
185         if (m_tracer != null)
186             super.fireEndDoc();
187     }
188 
189     /**
190      * This method is called when all the data needed for a call to the
191      * SAX handler's startElement() method has been gathered.
192      */
closeStartTag()193     protected void closeStartTag() throws SAXException
194     {
195 
196         m_elemContext.m_startTagOpen = false;
197 
198         final String localName = getLocalName(m_elemContext.m_elementName);
199         final String uri = getNamespaceURI(m_elemContext.m_elementName, true);
200 
201         // Now is time to send the startElement event
202         if (m_needToCallStartDocument)
203         {
204             startDocumentInternal();
205         }
206         m_saxHandler.startElement(uri, localName, m_elemContext.m_elementName, m_attributes);
207         // we've sent the official SAX attributes on their way,
208         // now we don't need them anymore.
209         m_attributes.clear();
210 
211         if(m_state != null)
212           m_state.setCurrentNode(null);
213     }
214 
215     /**
216      * Closes ane open cdata tag, and
217      * unlike the this.endCDATA() method (from the LexicalHandler) interface,
218      * this "internal" method will send the endCDATA() call to the wrapped
219      * handler.
220      *
221      */
closeCDATA()222     public void closeCDATA() throws SAXException
223     {
224 
225         // Output closing bracket - "]]>"
226         if (m_lexHandler != null && m_cdataTagOpen) {
227             m_lexHandler.endCDATA();
228         }
229 
230 
231         // There are no longer any calls made to
232         // m_lexHandler.startCDATA() without a balancing call to
233         // m_lexHandler.endCDATA()
234         // so we set m_cdataTagOpen to false to remember this.
235         m_cdataTagOpen = false;
236     }
237 
238     /**
239      * @see org.xml.sax.ContentHandler#endElement(String, String, String)
240      */
endElement(String namespaceURI, String localName, String qName)241     public void endElement(String namespaceURI, String localName, String qName)
242         throws SAXException
243     {
244         // Close any open elements etc.
245         flushPending();
246 
247         if (namespaceURI == null)
248         {
249             if (m_elemContext.m_elementURI != null)
250                 namespaceURI = m_elemContext.m_elementURI;
251             else
252                 namespaceURI = getNamespaceURI(qName, true);
253         }
254 
255         if (localName == null)
256         {
257             if (m_elemContext.m_elementLocalName != null)
258                 localName = m_elemContext.m_elementLocalName;
259             else
260                 localName = getLocalName(qName);
261         }
262 
263         m_saxHandler.endElement(namespaceURI, localName, qName);
264 
265         if (m_tracer != null)
266             super.fireEndElem(qName);
267 
268         /* Pop all namespaces at the current element depth.
269          * We are not waiting for official endPrefixMapping() calls.
270          */
271         m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth,
272             m_saxHandler);
273         m_elemContext = m_elemContext.m_prev;
274     }
275 
276     /**
277      * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
278      */
endPrefixMapping(String prefix)279     public void endPrefixMapping(String prefix) throws SAXException
280     {
281         /* poping all prefix mappings should have been done
282          * in endElement() already
283          */
284          return;
285     }
286 
287     /**
288      * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
289      */
ignorableWhitespace(char[] arg0, int arg1, int arg2)290     public void ignorableWhitespace(char[] arg0, int arg1, int arg2)
291         throws SAXException
292     {
293         m_saxHandler.ignorableWhitespace(arg0,arg1,arg2);
294     }
295 
296     /**
297      * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
298      */
setDocumentLocator(Locator arg0)299     public void setDocumentLocator(Locator arg0)
300     {
301         m_saxHandler.setDocumentLocator(arg0);
302     }
303 
304     /**
305      * @see org.xml.sax.ContentHandler#skippedEntity(String)
306      */
skippedEntity(String arg0)307     public void skippedEntity(String arg0) throws SAXException
308     {
309         m_saxHandler.skippedEntity(arg0);
310     }
311 
312     /**
313      * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
314      * @param prefix The prefix that maps to the URI
315      * @param uri The URI for the namespace
316      */
startPrefixMapping(String prefix, String uri)317     public void startPrefixMapping(String prefix, String uri)
318         throws SAXException
319     {
320        startPrefixMapping(prefix, uri, true);
321     }
322 
323     /**
324      * Remember the prefix/uri mapping at the current nested element depth.
325      *
326      * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
327      * @param prefix The prefix that maps to the URI
328      * @param uri The URI for the namespace
329      * @param shouldFlush a flag indicating if the mapping applies to the
330      * current element or an up coming child (not used).
331      */
332 
startPrefixMapping( String prefix, String uri, boolean shouldFlush)333     public boolean startPrefixMapping(
334         String prefix,
335         String uri,
336         boolean shouldFlush)
337         throws org.xml.sax.SAXException
338     {
339 
340         /* Remember the mapping, and at what depth it was declared
341          * This is one greater than the current depth because these
342          * mappings will apply to the next depth. This is in
343          * consideration that startElement() will soon be called
344          */
345 
346         boolean pushed;
347         int pushDepth;
348         if (shouldFlush)
349         {
350             flushPending();
351             // the prefix mapping applies to the child element (one deeper)
352             pushDepth = m_elemContext.m_currentElemDepth + 1;
353         }
354         else
355         {
356             // the prefix mapping applies to the current element
357             pushDepth = m_elemContext.m_currentElemDepth;
358         }
359         pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
360 
361         if (pushed)
362         {
363             m_saxHandler.startPrefixMapping(prefix,uri);
364 
365             if (getShouldOutputNSAttr())
366             {
367 
368 	              /* I don't know if we really needto do this. The
369 	               * callers of this object should have injected both
370 	               * startPrefixMapping and the attributes.  We are
371 	               * just covering our butt here.
372 	               */
373 	              String name;
374   	            if (EMPTYSTRING.equals(prefix))
375   	            {
376   	                name = "xmlns";
377   	                addAttributeAlways(XMLNS_URI, name, name,"CDATA",uri, false);
378   	            }
379   	            else
380                 {
381   	                if (!EMPTYSTRING.equals(uri)) // hack for attribset16 test
382   	                {                             // that maps ns1 prefix to "" URI
383   	                    name = "xmlns:" + prefix;
384 
385   	                    /* for something like xmlns:abc="w3.pretend.org"
386   	             	 	     *  the uri is the value, that is why we pass it in the
387   	             	 	     * value, or 5th slot of addAttributeAlways()
388   	                 	   */
389   	                    addAttributeAlways(XMLNS_URI, prefix, name,"CDATA",uri, false );
390   	                }
391   	            }
392             }
393         }
394         return pushed;
395     }
396 
397 
398     /**
399      * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
400      */
comment(char[] arg0, int arg1, int arg2)401     public void comment(char[] arg0, int arg1, int arg2) throws SAXException
402     {
403         flushPending();
404         if (m_lexHandler != null)
405             m_lexHandler.comment(arg0, arg1, arg2);
406 
407         if (m_tracer != null)
408             super.fireCommentEvent(arg0, arg1, arg2);
409     }
410 
411     /**
412      * @see org.xml.sax.ext.LexicalHandler#endCDATA()
413      */
endCDATA()414     public void endCDATA() throws SAXException
415     {
416         /* Normally we would do somthing with this but we ignore it.
417          * The neccessary call to m_lexHandler.endCDATA() will be made
418          * in flushPending().
419          *
420          * This is so that if we get calls like these:
421          *   this.startCDATA();
422          *   this.characters(chars1, off1, len1);
423          *   this.endCDATA();
424          *   this.startCDATA();
425          *   this.characters(chars2, off2, len2);
426          *   this.endCDATA();
427          *
428          * that we will only make these calls to the wrapped handlers:
429          *
430          *   m_lexHandler.startCDATA();
431          *   m_saxHandler.characters(chars1, off1, len1);
432          *   m_saxHandler.characters(chars1, off2, len2);
433          *   m_lexHandler.endCDATA();
434          *
435          * We will merge adjacent CDATA blocks.
436          */
437     }
438 
439     /**
440      * @see org.xml.sax.ext.LexicalHandler#endDTD()
441      */
endDTD()442     public void endDTD() throws SAXException
443     {
444         if (m_lexHandler != null)
445             m_lexHandler.endDTD();
446     }
447 
448     /**
449      * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
450      */
startEntity(String arg0)451     public void startEntity(String arg0) throws SAXException
452     {
453         if (m_lexHandler != null)
454             m_lexHandler.startEntity(arg0);
455     }
456 
457     /**
458      * @see ExtendedContentHandler#characters(String)
459      */
characters(String chars)460     public void characters(String chars) throws SAXException
461     {
462         final int length = chars.length();
463         if (length > m_charsBuff.length)
464         {
465             m_charsBuff = new char[length*2 + 1];
466         }
467         chars.getChars(0, length, m_charsBuff, 0);
468         this.characters(m_charsBuff, 0, length);
469     }
470 
ToXMLSAXHandler(ContentHandler handler, String encoding)471     public ToXMLSAXHandler(ContentHandler handler, String encoding)
472     {
473         super(handler, encoding);
474 
475         initCDATA();
476         // initNamespaces();
477         m_prefixMap = new NamespaceMappings();
478     }
479 
ToXMLSAXHandler( ContentHandler handler, LexicalHandler lex, String encoding)480     public ToXMLSAXHandler(
481         ContentHandler handler,
482         LexicalHandler lex,
483         String encoding)
484     {
485         super(handler, lex, encoding);
486 
487         initCDATA();
488         //      initNamespaces();
489         m_prefixMap = new NamespaceMappings();
490     }
491 
492     /**
493      * Start an element in the output document. This might be an XML element
494      * (<elem>data</elem> type) or a CDATA section.
495      */
startElement( String elementNamespaceURI, String elementLocalName, String elementName)496     public void startElement(
497     String elementNamespaceURI,
498     String elementLocalName,
499     String elementName) throws SAXException
500     {
501         startElement(
502             elementNamespaceURI,elementLocalName,elementName, null);
503 
504 
505     }
startElement(String elementName)506     public void startElement(String elementName) throws SAXException
507     {
508         startElement(null, null, elementName, null);
509     }
510 
511 
characters(char[] ch, int off, int len)512     public void characters(char[] ch, int off, int len) throws SAXException
513     {
514         // We do the first two things in flushPending() but we don't
515         // close any open CDATA calls.
516         if (m_needToCallStartDocument)
517         {
518             startDocumentInternal();
519             m_needToCallStartDocument = false;
520         }
521 
522         if (m_elemContext.m_startTagOpen)
523         {
524             closeStartTag();
525             m_elemContext.m_startTagOpen = false;
526         }
527 
528         if (m_elemContext.m_isCdataSection && !m_cdataTagOpen
529         && m_lexHandler != null)
530         {
531             m_lexHandler.startCDATA();
532             // We have made a call to m_lexHandler.startCDATA() with
533             // no balancing call to m_lexHandler.endCDATA()
534             // so we set m_cdataTagOpen true to remember this.
535             m_cdataTagOpen = true;
536         }
537 
538         /* If there are any occurances of "]]>" in the character data
539          * let m_saxHandler worry about it, we've already warned them with
540          * the previous call of m_lexHandler.startCDATA();
541          */
542         m_saxHandler.characters(ch, off, len);
543 
544         // time to generate characters event
545         if (m_tracer != null)
546             fireCharEvent(ch, off, len);
547     }
548 
549 
550     /**
551      * @see ExtendedContentHandler#endElement(String)
552      */
endElement(String elemName)553     public void endElement(String elemName) throws SAXException
554     {
555         endElement(null, null, elemName);
556     }
557 
558 
559     /**
560      * Send a namespace declaration in the output document. The namespace
561      * declaration will not be include if the namespace is already in scope
562      * with the same prefix.
563      */
namespaceAfterStartElement( final String prefix, final String uri)564     public void namespaceAfterStartElement(
565         final String prefix,
566         final String uri)
567         throws SAXException
568     {
569         startPrefixMapping(prefix,uri,false);
570     }
571 
572     /**
573      *
574      * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
575      * Send a processing instruction to the output document
576      */
processingInstruction(String target, String data)577     public void processingInstruction(String target, String data)
578         throws SAXException
579     {
580         flushPending();
581 
582         // Pass the processing instruction to the SAX handler
583         m_saxHandler.processingInstruction(target, data);
584 
585         // we don't want to leave serializer to fire off this event,
586         // so do it here.
587         if (m_tracer != null)
588             super.fireEscapingEvent(target, data);
589     }
590 
591     /**
592      * Undeclare the namespace that is currently pointed to by a given
593      * prefix. Inform SAX handler if prefix was previously mapped.
594      */
popNamespace(String prefix)595     protected boolean popNamespace(String prefix)
596     {
597         try
598         {
599             if (m_prefixMap.popNamespace(prefix))
600             {
601                 m_saxHandler.endPrefixMapping(prefix);
602                 return true;
603             }
604         }
605         catch (SAXException e)
606         {
607             // falls through
608         }
609         return false;
610     }
611 
startCDATA()612     public void startCDATA() throws SAXException
613     {
614         /* m_cdataTagOpen can only be true here if we have ignored the
615          * previous call to this.endCDATA() and the previous call
616          * this.startCDATA() before that is still "open". In this way
617          * we merge adjacent CDATA. If anything else happened after the
618          * ignored call to this.endCDATA() and this call then a call to
619          * flushPending() would have been made which would have
620          * closed the CDATA and set m_cdataTagOpen to false.
621          */
622         if (!m_cdataTagOpen )
623         {
624             flushPending();
625             if (m_lexHandler != null) {
626                 m_lexHandler.startCDATA();
627 
628                 // We have made a call to m_lexHandler.startCDATA() with
629                 // no balancing call to m_lexHandler.endCDATA()
630                 // so we set m_cdataTagOpen true to remember this.
631                 m_cdataTagOpen = true;
632             }
633         }
634     }
635 
636     /**
637      * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
638      */
startElement( String namespaceURI, String localName, String name, Attributes atts)639     public void startElement(
640     String namespaceURI,
641     String localName,
642     String name,
643     Attributes atts)
644         throws SAXException
645     {
646         flushPending();
647         super.startElement(namespaceURI, localName, name, atts);
648 
649         // Handle document type declaration (for first element only)
650          if (m_needToOutputDocTypeDecl)
651          {
652              String doctypeSystem = getDoctypeSystem();
653              if (doctypeSystem != null && m_lexHandler != null)
654              {
655                  String doctypePublic = getDoctypePublic();
656                  if (doctypeSystem != null)
657                      m_lexHandler.startDTD(
658                          name,
659                          doctypePublic,
660                          doctypeSystem);
661              }
662              m_needToOutputDocTypeDecl = false;
663          }
664         m_elemContext = m_elemContext.push(namespaceURI, localName, name);
665 
666         // ensurePrefixIsDeclared depends on the current depth, so
667         // the previous increment is necessary where it is.
668         if (namespaceURI != null)
669             ensurePrefixIsDeclared(namespaceURI, name);
670 
671         // add the attributes to the collected ones
672         if (atts != null)
673             addAttributes(atts);
674 
675 
676         // do we really need this CDATA section state?
677         m_elemContext.m_isCdataSection = isCdataSection();
678 
679     }
680 
ensurePrefixIsDeclared(String ns, String rawName)681     private void ensurePrefixIsDeclared(String ns, String rawName)
682         throws org.xml.sax.SAXException
683     {
684 
685         if (ns != null && ns.length() > 0)
686         {
687             int index;
688             final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
689             String prefix = (no_prefix) ? "" : rawName.substring(0, index);
690 
691 
692             if (null != prefix)
693             {
694                 String foundURI = m_prefixMap.lookupNamespace(prefix);
695 
696                 if ((null == foundURI) || !foundURI.equals(ns))
697                 {
698                     this.startPrefixMapping(prefix, ns, false);
699 
700                     if (getShouldOutputNSAttr()) {
701                         // Bugzilla1133: Generate attribute as well as namespace event.
702                         // SAX does expect both.
703                         this.addAttributeAlways(
704                             "http://www.w3.org/2000/xmlns/",
705                             no_prefix ? "xmlns" : prefix,  // local name
706                             no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname
707                             "CDATA",
708                             ns,
709                             false);
710                     }
711                 }
712 
713             }
714         }
715     }
716     /**
717      * Adds the given attribute to the set of attributes, and also makes sure
718      * that the needed prefix/uri mapping is declared, but only if there is a
719      * currently open element.
720      *
721      * @param uri the URI of the attribute
722      * @param localName the local name of the attribute
723      * @param rawName    the qualified name of the attribute
724      * @param type the type of the attribute (probably CDATA)
725      * @param value the value of the attribute
726      * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
727      * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
728      */
addAttribute( String uri, String localName, String rawName, String type, String value, boolean XSLAttribute)729     public void addAttribute(
730         String uri,
731         String localName,
732         String rawName,
733         String type,
734         String value,
735         boolean XSLAttribute)
736         throws SAXException
737     {
738         if (m_elemContext.m_startTagOpen)
739         {
740             ensurePrefixIsDeclared(uri, rawName);
741             addAttributeAlways(uri, localName, rawName, type, value, false);
742         }
743 
744     }
745 
746     /**
747      * Try's to reset the super class and reset this class for
748      * re-use, so that you don't need to create a new serializer
749      * (mostly for performance reasons).
750      *
751      * @return true if the class was successfuly reset.
752      * @see Serializer#reset()
753      */
reset()754     public boolean reset()
755     {
756         boolean wasReset = false;
757         if (super.reset())
758         {
759             resetToXMLSAXHandler();
760             wasReset = true;
761         }
762         return wasReset;
763     }
764 
765     /**
766      * Reset all of the fields owned by ToXMLSAXHandler class
767      *
768      */
resetToXMLSAXHandler()769     private void resetToXMLSAXHandler()
770     {
771         this.m_escapeSetting = true;
772     }
773 
774 }
775