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: ToXMLSAXHandler.java 468654 2006-10-28 07:09:23Z minchau $
020     */
021     package org.apache.xml.serializer;
022    
023    import java.io.IOException;
024    import java.io.OutputStream;
025    import java.io.Writer;
026    import java.util.Properties;
027    
028    import javax.xml.transform.Result;
029    
030    import org.w3c.dom.Node;
031    import org.xml.sax.Attributes;
032    import org.xml.sax.ContentHandler;
033    import org.xml.sax.Locator;
034    import org.xml.sax.SAXException;
035    import org.xml.sax.ext.LexicalHandler;
036    
037    /**
038     * This class receives notification of SAX-like events, and with gathered
039     * information over these calls it will invoke the equivalent SAX methods
040     * on a handler, the ultimate xsl:output method is known to be "xml".
041     * 
042     * This class is not a public API.
043     * @xsl.usage internal
044     */
045    public final class ToXMLSAXHandler extends ToSAXHandler
046    {
047    
048        /**
049         * Keeps track of whether output escaping is currently enabled
050         */
051        protected boolean m_escapeSetting = true;
052    
053        public ToXMLSAXHandler()
054        {
055            // default constructor (need to set content handler ASAP !)
056            m_prefixMap = new NamespaceMappings();
057            initCDATA();
058        }
059    
060        /**
061         * @see Serializer#getOutputFormat()
062         */
063        public Properties getOutputFormat()
064        {
065            return null;
066        }
067    
068        /**
069         * @see Serializer#getOutputStream()
070         */
071        public OutputStream getOutputStream()
072        {
073            return null;
074        }
075    
076        /**
077         * @see Serializer#getWriter()
078         */
079        public Writer getWriter()
080        {
081            return null;
082        }
083    
084        /**
085         * Do nothing for SAX.
086         */
087        public void indent(int n) throws SAXException
088        {
089        }
090    
091    
092        /**
093         * @see DOMSerializer#serialize(Node)
094         */
095        public void serialize(Node node) throws IOException
096        {
097        }
098    
099        /**
100         * @see SerializationHandler#setEscaping(boolean)
101         */
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         */
119        public void setOutputFormat(Properties format)
120        {
121        }
122    
123        /**
124         * @see Serializer#setOutputStream(OutputStream)
125         */
126        public void setOutputStream(OutputStream output)
127        {
128        }
129    
130        /**
131         * @see Serializer#setWriter(Writer)
132         */
133        public void setWriter(Writer writer)
134        {
135        }
136    
137        /**
138         * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
139         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
299        public void setDocumentLocator(Locator arg0)
300        {
301            m_saxHandler.setDocumentLocator(arg0);
302        }
303    
304        /**
305         * @see org.xml.sax.ContentHandler#skippedEntity(String)
306         */
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         */
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    
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         */
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         */
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         */
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         */
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         */
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    
471        public ToXMLSAXHandler(ContentHandler handler, String encoding)
472        {
473            super(handler, encoding);
474    
475            initCDATA();
476            // initNamespaces();
477            m_prefixMap = new NamespaceMappings();
478        }
479    
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         */
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        }
506        public void startElement(String elementName) throws SAXException
507        {
508            startElement(null, null, elementName, null);
509        }
510    
511    
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         */
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         */
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         */
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         */
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    
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         */
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     
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         */
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         */
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         */
769        private void resetToXMLSAXHandler()
770        {
771            this.m_escapeSetting = true;
772        }  
773    
774    }