1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21 package com.sun.org.apache.xml.internal.serializer;
22
23import java.io.IOException;
24import java.io.OutputStream;
25import java.io.Writer;
26import java.util.Properties;
27
28import javax.xml.transform.Result;
29
30import org.w3c.dom.Node;
31import org.xml.sax.Attributes;
32import org.xml.sax.ContentHandler;
33import org.xml.sax.Locator;
34import org.xml.sax.SAXException;
35import 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, it is only public because it is used by Xalan.
43 * @xsl.usage internal
44 */
45public 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
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     */
63    public Properties getOutputFormat()
64    {
65        return null;
66    }
67
68    /**
69     * @see Serializer#getOutputStream()
70     */
71    public OutputStream getOutputStream()
72    {
73        return null;
74    }
75
76    /**
77     * @see Serializer#getWriter()
78     */
79    public Writer getWriter()
80    {
81        return null;
82    }
83
84    /**
85     * Do nothing for SAX.
86     */
87    public void indent(int n) throws SAXException
88    {
89    }
90
91
92    /**
93     * @see DOMSerializer#serialize(Node)
94     */
95    public void serialize(Node node) throws IOException
96    {
97    }
98
99    /**
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        super.setDocumentLocator(arg0);
302        m_saxHandler.setDocumentLocator(arg0);
303    }
304
305    /**
306     * @see org.xml.sax.ContentHandler#skippedEntity(String)
307     */
308    public void skippedEntity(String arg0) throws SAXException
309    {
310        m_saxHandler.skippedEntity(arg0);
311    }
312
313    /**
314     * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
315     * @param prefix The prefix that maps to the URI
316     * @param uri The URI for the namespace
317     */
318    public void startPrefixMapping(String prefix, String uri)
319        throws SAXException
320    {
321       startPrefixMapping(prefix, uri, true);
322    }
323
324    /**
325     * Remember the prefix/uri mapping at the current nested element depth.
326     *
327     * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
328     * @param prefix The prefix that maps to the URI
329     * @param uri The URI for the namespace
330     * @param shouldFlush a flag indicating if the mapping applies to the
331     * current element or an up coming child (not used).
332     */
333
334    public boolean startPrefixMapping(
335        String prefix,
336        String uri,
337        boolean shouldFlush)
338        throws org.xml.sax.SAXException
339    {
340
341        /* Remember the mapping, and at what depth it was declared
342         * This is one greater than the current depth because these
343         * mappings will apply to the next depth. This is in
344         * consideration that startElement() will soon be called
345         */
346
347        boolean pushed;
348        int pushDepth;
349        if (shouldFlush)
350        {
351            flushPending();
352            // the prefix mapping applies to the child element (one deeper)
353            pushDepth = m_elemContext.m_currentElemDepth + 1;
354        }
355        else
356        {
357            // the prefix mapping applies to the current element
358            pushDepth = m_elemContext.m_currentElemDepth;
359        }
360        pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
361
362        if (pushed)
363        {
364            m_saxHandler.startPrefixMapping(prefix,uri);
365
366            if (getShouldOutputNSAttr())
367            {
368
369                      /* Brian M.: don't know if we really needto do this. The
370                       * callers of this object should have injected both
371                       * startPrefixMapping and the attributes.  We are
372                       * just covering our butt here.
373                       */
374                      String name;
375                    if (EMPTYSTRING.equals(prefix))
376                    {
377                        name = "xmlns";
378                        addAttributeAlways(XMLNS_URI, name, name,"CDATA",uri, false);
379                    }
380                    else
381                {
382                        if (!EMPTYSTRING.equals(uri)) // hack for XSLTC attribset16 test
383                        {                             // that maps ns1 prefix to "" URI
384                            name = "xmlns:" + prefix;
385
386                            /* for something like xmlns:abc="w3.pretend.org"
387                                     *  the uri is the value, that is why we pass it in the
388                                     * value, or 5th slot of addAttributeAlways()
389                                   */
390                            addAttributeAlways(XMLNS_URI, prefix, name,"CDATA",uri, false );
391                        }
392                    }
393            }
394        }
395        return pushed;
396    }
397
398
399    /**
400     * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
401     */
402    public void comment(char[] arg0, int arg1, int arg2) throws SAXException
403    {
404        flushPending();
405        if (m_lexHandler != null)
406            m_lexHandler.comment(arg0, arg1, arg2);
407
408        if (m_tracer != null)
409            super.fireCommentEvent(arg0, arg1, arg2);
410    }
411
412    /**
413     * @see org.xml.sax.ext.LexicalHandler#endCDATA()
414     */
415    public void endCDATA() throws SAXException
416    {
417        /* Normally we would do somthing with this but we ignore it.
418         * The neccessary call to m_lexHandler.endCDATA() will be made
419         * in flushPending().
420         *
421         * This is so that if we get calls like these:
422         *   this.startCDATA();
423         *   this.characters(chars1, off1, len1);
424         *   this.endCDATA();
425         *   this.startCDATA();
426         *   this.characters(chars2, off2, len2);
427         *   this.endCDATA();
428         *
429         * that we will only make these calls to the wrapped handlers:
430         *
431         *   m_lexHandler.startCDATA();
432         *   m_saxHandler.characters(chars1, off1, len1);
433         *   m_saxHandler.characters(chars1, off2, len2);
434         *   m_lexHandler.endCDATA();
435         *
436         * We will merge adjacent CDATA blocks.
437         */
438    }
439
440    /**
441     * @see org.xml.sax.ext.LexicalHandler#endDTD()
442     */
443    public void endDTD() throws SAXException
444    {
445        if (m_lexHandler != null)
446            m_lexHandler.endDTD();
447    }
448
449    /**
450     * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
451     */
452    public void startEntity(String arg0) throws SAXException
453    {
454        if (m_lexHandler != null)
455            m_lexHandler.startEntity(arg0);
456    }
457
458    /**
459     * @see ExtendedContentHandler#characters(String)
460     */
461    public void characters(String chars) throws SAXException
462    {
463        final int length = chars.length();
464        if (length > m_charsBuff.length)
465        {
466            m_charsBuff = new char[length*2 + 1];
467        }
468        chars.getChars(0, length, m_charsBuff, 0);
469        this.characters(m_charsBuff, 0, length);
470    }
471
472    /////////////////// from XSLTC //////////////
473    public ToXMLSAXHandler(ContentHandler handler, String encoding)
474    {
475        super(handler, encoding);
476
477        initCDATA();
478        // initNamespaces();
479        m_prefixMap = new NamespaceMappings();
480    }
481
482    public ToXMLSAXHandler(
483        ContentHandler handler,
484        LexicalHandler lex,
485        String encoding)
486    {
487        super(handler, lex, encoding);
488
489        initCDATA();
490        //      initNamespaces();
491        m_prefixMap = new NamespaceMappings();
492    }
493
494    /**
495     * Start an element in the output document. This might be an XML element
496     * (<elem>data</elem> type) or a CDATA section.
497     */
498    public void startElement(
499    String elementNamespaceURI,
500    String elementLocalName,
501    String elementName) throws SAXException
502    {
503        startElement(
504            elementNamespaceURI,elementLocalName,elementName, null);
505
506
507    }
508    public void startElement(String elementName) throws SAXException
509    {
510        startElement(null, null, elementName, null);
511    }
512
513
514    public void characters(char[] ch, int off, int len) throws SAXException
515    {
516        // We do the first two things in flushPending() but we don't
517        // close any open CDATA calls.
518        if (m_needToCallStartDocument)
519        {
520            startDocumentInternal();
521            m_needToCallStartDocument = false;
522        }
523
524        if (m_elemContext.m_startTagOpen)
525        {
526            closeStartTag();
527            m_elemContext.m_startTagOpen = false;
528        }
529
530        if (m_elemContext.m_isCdataSection && !m_cdataTagOpen
531        && m_lexHandler != null)
532        {
533            m_lexHandler.startCDATA();
534            // We have made a call to m_lexHandler.startCDATA() with
535            // no balancing call to m_lexHandler.endCDATA()
536            // so we set m_cdataTagOpen true to remember this.
537            m_cdataTagOpen = true;
538        }
539
540        /* If there are any occurances of "]]>" in the character data
541         * let m_saxHandler worry about it, we've already warned them with
542         * the previous call of m_lexHandler.startCDATA();
543         */
544        m_saxHandler.characters(ch, off, len);
545
546        // time to generate characters event
547        if (m_tracer != null)
548            fireCharEvent(ch, off, len);
549    }
550
551
552    /**
553     * @see ExtendedContentHandler#endElement(String)
554     */
555    public void endElement(String elemName) throws SAXException
556    {
557        endElement(null, null, elemName);
558    }
559
560
561    /**
562     * Send a namespace declaration in the output document. The namespace
563     * declaration will not be include if the namespace is already in scope
564     * with the same prefix.
565     */
566    public void namespaceAfterStartElement(
567        final String prefix,
568        final String uri)
569        throws SAXException
570    {
571        startPrefixMapping(prefix,uri,false);
572    }
573
574    /**
575     *
576     * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
577     * Send a processing instruction to the output document
578     */
579    public void processingInstruction(String target, String data)
580        throws SAXException
581    {
582        flushPending();
583
584        // Pass the processing instruction to the SAX handler
585        m_saxHandler.processingInstruction(target, data);
586
587        // we don't want to leave serializer to fire off this event,
588        // so do it here.
589        if (m_tracer != null)
590            super.fireEscapingEvent(target, data);
591    }
592
593    /**
594     * Undeclare the namespace that is currently pointed to by a given
595     * prefix. Inform SAX handler if prefix was previously mapped.
596     */
597    protected boolean popNamespace(String prefix)
598    {
599        try
600        {
601            if (m_prefixMap.popNamespace(prefix))
602            {
603                m_saxHandler.endPrefixMapping(prefix);
604                return true;
605            }
606        }
607        catch (SAXException e)
608        {
609            // falls through
610        }
611        return false;
612    }
613
614    public void startCDATA() throws SAXException
615    {
616        /* m_cdataTagOpen can only be true here if we have ignored the
617         * previous call to this.endCDATA() and the previous call
618         * this.startCDATA() before that is still "open". In this way
619         * we merge adjacent CDATA. If anything else happened after the
620         * ignored call to this.endCDATA() and this call then a call to
621         * flushPending() would have been made which would have
622         * closed the CDATA and set m_cdataTagOpen to false.
623         */
624        if (!m_cdataTagOpen )
625        {
626            flushPending();
627            if (m_lexHandler != null) {
628                m_lexHandler.startCDATA();
629
630                // We have made a call to m_lexHandler.startCDATA() with
631                // no balancing call to m_lexHandler.endCDATA()
632                // so we set m_cdataTagOpen true to remember this.
633                m_cdataTagOpen = true;
634            }
635        }
636    }
637
638    /**
639     * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
640     */
641    public void startElement(
642    String namespaceURI,
643    String localName,
644    String name,
645    Attributes atts)
646        throws SAXException
647    {
648        flushPending();
649        super.startElement(namespaceURI, localName, name, atts);
650
651        // Handle document type declaration (for first element only)
652         if (m_needToOutputDocTypeDecl)
653         {
654             String doctypeSystem = getDoctypeSystem();
655             if (doctypeSystem != null && m_lexHandler != null)
656             {
657                 String doctypePublic = getDoctypePublic();
658                 if (doctypeSystem != null)
659                     m_lexHandler.startDTD(
660                         name,
661                         doctypePublic,
662                         doctypeSystem);
663             }
664             m_needToOutputDocTypeDecl = false;
665         }
666        m_elemContext = m_elemContext.push(namespaceURI, localName, name);
667
668        // ensurePrefixIsDeclared depends on the current depth, so
669        // the previous increment is necessary where it is.
670        if (namespaceURI != null)
671            ensurePrefixIsDeclared(namespaceURI, name);
672
673        // add the attributes to the collected ones
674        if (atts != null)
675            addAttributes(atts);
676
677
678        // do we really need this CDATA section state?
679        m_elemContext.m_isCdataSection = isCdataSection();
680
681    }
682
683    private void ensurePrefixIsDeclared(String ns, String rawName)
684        throws org.xml.sax.SAXException
685    {
686
687        if (ns != null && ns.length() > 0)
688        {
689            int index;
690            final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
691            String prefix = (no_prefix) ? "" : rawName.substring(0, index);
692
693
694            if (null != prefix)
695            {
696                String foundURI = m_prefixMap.lookupNamespace(prefix);
697
698                if ((null == foundURI) || !foundURI.equals(ns))
699                {
700                    this.startPrefixMapping(prefix, ns, false);
701
702                    if (getShouldOutputNSAttr()) {
703                        // Bugzilla1133: Generate attribute as well as namespace event.
704                        // SAX does expect both.
705                        this.addAttributeAlways(
706                            "http://www.w3.org/2000/xmlns/",
707                            no_prefix ? "xmlns" : prefix,  // local name
708                            no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname
709                            "CDATA",
710                            ns,
711                            false);
712                    }
713                }
714
715            }
716        }
717    }
718    /**
719     * Adds the given attribute to the set of attributes, and also makes sure
720     * that the needed prefix/uri mapping is declared, but only if there is a
721     * currently open element.
722     *
723     * @param uri the URI of the attribute
724     * @param localName the local name of the attribute
725     * @param rawName    the qualified name of the attribute
726     * @param type the type of the attribute (probably CDATA)
727     * @param value the value of the attribute
728     * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
729     * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
730     */
731    public void addAttribute(
732        String uri,
733        String localName,
734        String rawName,
735        String type,
736        String value,
737        boolean XSLAttribute)
738        throws SAXException
739    {
740        if (m_elemContext.m_startTagOpen)
741        {
742            ensurePrefixIsDeclared(uri, rawName);
743            addAttributeAlways(uri, localName, rawName, type, value, false);
744        }
745
746    }
747
748    /**
749     * Try's to reset the super class and reset this class for
750     * re-use, so that you don't need to create a new serializer
751     * (mostly for performance reasons).
752     *
753     * @return true if the class was successfuly reset.
754     * @see Serializer#reset()
755     */
756    public boolean reset()
757    {
758        boolean wasReset = false;
759        if (super.reset())
760        {
761            resetToXMLSAXHandler();
762            wasReset = true;
763        }
764        return wasReset;
765    }
766
767    /**
768     * Reset all of the fields owned by ToXMLSAXHandler class
769     *
770     */
771    private void resetToXMLSAXHandler()
772    {
773        this.m_escapeSetting = true;
774    }
775
776}
777