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: ToHTMLSAXHandler.java 475978 2006-11-16 23:31:20Z minchau $
020     */
021    
022    package org.apache.xml.serializer;
023    
024    import java.io.IOException;
025    import java.io.OutputStream;
026    import java.io.Writer;
027    import java.util.Properties;
028    
029    import javax.xml.transform.Result;
030    
031    import org.w3c.dom.Node;
032    import org.xml.sax.Attributes;
033    import org.xml.sax.ContentHandler;
034    import org.xml.sax.Locator;
035    import org.xml.sax.SAXException;
036    import org.xml.sax.ext.LexicalHandler;
037    
038    /**
039     * This class accepts SAX-like calls, then sends true SAX calls to a
040     * wrapped SAX handler.  There is optimization done knowing that the ultimate
041     * output is HTML.
042     * 
043     * This class is not a public API.
044     * 
045     * @deprecated As of Xalan 2.7.1, replaced by the use of {@link ToXMLSAXHandler}.
046     * 
047     * @xsl.usage internal
048     */
049    public final class ToHTMLSAXHandler extends ToSAXHandler
050    {
051            /**
052             *  Handle document type declaration (for first element only)
053             */
054            private boolean m_dtdHandled = false;
055            
056        /**
057         * Keeps track of whether output escaping is currently enabled
058         */
059        protected boolean m_escapeSetting = true;
060    
061        /**
062         * Returns null.
063         * @return null
064         * @see Serializer#getOutputFormat()
065         */
066        public Properties getOutputFormat()
067        {
068            return null;
069        }
070    
071        /**
072         * Reurns null
073         * @return null
074         * @see Serializer#getOutputStream()
075         */
076        public OutputStream getOutputStream()
077        {
078            return null;
079        }
080    
081        /**
082         * Returns null
083         * @return null
084         * @see Serializer#getWriter()
085         */
086        public Writer getWriter()
087        {
088            return null;
089        }
090    
091        /**
092         * Does nothing.
093         *
094         */
095        public void indent(int n) throws SAXException
096        {
097        }
098    
099        /**
100         * Does nothing.
101         * @see DOMSerializer#serialize(Node)
102         */
103        public void serialize(Node node) throws IOException
104        {
105            return;
106        }
107    
108        /**
109         * Turns special character escaping on/off.
110         *
111         *
112         * @param escape true if escaping is to be set on.
113         *
114         * @see SerializationHandler#setEscaping(boolean)
115         */
116        public boolean setEscaping(boolean escape) throws SAXException
117        {
118            boolean oldEscapeSetting = m_escapeSetting;
119            m_escapeSetting = escape;
120    
121            if (escape) {
122                processingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, "");
123            } else {
124                processingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, "");
125            }
126    
127            return oldEscapeSetting;
128        }
129    
130        /**
131         * Does nothing
132         * @param indent the number of spaces to indent per indentation level
133         * (ignored)
134         * @see SerializationHandler#setIndent(boolean)
135         */
136        public void setIndent(boolean indent)
137        {
138        }
139    
140        /**
141         * Does nothing.
142         * @param format this parameter is not used
143         * @see Serializer#setOutputFormat(Properties)
144         */
145        public void setOutputFormat(Properties format)
146        {
147        }
148    
149        /**
150         * Does nothing.
151         * @param output this parameter is ignored
152         * @see Serializer#setOutputStream(OutputStream)
153         */
154        public void setOutputStream(OutputStream output)
155        {
156        }
157    
158    
159        /**
160         * Does nothing.
161         * @param writer this parameter is ignored.
162         * @see Serializer#setWriter(Writer)
163         */
164        public void setWriter(Writer writer)
165        {
166        }
167    
168        /**
169         * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
170         */
171        /**
172         * Does nothing.
173         *
174         * @param eName this parameter is ignored
175         * @param aName this parameter is ignored
176         * @param type this parameter is ignored
177         * @param valueDefault this parameter is ignored
178         * @param value this parameter is ignored
179         * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String,String,String)
180         */    
181        public void attributeDecl(
182            String eName,
183            String aName,
184            String type,
185            String valueDefault,
186            String value)
187            throws SAXException
188        {
189        }
190    
191    
192        /**
193         * Does nothing.
194         * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
195         */    
196        public void elementDecl(String name, String model) throws SAXException
197        {
198            return;
199        }
200    
201        /**
202         * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
203         */
204        public void externalEntityDecl(String arg0, String arg1, String arg2)
205            throws SAXException
206        {
207        }
208    
209        /**
210         * Does nothing.
211         *
212         * @see org.xml.sax.DTDHandler#unparsedEntityDecl
213         */
214        public void internalEntityDecl(String name, String value)
215            throws SAXException
216        {
217        }
218    
219        /**
220         * Receive notification of the end of an element.
221         *
222         * <p>The SAX parser will invoke this method at the end of every
223         * element in the XML document; there will be a corresponding
224         * startElement() event for every endElement() event (even when the
225         * element is empty).</p>
226         *
227         * <p>If the element name has a namespace prefix, the prefix will
228         * still be attached to the name.</p>
229         *
230         *
231         * @param uri The Namespace URI, or the empty string if the
232         *        element has no Namespace URI or if Namespace
233         *        processing is not being performed.
234         * @param localName The local name (without prefix), or the
235         *        empty string if Namespace processing is not being
236         *        performed.
237         * @param qName The qualified name (with prefix), or the
238         *        empty string if qualified names are not available.
239         * @throws org.xml.sax.SAXException Any SAX exception, possibly
240         *            wrapping another exception.
241         * @see org.xml.sax.ContentHandler#endElement(String, String, String)
242         */
243        public void endElement(String uri, String localName, String qName)
244            throws SAXException
245        {
246            flushPending();
247            m_saxHandler.endElement(uri, localName, qName);
248            
249            // time to fire off endElement event
250            if (m_tracer != null)
251                super.fireEndElem(qName);
252        }
253    
254        /**
255         * Does nothing.
256         */
257        public void endPrefixMapping(String prefix) throws SAXException
258        {
259        }
260    
261        /**
262         * Does nothing.
263         * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
264         */
265        public void ignorableWhitespace(char[] ch, int start, int length)
266            throws SAXException
267        {
268        }
269        
270        /**
271         * Receive notification of a processing instruction.
272         *
273         * <p>The Parser will invoke this method once for each processing
274         * instruction found: note that processing instructions may occur
275         * before or after the main document element.</p>
276         *
277         * <p>A SAX parser should never report an XML declaration (XML 1.0,
278         * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
279         * using this method.</p>
280         *
281         * @param target The processing instruction target.
282         * @param data The processing instruction data, or null if
283         *        none was supplied.
284         * @throws org.xml.sax.SAXException Any SAX exception, possibly
285         *            wrapping another exception.
286         *
287         * @throws org.xml.sax.SAXException
288         * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
289         */
290        public void processingInstruction(String target, String data)
291            throws SAXException
292        {
293            flushPending();
294            m_saxHandler.processingInstruction(target,data);
295    
296                    // time to fire off processing instruction event
297                    
298            if (m_tracer != null)           
299                        super.fireEscapingEvent(target,data);        
300        }
301    
302        /**
303         * Does nothing.  
304         * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
305         */
306        public void setDocumentLocator(Locator arg0)
307        {
308            // do nothing
309        }
310    
311        /**
312         * Does nothing.
313         * @see org.xml.sax.ContentHandler#skippedEntity(String)
314         */
315        public void skippedEntity(String arg0) throws SAXException
316        {
317        }
318    
319        /**
320         * Receive notification of the beginning of an element, although this is a
321         * SAX method additional namespace or attribute information can occur before
322         * or after this call, that is associated with this element.
323         *
324         *
325         * @param namespaceURI The Namespace URI, or the empty string if the
326         *        element has no Namespace URI or if Namespace
327         *        processing is not being performed.
328         * @param localName The local name (without prefix), or the
329         *        empty string if Namespace processing is not being
330         *        performed.
331         * @param qName The elements name.
332         * @param atts The attributes attached to the element, if any.
333         * @throws org.xml.sax.SAXException Any SAX exception, possibly
334         *            wrapping another exception.
335         * @see org.xml.sax.ContentHandler#startElement
336         * @see org.xml.sax.ContentHandler#endElement
337         * @see org.xml.sax.AttributeList
338         *
339         * @throws org.xml.sax.SAXException
340         *
341         * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
342         */
343        public void startElement(
344            String namespaceURI,
345            String localName,
346            String qName,
347            Attributes atts)
348            throws SAXException
349        {
350            flushPending();
351            super.startElement(namespaceURI, localName, qName, atts);
352            m_saxHandler.startElement(namespaceURI, localName, qName, atts);
353            m_elemContext.m_startTagOpen = false;
354        }
355    
356        /**
357         * Receive notification of a comment anywhere in the document. This callback
358         * will be used for comments inside or outside the document element.
359         * @param ch An array holding the characters in the comment.
360         * @param start The starting position in the array.
361         * @param length The number of characters to use from the array.
362         * @throws org.xml.sax.SAXException The application may raise an exception.
363         *
364         * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
365         */
366        public void comment(char[] ch, int start, int length) throws SAXException
367        {
368            flushPending();
369            if (m_lexHandler != null)
370                m_lexHandler.comment(ch, start, length);
371    
372            // time to fire off comment event
373            if (m_tracer != null)
374                super.fireCommentEvent(ch, start, length);
375            return;
376        }
377    
378        /**
379         * Does nothing.
380         * @see org.xml.sax.ext.LexicalHandler#endCDATA()
381         */
382        public void endCDATA() throws SAXException
383        {
384            return;
385        }
386    
387        /**
388         * Does nothing.
389         * @see org.xml.sax.ext.LexicalHandler#endDTD()
390         */
391        public void endDTD() throws SAXException
392        {
393        }
394    
395        /**
396         * Does nothing.
397         * @see org.xml.sax.ext.LexicalHandler#startCDATA()
398         */
399        public void startCDATA() throws SAXException
400        {
401        }
402    
403        /**
404         * Does nothing.
405         * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
406         */
407        public void startEntity(String arg0) throws SAXException
408        {
409        }
410    
411        /**
412         * Receive notification of the end of a document.
413         *
414         * <p>The SAX parser will invoke this method only once, and it will
415         * be the last method invoked during the parse.  The parser shall
416         * not invoke this method until it has either abandoned parsing
417         * (because of an unrecoverable error) or reached the end of
418         * input.</p>
419         *
420         * @throws org.xml.sax.SAXException Any SAX exception, possibly
421         *            wrapping another exception.
422         *
423         * @throws org.xml.sax.SAXException
424         *
425         *
426         */
427        public void endDocument() throws SAXException
428        {
429            flushPending();
430    
431            // Close output document
432            m_saxHandler.endDocument();
433    
434            if (m_tracer != null)
435                super.fireEndDoc();        
436        }
437    
438        /**
439         * This method is called when all the data needed for a call to the
440         * SAX handler's startElement() method has been gathered.
441         */
442        protected void closeStartTag() throws SAXException
443        {
444    
445            m_elemContext.m_startTagOpen = false;
446    
447            // Now is time to send the startElement event
448            m_saxHandler.startElement(
449                EMPTYSTRING,
450                m_elemContext.m_elementName,
451                m_elemContext.m_elementName,
452                m_attributes);
453            m_attributes.clear();       
454    
455        }
456    
457        /**
458         * Do nothing.
459         * @see SerializationHandler#close()
460         */
461        public void close()
462        {
463            return;
464        }
465    
466        /**
467         * Receive notification of character data.
468         *
469         * @param chars The string of characters to process.
470         *
471         * @throws org.xml.sax.SAXException
472         *
473         * @see ExtendedContentHandler#characters(String)
474         */
475        public void characters(final String chars) throws SAXException
476        {
477            final int length = chars.length();
478            if (length > m_charsBuff.length)
479            {
480                m_charsBuff = new char[length * 2 + 1];
481            }
482            chars.getChars(0, length, m_charsBuff, 0);        
483            this.characters(m_charsBuff, 0, length);
484        }
485    
486    
487        /**
488         * A constructor
489         * @param handler the wrapped SAX content handler
490         * @param encoding the encoding of the output HTML document
491         */
492        public ToHTMLSAXHandler(ContentHandler handler, String encoding)
493        {
494            super(handler,encoding);
495        }
496        /**
497         * A constructor.
498         * @param handler the wrapped SAX content handler
499         * @param lex the wrapped lexical handler
500         * @param encoding the encoding of the output HTML document
501         */
502        public ToHTMLSAXHandler(
503            ContentHandler handler,
504            LexicalHandler lex,
505            String encoding)
506        {
507            super(handler,lex,encoding);
508        }
509    
510        /**
511         * An element starts, but attributes are not fully known yet.
512         *
513         * @param elementNamespaceURI the URI of the namespace of the element
514         * (optional)
515         * @param elementLocalName the element name, but without prefix
516         * (optional)
517         * @param elementName the element name, with prefix, if any (required)
518         *
519         * @see ExtendedContentHandler#startElement(String)
520         */
521        public void startElement(
522            String elementNamespaceURI,
523            String elementLocalName,
524            String elementName) throws SAXException
525        {
526    
527            super.startElement(elementNamespaceURI, elementLocalName, elementName);
528    
529            flushPending();
530    
531            // Handle document type declaration (for first element only)
532            if (!m_dtdHandled)
533            {
534                String doctypeSystem = getDoctypeSystem();
535                String doctypePublic = getDoctypePublic();
536                if ((doctypeSystem != null) || (doctypePublic != null)) {
537                    if (m_lexHandler != null)
538                        m_lexHandler.startDTD(
539                            elementName,
540                            doctypePublic,
541                            doctypeSystem);
542                }
543                            m_dtdHandled = true;
544            }
545            m_elemContext = m_elemContext.push(elementNamespaceURI, elementLocalName, elementName);
546        }
547        /**
548         * An element starts, but attributes are not fully known yet.
549         *
550         * @param elementName the element name, with prefix, if any
551         *
552         * @see ExtendedContentHandler#startElement(String)
553         */
554        public void startElement(String elementName) throws SAXException
555        {
556            this.startElement(null,null, elementName);
557        }
558        
559        /**
560         * Receive notification of the end of an element.
561         * @param elementName The element type name
562         * @throws org.xml.sax.SAXException Any SAX exception, possibly
563         *     wrapping another exception.
564         *
565         * @see ExtendedContentHandler#endElement(String)
566         */
567        public void endElement(String elementName) throws SAXException
568        {
569            flushPending();
570            m_saxHandler.endElement(EMPTYSTRING, elementName, elementName);
571    
572            // time to fire off endElement event
573                    if (m_tracer != null)
574                super.fireEndElem(elementName);        
575        }
576    
577        /**
578         * Receive notification of character data.
579         *
580         * <p>The Parser will call this method to report each chunk of
581         * character data.  SAX parsers may return all contiguous character
582         * data in a single chunk, or they may split it into several
583         * chunks; however, all of the characters in any single event
584         * must come from the same external entity, so that the Locator
585         * provides useful information.</p>
586         *
587         * <p>The application must not attempt to read from the array
588         * outside of the specified range.</p>
589         *
590         * <p>Note that some parsers will report whitespace using the
591         * ignorableWhitespace() method rather than this one (validating
592         * parsers must do so).</p>
593         *
594         * @param ch The characters from the XML document.
595         * @param off The start position in the array.
596         * @param len The number of characters to read from the array.
597         * @throws org.xml.sax.SAXException Any SAX exception, possibly
598         *            wrapping another exception.
599         * @see #ignorableWhitespace
600         * @see org.xml.sax.Locator
601         *
602         * @throws org.xml.sax.SAXException
603         *
604         * @see org.xml.sax.ContentHandler#characters(char[], int, int)
605         */
606        public void characters(char[] ch, int off, int len) throws SAXException
607        {
608    
609            flushPending();
610            m_saxHandler.characters(ch, off, len);
611    
612            // time to fire off characters event
613                    if (m_tracer != null)
614                super.fireCharEvent(ch, off, len);        
615        }
616    
617        /**
618         * This method flushes any pending events, which can be startDocument()
619         * closing the opening tag of an element, or closing an open CDATA section.
620         */
621        public void flushPending() throws SAXException
622        {
623                    if (m_needToCallStartDocument)
624                    {
625                            startDocumentInternal();
626                            m_needToCallStartDocument = false;
627                    }               
628            // Close any open element
629            if (m_elemContext.m_startTagOpen)
630            {
631                closeStartTag();
632                m_elemContext.m_startTagOpen = false;
633            }
634        }
635        /**
636         * Handle a prefix/uri mapping, which is associated with a startElement()
637         * that is soon to follow. Need to close any open start tag to make
638         * sure than any name space attributes due to this event are associated wih
639         * the up comming element, not the current one.
640         * @see ExtendedContentHandler#startPrefixMapping
641         *
642         * @param prefix The Namespace prefix being declared.
643         * @param uri The Namespace URI the prefix is mapped to.
644         * @param shouldFlush true if any open tags need to be closed first, this
645         * will impact which element the mapping applies to (open parent, or its up
646         * comming child)
647         * @return returns true if the call made a change to the current
648         * namespace information, false if it did not change anything, e.g. if the
649         * prefix/namespace mapping was already in scope from before.
650         *
651         * @throws org.xml.sax.SAXException The client may throw
652         *            an exception during processing.
653         */    
654        public boolean startPrefixMapping(
655            String prefix,
656            String uri,
657            boolean shouldFlush)
658            throws SAXException
659        {
660            // no namespace support for HTML
661            if (shouldFlush) 
662                flushPending();   
663            m_saxHandler.startPrefixMapping(prefix,uri);
664            return false;
665        }
666    
667        /**
668         * Begin the scope of a prefix-URI Namespace mapping
669         * just before another element is about to start.
670         * This call will close any open tags so that the prefix mapping
671         * will not apply to the current element, but the up comming child.
672         *
673         * @see org.xml.sax.ContentHandler#startPrefixMapping
674         *
675         * @param prefix The Namespace prefix being declared.
676         * @param uri The Namespace URI the prefix is mapped to.
677         *
678         * @throws org.xml.sax.SAXException The client may throw
679         *            an exception during processing.
680         *
681         */
682        public void startPrefixMapping(String prefix, String uri)
683            throws org.xml.sax.SAXException
684        {
685            startPrefixMapping(prefix,uri,true);        
686        }
687    
688        /**
689         * This method is used when a prefix/uri namespace mapping
690         * is indicated after the element was started with a
691         * startElement() and before and endElement().
692         * startPrefixMapping(prefix,uri) would be used before the
693         * startElement() call.
694         * @param prefix the prefix associated with the given URI.
695         * @param uri the URI of the namespace
696         *
697         * @see ExtendedContentHandler#namespaceAfterStartElement(String, String)
698         */
699        public void namespaceAfterStartElement(
700            final String prefix,
701            final String uri)
702            throws SAXException
703        {
704            // hack for XSLTC with finding URI for default namespace
705            if (m_elemContext.m_elementURI == null)
706            {
707                String prefix1 = getPrefixPart(m_elemContext.m_elementName);
708                if (prefix1 == null && EMPTYSTRING.equals(prefix))
709                {
710                    // the elements URI is not known yet, and it
711                    // doesn't have a prefix, and we are currently
712                    // setting the uri for prefix "", so we have
713                    // the uri for the element... lets remember it
714                    m_elemContext.m_elementURI = uri;
715                }
716            }       
717            startPrefixMapping(prefix,uri,false);
718        }
719        
720        /**
721         * Try's to reset the super class and reset this class for 
722         * re-use, so that you don't need to create a new serializer 
723         * (mostly for performance reasons).
724         * 
725         * @return true if the class was successfuly reset.
726         * @see Serializer#reset()
727         */
728        public boolean reset()
729        {
730            boolean wasReset = false;
731            if (super.reset())
732            {
733                resetToHTMLSAXHandler();
734                wasReset = true;
735            }
736            return wasReset;
737        }
738        
739        /**
740         * Reset all of the fields owned by ToHTMLSAXHandler class
741         *
742         */
743        private void resetToHTMLSAXHandler()
744        {
745            this.m_dtdHandled = false;
746            this.m_escapeSetting = true;
747        }  
748    }