| /* |
| * Copyright 2001-2006 The Apache Software Foundation. |
| * |
| * Licensed 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$ |
| */ |
| 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("<?xml version=\""); |
| writer.write(version); |
| writer.write("\" encoding=\""); |
| writer.write(encoding); |
| writer.write('\"'); |
| writer.write(standalone); |
| writer.write("?>"); |
| if (m_doIndent) |
| 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 <tt>xml:space="preserve"</tt> attribute. Only XML |
| * and HTML serializers need to support this method. |
| * <p> |
| * The contents of the whitespace preserving section will be delivered |
| * through the regular <tt>characters</tt> 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('>'); |
| |
| // Always output a newline char if not inside of an |
| // element. The whitespace is not significant in that |
| // case. |
| if (m_elemContext.m_currentElemDepth <= 0) |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| |
| 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 (Exception e){} |
| } |
| } |
| |
| /** |
| * @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; |
| } |
| } |