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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 * $Id: ToXMLSAXHandler.java 468654 2006-10-28 07:09:23Z minchau $
21 package org.apache.xml.serializer;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.io.Writer;
26 import java.util.Properties;
28 import javax.xml.transform.Result;
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;
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".
42 * This class is not a public API.
45 public final class ToXMLSAXHandler extends ToSAXHandler
49 * Keeps track of whether output escaping is currently enabled
51 protected boolean m_escapeSetting = true;
53 public ToXMLSAXHandler()
55 // default constructor (need to set content handler ASAP !)
56 m_prefixMap = new NamespaceMappings();
61 * @see Serializer#getOutputFormat()
63 public Properties getOutputFormat()
69 * @see Serializer#getOutputStream()
71 public OutputStream getOutputStream()
77 * @see Serializer#getWriter()
79 public Writer getWriter()
87 public void indent(int n) throws SAXException
93 * @see DOMSerializer#serialize(Node)
95 public void serialize(Node node) throws IOException
100 * @see SerializationHandler#setEscaping(boolean)
102 public boolean setEscaping(boolean escape) throws SAXException
104 boolean oldEscapeSetting = m_escapeSetting;
105 m_escapeSetting = escape;
108 processingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, "");
110 processingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, "");
113 return oldEscapeSetting;
117 * @see Serializer#setOutputFormat(Properties)
119 public void setOutputFormat(Properties format)
124 * @see Serializer#setOutputStream(OutputStream)
126 public void setOutputStream(OutputStream output)
131 * @see Serializer#setWriter(Writer)
133 public void setWriter(Writer writer)
138 * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
140 public void attributeDecl(
151 * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
153 public void elementDecl(String arg0, String arg1) throws SAXException
158 * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
160 public void externalEntityDecl(String arg0, String arg1, String arg2)
166 * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(String, String)
168 public void internalEntityDecl(String arg0, String arg1)
174 * Receives notification of the end of the document.
175 * @see org.xml.sax.ContentHandler#endDocument()
177 public void endDocument() throws SAXException
182 // Close output document
183 m_saxHandler.endDocument();
185 if (m_tracer != null)
190 * This method is called when all the data needed for a call to the
191 * SAX handler's startElement() method has been gathered.
193 protected void closeStartTag() throws SAXException
196 m_elemContext.m_startTagOpen = false;
198 final String localName = getLocalName(m_elemContext.m_elementName);
199 final String uri = getNamespaceURI(m_elemContext.m_elementName, true);
201 // Now is time to send the startElement event
202 if (m_needToCallStartDocument)
204 startDocumentInternal();
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();
212 m_state.setCurrentNode(null);
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
222 public void closeCDATA() throws SAXException
225 // Output closing bracket - "]]>"
226 if (m_lexHandler != null && m_cdataTagOpen) {
227 m_lexHandler.endCDATA();
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;
239 * @see org.xml.sax.ContentHandler#endElement(String, String, String)
241 public void endElement(String namespaceURI, String localName, String qName)
244 // Close any open elements etc.
247 if (namespaceURI == null)
249 if (m_elemContext.m_elementURI != null)
250 namespaceURI = m_elemContext.m_elementURI;
252 namespaceURI = getNamespaceURI(qName, true);
255 if (localName == null)
257 if (m_elemContext.m_elementLocalName != null)
258 localName = m_elemContext.m_elementLocalName;
260 localName = getLocalName(qName);
263 m_saxHandler.endElement(namespaceURI, localName, qName);
265 if (m_tracer != null)
266 super.fireEndElem(qName);
268 /* Pop all namespaces at the current element depth.
269 * We are not waiting for official endPrefixMapping() calls.
271 m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth,
273 m_elemContext = m_elemContext.m_prev;
277 * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
279 public void endPrefixMapping(String prefix) throws SAXException
281 /* poping all prefix mappings should have been done
282 * in endElement() already
288 * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
290 public void ignorableWhitespace(char[] arg0, int arg1, int arg2)
293 m_saxHandler.ignorableWhitespace(arg0,arg1,arg2);
297 * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
299 public void setDocumentLocator(Locator arg0)
301 m_saxHandler.setDocumentLocator(arg0);
305 * @see org.xml.sax.ContentHandler#skippedEntity(String)
307 public void skippedEntity(String arg0) throws SAXException
309 m_saxHandler.skippedEntity(arg0);
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
317 public void startPrefixMapping(String prefix, String uri)
320 startPrefixMapping(prefix, uri, true);
324 * Remember the prefix/uri mapping at the current nested element depth.
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).
333 public boolean startPrefixMapping(
337 throws org.xml.sax.SAXException
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
351 // the prefix mapping applies to the child element (one deeper)
352 pushDepth = m_elemContext.m_currentElemDepth + 1;
356 // the prefix mapping applies to the current element
357 pushDepth = m_elemContext.m_currentElemDepth;
359 pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
363 m_saxHandler.startPrefixMapping(prefix,uri);
365 if (getShouldOutputNSAttr())
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.
374 if (EMPTYSTRING.equals(prefix))
377 addAttributeAlways(XMLNS_URI, name, name,"CDATA",uri, false);
381 if (!EMPTYSTRING.equals(uri)) // hack for attribset16 test
382 { // that maps ns1 prefix to "" URI
383 name = "xmlns:" + prefix;
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()
389 addAttributeAlways(XMLNS_URI, prefix, name,"CDATA",uri, false );
399 * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
401 public void comment(char[] arg0, int arg1, int arg2) throws SAXException
404 if (m_lexHandler != null)
405 m_lexHandler.comment(arg0, arg1, arg2);
407 if (m_tracer != null)
408 super.fireCommentEvent(arg0, arg1, arg2);
412 * @see org.xml.sax.ext.LexicalHandler#endCDATA()
414 public void endCDATA() throws SAXException
416 /* Normally we would do somthing with this but we ignore it.
417 * The neccessary call to m_lexHandler.endCDATA() will be made
420 * This is so that if we get calls like these:
422 * this.characters(chars1, off1, len1);
425 * this.characters(chars2, off2, len2);
428 * that we will only make these calls to the wrapped handlers:
430 * m_lexHandler.startCDATA();
431 * m_saxHandler.characters(chars1, off1, len1);
432 * m_saxHandler.characters(chars1, off2, len2);
433 * m_lexHandler.endCDATA();
435 * We will merge adjacent CDATA blocks.
440 * @see org.xml.sax.ext.LexicalHandler#endDTD()
442 public void endDTD() throws SAXException
444 if (m_lexHandler != null)
445 m_lexHandler.endDTD();
449 * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
451 public void startEntity(String arg0) throws SAXException
453 if (m_lexHandler != null)
454 m_lexHandler.startEntity(arg0);
458 * @see ExtendedContentHandler#characters(String)
460 public void characters(String chars) throws SAXException
462 final int length = chars.length();
463 if (length > m_charsBuff.length)
465 m_charsBuff = new char[length*2 + 1];
467 chars.getChars(0, length, m_charsBuff, 0);
468 this.characters(m_charsBuff, 0, length);
471 public ToXMLSAXHandler(ContentHandler handler, String encoding)
473 super(handler, encoding);
477 m_prefixMap = new NamespaceMappings();
480 public ToXMLSAXHandler(
481 ContentHandler handler,
485 super(handler, lex, encoding);
489 m_prefixMap = new NamespaceMappings();
493 * Start an element in the output document. This might be an XML element
494 * (<elem>data</elem> type) or a CDATA section.
496 public void startElement(
497 String elementNamespaceURI,
498 String elementLocalName,
499 String elementName) throws SAXException
502 elementNamespaceURI,elementLocalName,elementName, null);
506 public void startElement(String elementName) throws SAXException
508 startElement(null, null, elementName, null);
512 public void characters(char[] ch, int off, int len) throws SAXException
514 // We do the first two things in flushPending() but we don't
515 // close any open CDATA calls.
516 if (m_needToCallStartDocument)
518 startDocumentInternal();
519 m_needToCallStartDocument = false;
522 if (m_elemContext.m_startTagOpen)
525 m_elemContext.m_startTagOpen = false;
528 if (m_elemContext.m_isCdataSection && !m_cdataTagOpen
529 && m_lexHandler != null)
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;
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();
542 m_saxHandler.characters(ch, off, len);
544 // time to generate characters event
545 if (m_tracer != null)
546 fireCharEvent(ch, off, len);
551 * @see ExtendedContentHandler#endElement(String)
553 public void endElement(String elemName) throws SAXException
555 endElement(null, null, elemName);
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.
564 public void namespaceAfterStartElement(
569 startPrefixMapping(prefix,uri,false);
574 * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
575 * Send a processing instruction to the output document
577 public void processingInstruction(String target, String data)
582 // Pass the processing instruction to the SAX handler
583 m_saxHandler.processingInstruction(target, data);
585 // we don't want to leave serializer to fire off this event,
587 if (m_tracer != null)
588 super.fireEscapingEvent(target, data);
592 * Undeclare the namespace that is currently pointed to by a given
593 * prefix. Inform SAX handler if prefix was previously mapped.
595 protected boolean popNamespace(String prefix)
599 if (m_prefixMap.popNamespace(prefix))
601 m_saxHandler.endPrefixMapping(prefix);
605 catch (SAXException e)
612 public void startCDATA() throws SAXException
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.
622 if (!m_cdataTagOpen )
625 if (m_lexHandler != null) {
626 m_lexHandler.startCDATA();
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;
637 * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
639 public void startElement(
647 super.startElement(namespaceURI, localName, name, atts);
649 // Handle document type declaration (for first element only)
650 if (m_needToOutputDocTypeDecl)
652 String doctypeSystem = getDoctypeSystem();
653 if (doctypeSystem != null && m_lexHandler != null)
655 String doctypePublic = getDoctypePublic();
656 if (doctypeSystem != null)
657 m_lexHandler.startDTD(
662 m_needToOutputDocTypeDecl = false;
664 m_elemContext = m_elemContext.push(namespaceURI, localName, name);
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);
671 // add the attributes to the collected ones
676 // do we really need this CDATA section state?
677 m_elemContext.m_isCdataSection = isCdataSection();
681 private void ensurePrefixIsDeclared(String ns, String rawName)
682 throws org.xml.sax.SAXException
685 if (ns != null && ns.length() > 0)
688 final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
689 String prefix = (no_prefix) ? "" : rawName.substring(0, index);
694 String foundURI = m_prefixMap.lookupNamespace(prefix);
696 if ((null == foundURI) || !foundURI.equals(ns))
698 this.startPrefixMapping(prefix, ns, false);
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
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.
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)
729 public void addAttribute(
735 boolean XSLAttribute)
738 if (m_elemContext.m_startTagOpen)
740 ensurePrefixIsDeclared(uri, rawName);
741 addAttributeAlways(uri, localName, rawName, type, value, false);
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).
751 * @return true if the class was successfuly reset.
752 * @see Serializer#reset()
754 public boolean reset()
756 boolean wasReset = false;
759 resetToXMLSAXHandler();
766 * Reset all of the fields owned by ToXMLSAXHandler class
769 private void resetToXMLSAXHandler()
771 this.m_escapeSetting = true;