/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id: ToXMLSAXHandler.java 468654 2006-10-28 07:09:23Z minchau $ */ package org.apache.xml.serializer; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.util.Properties; import javax.xml.transform.Result; import org.w3c.dom.Node; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; /** * This class receives notification of SAX-like events, and with gathered * information over these calls it will invoke the equivalent SAX methods * on a handler, the ultimate xsl:output method is known to be "xml". * * This class is not a public API. * @xsl.usage internal */ public final class ToXMLSAXHandler extends ToSAXHandler { /** * Keeps track of whether output escaping is currently enabled */ protected boolean m_escapeSetting = true; public ToXMLSAXHandler() { // default constructor (need to set content handler ASAP !) m_prefixMap = new NamespaceMappings(); initCDATA(); } /** * @see Serializer#getOutputFormat() */ public Properties getOutputFormat() { return null; } /** * @see Serializer#getOutputStream() */ public OutputStream getOutputStream() { return null; } /** * @see Serializer#getWriter() */ public Writer getWriter() { return null; } /** * Do nothing for SAX. */ public void indent(int n) throws SAXException { } /** * @see DOMSerializer#serialize(Node) */ public void serialize(Node node) throws IOException { } /** * @see SerializationHandler#setEscaping(boolean) */ public boolean setEscaping(boolean escape) throws SAXException { boolean oldEscapeSetting = m_escapeSetting; m_escapeSetting = escape; if (escape) { processingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, ""); } else { processingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, ""); } return oldEscapeSetting; } /** * @see Serializer#setOutputFormat(Properties) */ public void setOutputFormat(Properties format) { } /** * @see Serializer#setOutputStream(OutputStream) */ public void setOutputStream(OutputStream output) { } /** * @see Serializer#setWriter(Writer) */ public void setWriter(Writer writer) { } /** * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String) */ public void attributeDecl( String arg0, String arg1, String arg2, String arg3, String arg4) throws SAXException { } /** * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String) */ public void elementDecl(String arg0, String arg1) throws SAXException { } /** * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String) */ public void externalEntityDecl(String arg0, String arg1, String arg2) throws SAXException { } /** * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(String, String) */ public void internalEntityDecl(String arg0, String arg1) throws SAXException { } /** * Receives notification of the end of the document. * @see org.xml.sax.ContentHandler#endDocument() */ public void endDocument() throws SAXException { flushPending(); // Close output document m_saxHandler.endDocument(); if (m_tracer != null) super.fireEndDoc(); } /** * This method is called when all the data needed for a call to the * SAX handler's startElement() method has been gathered. */ protected void closeStartTag() throws SAXException { m_elemContext.m_startTagOpen = false; final String localName = getLocalName(m_elemContext.m_elementName); final String uri = getNamespaceURI(m_elemContext.m_elementName, true); // Now is time to send the startElement event if (m_needToCallStartDocument) { startDocumentInternal(); } m_saxHandler.startElement(uri, localName, m_elemContext.m_elementName, m_attributes); // we've sent the official SAX attributes on their way, // now we don't need them anymore. m_attributes.clear(); if(m_state != null) m_state.setCurrentNode(null); } /** * Closes ane open cdata tag, and * unlike the this.endCDATA() method (from the LexicalHandler) interface, * this "internal" method will send the endCDATA() call to the wrapped * handler. * */ public void closeCDATA() throws SAXException { // Output closing bracket - "]]>" if (m_lexHandler != null && m_cdataTagOpen) { m_lexHandler.endCDATA(); } // There are no longer any calls made to // m_lexHandler.startCDATA() without a balancing call to // m_lexHandler.endCDATA() // so we set m_cdataTagOpen to false to remember this. m_cdataTagOpen = false; } /** * @see org.xml.sax.ContentHandler#endElement(String, String, String) */ public void endElement(String namespaceURI, String localName, String qName) throws SAXException { // Close any open elements etc. flushPending(); if (namespaceURI == null) { if (m_elemContext.m_elementURI != null) namespaceURI = m_elemContext.m_elementURI; else namespaceURI = getNamespaceURI(qName, true); } if (localName == null) { if (m_elemContext.m_elementLocalName != null) localName = m_elemContext.m_elementLocalName; else localName = getLocalName(qName); } m_saxHandler.endElement(namespaceURI, localName, qName); if (m_tracer != null) super.fireEndElem(qName); /* Pop all namespaces at the current element depth. * We are not waiting for official endPrefixMapping() calls. */ m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, m_saxHandler); m_elemContext = m_elemContext.m_prev; } /** * @see org.xml.sax.ContentHandler#endPrefixMapping(String) */ public void endPrefixMapping(String prefix) throws SAXException { /* poping all prefix mappings should have been done * in endElement() already */ return; } /** * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int) */ public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException { m_saxHandler.ignorableWhitespace(arg0,arg1,arg2); } /** * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator) */ public void setDocumentLocator(Locator arg0) { m_saxHandler.setDocumentLocator(arg0); } /** * @see org.xml.sax.ContentHandler#skippedEntity(String) */ public void skippedEntity(String arg0) throws SAXException { m_saxHandler.skippedEntity(arg0); } /** * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String) * @param prefix The prefix that maps to the URI * @param uri The URI for the namespace */ public void startPrefixMapping(String prefix, String uri) throws SAXException { startPrefixMapping(prefix, uri, true); } /** * Remember the prefix/uri mapping at the current nested element depth. * * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String) * @param prefix The prefix that maps to the URI * @param uri The URI for the namespace * @param shouldFlush a flag indicating if the mapping applies to the * current element or an up coming child (not used). */ public boolean startPrefixMapping( String prefix, String uri, boolean shouldFlush) throws org.xml.sax.SAXException { /* Remember the mapping, and at what depth it was declared * This is one greater than the current depth because these * mappings will apply to the next depth. This is in * consideration that startElement() will soon be called */ boolean pushed; int pushDepth; if (shouldFlush) { flushPending(); // the prefix mapping applies to the child element (one deeper) pushDepth = m_elemContext.m_currentElemDepth + 1; } else { // the prefix mapping applies to the current element pushDepth = m_elemContext.m_currentElemDepth; } pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth); if (pushed) { m_saxHandler.startPrefixMapping(prefix,uri); if (getShouldOutputNSAttr()) { /* I don't know if we really needto do this. The * callers of this object should have injected both * startPrefixMapping and the attributes. We are * just covering our butt here. */ String name; if (EMPTYSTRING.equals(prefix)) { name = "xmlns"; addAttributeAlways(XMLNS_URI, name, name,"CDATA",uri, false); } else { if (!EMPTYSTRING.equals(uri)) // hack for attribset16 test { // that maps ns1 prefix to "" URI name = "xmlns:" + prefix; /* for something like xmlns:abc="w3.pretend.org" * the uri is the value, that is why we pass it in the * value, or 5th slot of addAttributeAlways() */ addAttributeAlways(XMLNS_URI, prefix, name,"CDATA",uri, false ); } } } } return pushed; } /** * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int) */ public void comment(char[] arg0, int arg1, int arg2) throws SAXException { flushPending(); if (m_lexHandler != null) m_lexHandler.comment(arg0, arg1, arg2); if (m_tracer != null) super.fireCommentEvent(arg0, arg1, arg2); } /** * @see org.xml.sax.ext.LexicalHandler#endCDATA() */ public void endCDATA() throws SAXException { /* Normally we would do somthing with this but we ignore it. * The neccessary call to m_lexHandler.endCDATA() will be made * in flushPending(). * * This is so that if we get calls like these: * this.startCDATA(); * this.characters(chars1, off1, len1); * this.endCDATA(); * this.startCDATA(); * this.characters(chars2, off2, len2); * this.endCDATA(); * * that we will only make these calls to the wrapped handlers: * * m_lexHandler.startCDATA(); * m_saxHandler.characters(chars1, off1, len1); * m_saxHandler.characters(chars1, off2, len2); * m_lexHandler.endCDATA(); * * We will merge adjacent CDATA blocks. */ } /** * @see org.xml.sax.ext.LexicalHandler#endDTD() */ public void endDTD() throws SAXException { if (m_lexHandler != null) m_lexHandler.endDTD(); } /** * @see org.xml.sax.ext.LexicalHandler#startEntity(String) */ public void startEntity(String arg0) throws SAXException { if (m_lexHandler != null) m_lexHandler.startEntity(arg0); } /** * @see ExtendedContentHandler#characters(String) */ public void characters(String chars) throws SAXException { final int length = chars.length(); if (length > m_charsBuff.length) { m_charsBuff = new char[length*2 + 1]; } chars.getChars(0, length, m_charsBuff, 0); this.characters(m_charsBuff, 0, length); } public ToXMLSAXHandler(ContentHandler handler, String encoding) { super(handler, encoding); initCDATA(); // initNamespaces(); m_prefixMap = new NamespaceMappings(); } public ToXMLSAXHandler( ContentHandler handler, LexicalHandler lex, String encoding) { super(handler, lex, encoding); initCDATA(); // initNamespaces(); m_prefixMap = new NamespaceMappings(); } /** * Start an element in the output document. This might be an XML element * (data type) or a CDATA section. */ public void startElement( String elementNamespaceURI, String elementLocalName, String elementName) throws SAXException { startElement( elementNamespaceURI,elementLocalName,elementName, null); } public void startElement(String elementName) throws SAXException { startElement(null, null, elementName, null); } public void characters(char[] ch, int off, int len) throws SAXException { // We do the first two things in flushPending() but we don't // close any open CDATA calls. if (m_needToCallStartDocument) { startDocumentInternal(); m_needToCallStartDocument = false; } if (m_elemContext.m_startTagOpen) { closeStartTag(); m_elemContext.m_startTagOpen = false; } if (m_elemContext.m_isCdataSection && !m_cdataTagOpen && m_lexHandler != null) { m_lexHandler.startCDATA(); // We have made a call to m_lexHandler.startCDATA() with // no balancing call to m_lexHandler.endCDATA() // so we set m_cdataTagOpen true to remember this. m_cdataTagOpen = true; } /* If there are any occurances of "]]>" in the character data * let m_saxHandler worry about it, we've already warned them with * the previous call of m_lexHandler.startCDATA(); */ m_saxHandler.characters(ch, off, len); // time to generate characters event if (m_tracer != null) fireCharEvent(ch, off, len); } /** * @see ExtendedContentHandler#endElement(String) */ public void endElement(String elemName) throws SAXException { endElement(null, null, elemName); } /** * Send a namespace declaration in the output document. The namespace * declaration will not be include if the namespace is already in scope * with the same prefix. */ public void namespaceAfterStartElement( final String prefix, final String uri) throws SAXException { startPrefixMapping(prefix,uri,false); } /** * * @see org.xml.sax.ContentHandler#processingInstruction(String, String) * Send a processing instruction to the output document */ public void processingInstruction(String target, String data) throws SAXException { flushPending(); // Pass the processing instruction to the SAX handler m_saxHandler.processingInstruction(target, data); // we don't want to leave serializer to fire off this event, // so do it here. if (m_tracer != null) super.fireEscapingEvent(target, data); } /** * Undeclare the namespace that is currently pointed to by a given * prefix. Inform SAX handler if prefix was previously mapped. */ protected boolean popNamespace(String prefix) { try { if (m_prefixMap.popNamespace(prefix)) { m_saxHandler.endPrefixMapping(prefix); return true; } } catch (SAXException e) { // falls through } return false; } public void startCDATA() throws SAXException { /* m_cdataTagOpen can only be true here if we have ignored the * previous call to this.endCDATA() and the previous call * this.startCDATA() before that is still "open". In this way * we merge adjacent CDATA. If anything else happened after the * ignored call to this.endCDATA() and this call then a call to * flushPending() would have been made which would have * closed the CDATA and set m_cdataTagOpen to false. */ if (!m_cdataTagOpen ) { flushPending(); if (m_lexHandler != null) { m_lexHandler.startCDATA(); // We have made a call to m_lexHandler.startCDATA() with // no balancing call to m_lexHandler.endCDATA() // so we set m_cdataTagOpen true to remember this. m_cdataTagOpen = true; } } } /** * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) */ public void startElement( String namespaceURI, String localName, String name, Attributes atts) throws SAXException { flushPending(); super.startElement(namespaceURI, localName, name, atts); // Handle document type declaration (for first element only) if (m_needToOutputDocTypeDecl) { String doctypeSystem = getDoctypeSystem(); if (doctypeSystem != null && m_lexHandler != null) { String doctypePublic = getDoctypePublic(); if (doctypeSystem != null) m_lexHandler.startDTD( name, doctypePublic, doctypeSystem); } m_needToOutputDocTypeDecl = false; } m_elemContext = m_elemContext.push(namespaceURI, localName, name); // ensurePrefixIsDeclared depends on the current depth, so // the previous increment is necessary where it is. if (namespaceURI != null) ensurePrefixIsDeclared(namespaceURI, name); // add the attributes to the collected ones if (atts != null) addAttributes(atts); // do we really need this CDATA section state? m_elemContext.m_isCdataSection = isCdataSection(); } private void ensurePrefixIsDeclared(String ns, String rawName) throws org.xml.sax.SAXException { if (ns != null && ns.length() > 0) { int index; final boolean no_prefix = ((index = rawName.indexOf(":")) < 0); String prefix = (no_prefix) ? "" : rawName.substring(0, index); if (null != prefix) { String foundURI = m_prefixMap.lookupNamespace(prefix); if ((null == foundURI) || !foundURI.equals(ns)) { this.startPrefixMapping(prefix, ns, false); if (getShouldOutputNSAttr()) { // Bugzilla1133: Generate attribute as well as namespace event. // SAX does expect both. this.addAttributeAlways( "http://www.w3.org/2000/xmlns/", no_prefix ? "xmlns" : prefix, // local name no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname "CDATA", ns, false); } } } } } /** * Adds the given attribute to the set of attributes, and also makes sure * that the needed prefix/uri mapping is declared, but only if there is a * currently open element. * * @param uri the URI of the attribute * @param localName the local name of the attribute * @param rawName the qualified name of the attribute * @param type the type of the attribute (probably CDATA) * @param value the value of the attribute * @param XSLAttribute true if this attribute is coming from an xsl:attribute element * @see ExtendedContentHandler#addAttribute(String, String, String, String, String) */ public void addAttribute( String uri, String localName, String rawName, String type, String value, boolean XSLAttribute) throws SAXException { if (m_elemContext.m_startTagOpen) { ensurePrefixIsDeclared(uri, rawName); addAttributeAlways(uri, localName, rawName, type, value, false); } } /** * Try's to reset the super class and reset this class for * re-use, so that you don't need to create a new serializer * (mostly for performance reasons). * * @return true if the class was successfuly reset. * @see Serializer#reset() */ public boolean reset() { boolean wasReset = false; if (super.reset()) { resetToXMLSAXHandler(); wasReset = true; } return wasReset; } /** * Reset all of the fields owned by ToXMLSAXHandler class * */ private void resetToXMLSAXHandler() { this.m_escapeSetting = true; } }