/* * 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: ToXMLStream.java 469359 2006-10-31 03:43:19Z minchau $ */ package org.apache.xml.serializer; import java.io.IOException; import javax.xml.transform.ErrorListener; import javax.xml.transform.Result; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import org.apache.xml.serializer.utils.MsgKey; import org.apache.xml.serializer.utils.Utils; import org.xml.sax.SAXException; /** * This class converts SAX or SAX-like calls to a * serialized xml document. The xsl:output method is "xml". * * This class is used explicitly in code generated by XSLTC, * so it is "public", but it should * be viewed as internal or package private, this is not an API. * * @xsl.usage internal */ public class ToXMLStream extends ToStream { /** * Map that tells which XML characters should have special treatment, and it * provides character to entity name lookup. */ private CharInfo m_xmlcharInfo = CharInfo.getCharInfo(CharInfo.XML_ENTITIES_RESOURCE, Method.XML); /** * Default constructor. */ public ToXMLStream() { m_charInfo = m_xmlcharInfo; initCDATA(); // initialize namespaces m_prefixMap = new NamespaceMappings(); } /** * Copy properties from another SerializerToXML. * * @param xmlListener non-null reference to a SerializerToXML object. */ public void CopyFrom(ToXMLStream xmlListener) { setWriter(xmlListener.m_writer); // m_outputStream = xmlListener.m_outputStream; String encoding = xmlListener.getEncoding(); setEncoding(encoding); setOmitXMLDeclaration(xmlListener.getOmitXMLDeclaration()); m_ispreserve = xmlListener.m_ispreserve; m_preserves = xmlListener.m_preserves; m_isprevtext = xmlListener.m_isprevtext; m_doIndent = xmlListener.m_doIndent; setIndentAmount(xmlListener.getIndentAmount()); m_startNewLine = xmlListener.m_startNewLine; m_needToOutputDocTypeDecl = xmlListener.m_needToOutputDocTypeDecl; setDoctypeSystem(xmlListener.getDoctypeSystem()); setDoctypePublic(xmlListener.getDoctypePublic()); setStandalone(xmlListener.getStandalone()); setMediaType(xmlListener.getMediaType()); m_encodingInfo = xmlListener.m_encodingInfo; m_spaceBeforeClose = xmlListener.m_spaceBeforeClose; m_cdataStartCalled = xmlListener.m_cdataStartCalled; } /** * Receive notification of the beginning of a document. * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * * @throws org.xml.sax.SAXException */ public void startDocumentInternal() throws org.xml.sax.SAXException { if (m_needToCallStartDocument) { super.startDocumentInternal(); m_needToCallStartDocument = false; if (m_inEntityRef) return; m_needToOutputDocTypeDecl = true; m_startNewLine = false; /* The call to getXMLVersion() might emit an error message * and we should emit this message regardless of if we are * writing out an XML header or not. */ final String version = getXMLVersion(); if (getOmitXMLDeclaration() == false) { String encoding = Encodings.getMimeEncoding(getEncoding()); String standalone; if (m_standaloneWasSpecified) { standalone = " standalone=\"" + getStandalone() + "\""; } else { standalone = ""; } try { final java.io.Writer writer = m_writer; writer.write(""); if (m_doIndent) { if (m_standaloneWasSpecified || getDoctypePublic() != null || getDoctypeSystem() != null) { // We almost never put a newline after the XML // header because this XML could be used as // an extenal general parsed entity // and we don't know the context into which it // will be used in the future. Only when // standalone, or a doctype system or public is // specified are we free to insert a new line // after the header. Is it even worth bothering // in these rare cases? writer.write(m_lineSep, 0, m_lineSepLen); } } } catch(IOException e) { throw new SAXException(e); } } } } /** * Receive notification of the end of a document. * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * * @throws org.xml.sax.SAXException */ public void endDocument() throws org.xml.sax.SAXException { flushPending(); if (m_doIndent && !m_isprevtext) { try { outputLineSep(); } catch(IOException e) { throw new SAXException(e); } } flushWriter(); if (m_tracer != null) super.fireEndDoc(); } /** * Starts a whitespace preserving section. All characters printed * within a preserving section are printed without indentation and * without consolidating multiple spaces. This is equivalent to * the xml:space="preserve" attribute. Only XML * and HTML serializers need to support this method. *
* The contents of the whitespace preserving section will be delivered * through the regular characters event. * * @throws org.xml.sax.SAXException */ public void startPreserving() throws org.xml.sax.SAXException { // Not sure this is really what we want. -sb m_preserves.push(true); m_ispreserve = true; } /** * Ends a whitespace preserving section. * * @see #startPreserving * * @throws org.xml.sax.SAXException */ public void endPreserving() throws org.xml.sax.SAXException { // Not sure this is really what we want. -sb m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop(); } /** * Receive notification of a processing instruction. * * @param target The processing instruction target. * @param data The processing instruction data, or null if * none was supplied. * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * * @throws org.xml.sax.SAXException */ public void processingInstruction(String target, String data) throws org.xml.sax.SAXException { if (m_inEntityRef) return; flushPending(); if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING)) { startNonEscaping(); } else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING)) { endNonEscaping(); } else { try { if (m_elemContext.m_startTagOpen) { closeStartTag(); m_elemContext.m_startTagOpen = false; } else if (m_needToCallStartDocument) startDocumentInternal(); if (shouldIndent()) indent(); final java.io.Writer writer = m_writer; writer.write(""); writer.write(target); if (data.length() > 0 && !Character.isSpaceChar(data.charAt(0))) writer.write(' '); int indexOfQLT = data.indexOf("?>"); if (indexOfQLT >= 0) { // See XSLT spec on error recovery of "?>" in PIs. if (indexOfQLT > 0) { writer.write(data.substring(0, indexOfQLT)); } writer.write("? >"); // add space between. if ((indexOfQLT + 2) < data.length()) { writer.write(data.substring(indexOfQLT + 2)); } } else { writer.write(data); } writer.write('?'); writer.write('>'); /* * Don't write out any indentation whitespace now, * because there may be non-whitespace text after this. * * Simply mark that at this point if we do decide * to indent that we should * add a newline on the end of the current line before * the indentation at the start of the next line. */ m_startNewLine = true; } catch(IOException e) { throw new SAXException(e); } } if (m_tracer != null) super.fireEscapingEvent(target, data); } /** * Receive notivication of a entityReference. * * @param name The name of the entity. * * @throws org.xml.sax.SAXException */ public void entityReference(String name) throws org.xml.sax.SAXException { if (m_elemContext.m_startTagOpen) { closeStartTag(); m_elemContext.m_startTagOpen = false; } try { if (shouldIndent()) indent(); final java.io.Writer writer = m_writer; writer.write('&'); writer.write(name); writer.write(';'); } catch(IOException e) { throw new SAXException(e); } if (m_tracer != null) super.fireEntityReference(name); } /** * This method is used to add an attribute to the currently open element. * The caller has guaranted that this attribute is unique, which means that it * not been seen before and will not be seen again. * * @param name the qualified name of the attribute * @param value the value of the attribute which can contain only * ASCII printable characters characters in the range 32 to 127 inclusive. * @param flags the bit values of this integer give optimization information. */ public void addUniqueAttribute(String name, String value, int flags) throws SAXException { if (m_elemContext.m_startTagOpen) { try { final String patchedName = patchName(name); final java.io.Writer writer = m_writer; if ((flags & NO_BAD_CHARS) > 0 && m_xmlcharInfo.onlyQuotAmpLtGt) { // "flags" has indicated that the characters // '>' '<' '&' and '"' are not in the value and // m_htmlcharInfo has recorded that there are no other // entities in the range 32 to 127 so we write out the // value directly writer.write(' '); writer.write(patchedName); writer.write("=\""); writer.write(value); writer.write('"'); } else { writer.write(' '); writer.write(patchedName); writer.write("=\""); writeAttrString(writer, value, this.getEncoding()); writer.write('"'); } } catch (IOException e) { throw new SAXException(e); } } } /** * Add an attribute to the current element. * @param uri the URI associated with the element name * @param localName local part of the attribute name * @param rawName prefix:localName * @param type * @param value the value of the attribute * @param xslAttribute true if this attribute is from an xsl:attribute, * false if declared within the elements opening tag. * @throws SAXException */ public void addAttribute( String uri, String localName, String rawName, String type, String value, boolean xslAttribute) throws SAXException { if (m_elemContext.m_startTagOpen) { boolean was_added = addAttributeAlways(uri, localName, rawName, type, value, xslAttribute); /* * We don't run this block of code if: * 1. The attribute value was only replaced (was_added is false). * 2. The attribute is from an xsl:attribute element (that is handled * in the addAttributeAlways() call just above. * 3. The name starts with "xmlns", i.e. it is a namespace declaration. */ if (was_added && !xslAttribute && !rawName.startsWith("xmlns")) { String prefixUsed = ensureAttributesNamespaceIsDeclared( uri, localName, rawName); if (prefixUsed != null && rawName != null && !rawName.startsWith(prefixUsed)) { // use a different raw name, with the prefix used in the // generated namespace declaration rawName = prefixUsed + ":" + localName; } } addAttributeAlways(uri, localName, rawName, type, value, xslAttribute); } else { /* * The startTag is closed, yet we are adding an attribute? * * Section: 7.1.3 Creating Attributes Adding an attribute to an * element after a PI (for example) has been added to it is an * error. The attributes can be ignored. The spec doesn't explicitly * say this is disallowed, as it does for child elements, but it * makes sense to have the same treatment. * * We choose to ignore the attribute which is added too late. */ // Generate a warning of the ignored attributes // Create the warning message String msg = Utils.messages.createMessage( MsgKey.ER_ILLEGAL_ATTRIBUTE_POSITION,new Object[]{ localName }); try { // Prepare to issue the warning message Transformer tran = super.getTransformer(); ErrorListener errHandler = tran.getErrorListener(); // Issue the warning message if (null != errHandler && m_sourceLocator != null) errHandler.warning(new TransformerException(msg, m_sourceLocator)); else System.out.println(msg); } catch (TransformerException e){ // A user defined error handler, errHandler, may throw // a TransformerException if it chooses to, and if it does // we will wrap it with a SAXException and re-throw. // Of course if the handler throws another type of // exception, like a RuntimeException, then that is OK too. SAXException se = new SAXException(e); throw se; } } } /** * @see ExtendedContentHandler#endElement(String) */ public void endElement(String elemName) throws SAXException { endElement(null, null, elemName); } /** * This method is used to notify the serializer of a namespace mapping (or node) * that applies to the current element whose startElement() call has already been seen. * The official SAX startPrefixMapping(prefix,uri) is to define a mapping for a child * element that is soon to be seen with a startElement() call. The official SAX call * does not apply to the current element, hence the reason for this method. */ public void namespaceAfterStartElement( final String prefix, final String uri) throws SAXException { // hack for XSLTC with finding URI for default namespace if (m_elemContext.m_elementURI == null) { String prefix1 = getPrefixPart(m_elemContext.m_elementName); if (prefix1 == null && EMPTYSTRING.equals(prefix)) { // the elements URI is not known yet, and it // doesn't have a prefix, and we are currently // setting the uri for prefix "", so we have // the uri for the element... lets remember it m_elemContext.m_elementURI = uri; } } startPrefixMapping(prefix,uri,false); return; } /** * From XSLTC * Declare a prefix to point to a namespace URI. Inform SAX handler * if this is a new prefix mapping. */ protected boolean pushNamespace(String prefix, String uri) { try { if (m_prefixMap.pushNamespace( prefix, uri, m_elemContext.m_currentElemDepth)) { startPrefixMapping(prefix, uri); return true; } } catch (SAXException e) { // falls through } return 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. */ public boolean reset() { boolean wasReset = false; if (super.reset()) { // Make this call when resetToXMLStream does // something. // resetToXMLStream(); wasReset = true; } return wasReset; } /** * Reset all of the fields owned by ToStream class * */ private void resetToXMLStream() { // This is an empty method, but is kept for future use // as a place holder for a location to reset fields // defined within this class return; } /** * This method checks for the XML version of output document. * If XML version of output document is not specified, then output * document is of version XML 1.0. * If XML version of output doucment is specified, but it is not either * XML 1.0 or XML 1.1, a warning message is generated, the XML Version of * output document is set to XML 1.0 and processing continues. * @return string (XML version) */ private String getXMLVersion() { String xmlVersion = getVersion(); if(xmlVersion == null || xmlVersion.equals(XMLVERSION10)) { xmlVersion = XMLVERSION10; } else if(xmlVersion.equals(XMLVERSION11)) { xmlVersion = XMLVERSION11; } else { String msg = Utils.messages.createMessage( MsgKey.ER_XML_VERSION_NOT_SUPPORTED,new Object[]{ xmlVersion }); try { // Prepare to issue the warning message Transformer tran = super.getTransformer(); ErrorListener errHandler = tran.getErrorListener(); // Issue the warning message if (null != errHandler && m_sourceLocator != null) errHandler.warning(new TransformerException(msg, m_sourceLocator)); else System.out.println(msg); } catch (Exception e){} xmlVersion = XMLVERSION10; } return xmlVersion; } }