| /* |
| * reserved comment block |
| * DO NOT REMOVE OR ALTER! |
| */ |
| /* |
| * Copyright 2001-2004 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: ToStream.java,v 1.4 2005/11/10 06:43:26 suresh_emailid Exp $ |
| */ |
| package com.sun.org.apache.xml.internal.serializer; |
| |
| import com.sun.org.apache.xalan.internal.utils.SecuritySupport; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.io.Writer; |
| import java.util.Properties; |
| import java.util.StringTokenizer; |
| import java.util.Vector; |
| |
| import javax.xml.transform.ErrorListener; |
| import javax.xml.transform.OutputKeys; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerException; |
| |
| import com.sun.org.apache.xml.internal.serializer.utils.MsgKey; |
| import com.sun.org.apache.xml.internal.serializer.utils.Utils; |
| import com.sun.org.apache.xml.internal.serializer.utils.WrappedRuntimeException; |
| import jdk.xml.internal.JdkXmlUtils; |
| import org.w3c.dom.Node; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.SAXException; |
| |
| //import com.sun.media.sound.IESecurity; |
| |
| /** |
| * This abstract class is a base class for other stream |
| * serializers (xml, html, text ...) that write output to a stream. |
| * |
| * @xsl.usage internal |
| * @LastModified: Apr 2021 |
| */ |
| abstract public class ToStream extends SerializerBase |
| { |
| |
| private static final String COMMENT_BEGIN = "<!--"; |
| private static final String COMMENT_END = "-->"; |
| |
| /** Stack to keep track of disabling output escaping. */ |
| protected BoolStack m_disableOutputEscapingStates = new BoolStack(); |
| |
| |
| /** |
| * The encoding information associated with this serializer. |
| * Although initially there is no encoding, |
| * there is a dummy EncodingInfo object that will say |
| * that every character is in the encoding. This is useful |
| * for a serializer that is in temporary output state and has |
| * no associated encoding. A serializer in final output state |
| * will have an encoding, and will worry about whether |
| * single chars or surrogate pairs of high/low chars form |
| * characters in the output encoding. |
| */ |
| EncodingInfo m_encodingInfo = new EncodingInfo(null,null); |
| |
| /** |
| * Method reference to the sun.io.CharToByteConverter#canConvert method |
| * for this encoding. Invalid if m_charToByteConverter is null. |
| */ |
| java.lang.reflect.Method m_canConvertMeth; |
| |
| |
| |
| /** |
| * Boolean that tells if we already tried to get the converter. |
| */ |
| boolean m_triedToGetConverter = false; |
| |
| |
| /** |
| * Opaque reference to the sun.io.CharToByteConverter for this |
| * encoding. |
| */ |
| Object m_charToByteConverter = null; |
| |
| |
| /** |
| * Stack to keep track of whether or not we need to |
| * preserve whitespace. |
| * |
| * Used to push/pop values used for the field m_ispreserve, but |
| * m_ispreserve is only relevant if m_doIndent is true. |
| * If m_doIndent is false this field has no impact. |
| * |
| */ |
| protected BoolStack m_preserves = new BoolStack(); |
| |
| /** |
| * State flag to tell if preservation of whitespace |
| * is important. |
| * |
| * Used only in shouldIndent() but only if m_doIndent is true. |
| * If m_doIndent is false this flag has no impact. |
| * |
| */ |
| protected boolean m_ispreserve = false; |
| |
| /** |
| * State flag that tells if the previous node processed |
| * was text, so we can tell if we should preserve whitespace. |
| * |
| * Used in endDocument() and shouldIndent() but |
| * only if m_doIndent is true. |
| * If m_doIndent is false this flag has no impact. |
| */ |
| protected boolean m_isprevtext = false; |
| |
| /** |
| * The maximum character size before we have to resort |
| * to escaping. |
| */ |
| protected int m_maxCharacter = Encodings.getLastPrintable(); |
| |
| |
| /** |
| * The system line separator for writing out line breaks. |
| * The default value is from the system property, |
| * but this value can be set through the xsl:output |
| * extension attribute xalan:line-separator. |
| */ |
| protected char[] m_lineSep = |
| SecuritySupport.getSystemProperty("line.separator").toCharArray(); |
| |
| /** |
| * True if the the system line separator is to be used. |
| */ |
| protected boolean m_lineSepUse = true; |
| |
| /** |
| * The length of the line seperator, since the write is done |
| * one character at a time. |
| */ |
| protected int m_lineSepLen = m_lineSep.length; |
| |
| /** |
| * Map that tells which characters should have special treatment, and it |
| * provides character to entity name lookup. |
| */ |
| protected CharInfo m_charInfo; |
| |
| /** True if we control the buffer, and we should flush the output on endDocument. */ |
| boolean m_shouldFlush = true; |
| |
| /** |
| * Add space before '/>' for XHTML. |
| */ |
| protected boolean m_spaceBeforeClose = false; |
| |
| /** |
| * Flag to signal that a newline should be added. |
| * |
| * Used only in indent() which is called only if m_doIndent is true. |
| * If m_doIndent is false this flag has no impact. |
| */ |
| boolean m_startNewLine; |
| |
| /** |
| * Tells if we're in an internal document type subset. |
| */ |
| protected boolean m_inDoctype = false; |
| |
| /** |
| * Flag to quickly tell if the encoding is UTF8. |
| */ |
| boolean m_isUTF8 = false; |
| |
| /** The xsl:output properties. */ |
| protected Properties m_format; |
| |
| /** |
| * remembers if we are in between the startCDATA() and endCDATA() callbacks |
| */ |
| protected boolean m_cdataStartCalled = false; |
| |
| /** |
| * If this flag is true DTD entity references are not left as-is, |
| * which is exiting older behavior. |
| */ |
| private boolean m_expandDTDEntities = true; |
| |
| |
| /** |
| * Default constructor |
| */ |
| public ToStream() |
| { |
| } |
| |
| /** |
| * This helper method to writes out "]]>" when closing a CDATA section. |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| protected void closeCDATA() throws org.xml.sax.SAXException |
| { |
| try |
| { |
| m_writer.write(CDATA_DELIMITER_CLOSE); |
| // write out a CDATA section closing "]]>" |
| m_cdataTagOpen = false; // Remember that we have done so. |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| } |
| |
| /** |
| * Serializes the DOM node. Throws an exception only if an I/O |
| * exception occured while serializing. |
| * |
| * @param node Node to serialize. |
| * @throws IOException An I/O exception occured while serializing |
| */ |
| public void serialize(Node node) throws IOException |
| { |
| |
| try |
| { |
| TreeWalker walker = |
| new TreeWalker(this); |
| |
| walker.traverse(node); |
| } |
| catch (org.xml.sax.SAXException se) |
| { |
| throw new WrappedRuntimeException(se); |
| } |
| } |
| |
| /** |
| * Return true if the character is the high member of a surrogate pair. |
| * |
| * NEEDSDOC @param c |
| * |
| * NEEDSDOC ($objectName$) @return |
| */ |
| static final boolean isUTF16Surrogate(char c) |
| { |
| return (c & 0xFC00) == 0xD800; |
| } |
| |
| /** |
| * Taken from XSLTC |
| */ |
| private boolean m_escaping = true; |
| |
| /** |
| * Flush the formatter's result stream. |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| protected final void flushWriter() throws org.xml.sax.SAXException |
| { |
| final java.io.Writer writer = m_writer; |
| if (null != writer) |
| { |
| try |
| { |
| if (writer instanceof WriterToUTF8Buffered) |
| { |
| if (m_shouldFlush) |
| ((WriterToUTF8Buffered) writer).flush(); |
| else |
| ((WriterToUTF8Buffered) writer).flushBuffer(); |
| } |
| if (writer instanceof WriterToASCI) |
| { |
| if (m_shouldFlush) |
| writer.flush(); |
| } |
| else |
| { |
| // Flush always. |
| // Not a great thing if the writer was created |
| // by this class, but don't have a choice. |
| writer.flush(); |
| } |
| } |
| catch (IOException ioe) |
| { |
| throw new org.xml.sax.SAXException(ioe); |
| } |
| } |
| } |
| |
| /** |
| * Get the output stream where the events will be serialized to. |
| * |
| * @return reference to the result stream, or null of only a writer was |
| * set. |
| */ |
| public OutputStream getOutputStream() |
| { |
| |
| if (m_writer instanceof WriterToUTF8Buffered) |
| return ((WriterToUTF8Buffered) m_writer).getOutputStream(); |
| if (m_writer instanceof WriterToASCI) |
| return ((WriterToASCI) m_writer).getOutputStream(); |
| else |
| return null; |
| } |
| |
| // Implement DeclHandler |
| |
| /** |
| * Report an element type declaration. |
| * |
| * <p>The content model will consist of the string "EMPTY", the |
| * string "ANY", or a parenthesised group, optionally followed |
| * by an occurrence indicator. The model will be normalized so |
| * that all whitespace is removed,and will include the enclosing |
| * parentheses.</p> |
| * |
| * @param name The element type name. |
| * @param model The content model as a normalized string. |
| * @exception SAXException The application may raise an exception. |
| */ |
| public void elementDecl(String name, String model) throws SAXException |
| { |
| // Do not inline external DTD |
| if (m_inExternalDTD) |
| return; |
| try |
| { |
| final java.io.Writer writer = m_writer; |
| DTDprolog(); |
| |
| writer.write("<!ELEMENT "); |
| writer.write(name); |
| writer.write(' '); |
| writer.write(model); |
| writer.write('>'); |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| |
| } |
| |
| /** |
| * Report an internal entity declaration. |
| * |
| * <p>Only the effective (first) declaration for each entity |
| * will be reported.</p> |
| * |
| * @param name The name of the entity. If it is a parameter |
| * entity, the name will begin with '%'. |
| * @param value The replacement text of the entity. |
| * @exception SAXException The application may raise an exception. |
| * @see #externalEntityDecl |
| * @see org.xml.sax.DTDHandler#unparsedEntityDecl |
| */ |
| public void internalEntityDecl(String name, String value) |
| throws SAXException |
| { |
| // Do not inline external DTD |
| if (m_inExternalDTD) |
| return; |
| try |
| { |
| DTDprolog(); |
| outputEntityDecl(name, value); |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| |
| } |
| |
| /** |
| * Output the doc type declaration. |
| * |
| * @param name non-null reference to document type name. |
| * NEEDSDOC @param value |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| void outputEntityDecl(String name, String value) throws IOException |
| { |
| final java.io.Writer writer = m_writer; |
| writer.write("<!ENTITY "); |
| writer.write(name); |
| writer.write(" \""); |
| writer.write(value); |
| writer.write("\">"); |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| } |
| |
| /** |
| * Output a system-dependent line break. |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| protected final void outputLineSep() throws IOException |
| { |
| |
| m_writer.write(m_lineSep, 0, m_lineSepLen); |
| } |
| |
| /** |
| * Specifies an output format for this serializer. It the |
| * serializer has already been associated with an output format, |
| * it will switch to the new format. This method should not be |
| * called while the serializer is in the process of serializing |
| * a document. |
| * |
| * @param format The output format to use |
| */ |
| public void setOutputFormat(Properties format) |
| { |
| |
| boolean shouldFlush = m_shouldFlush; |
| |
| init(m_writer, format, false, false); |
| |
| m_shouldFlush = shouldFlush; |
| } |
| |
| /** |
| * Initialize the serializer with the specified writer and output format. |
| * Must be called before calling any of the serialize methods. |
| * This method can be called multiple times and the xsl:output properties |
| * passed in the 'format' parameter are accumulated across calls. |
| * |
| * @param writer The writer to use |
| * @param format The output format |
| * @param shouldFlush True if the writer should be flushed at EndDocument. |
| */ |
| private synchronized void init( |
| Writer writer, |
| Properties format, |
| boolean defaultProperties, |
| boolean shouldFlush) |
| { |
| |
| m_shouldFlush = shouldFlush; |
| |
| |
| // if we are tracing events we need to trace what |
| // characters are written to the output writer. |
| if (m_tracer != null |
| && !(writer instanceof SerializerTraceWriter) ) |
| m_writer = new SerializerTraceWriter(writer, m_tracer); |
| else |
| m_writer = writer; |
| |
| |
| m_format = format; |
| // m_cdataSectionNames = |
| // OutputProperties.getQNameProperties( |
| // OutputKeys.CDATA_SECTION_ELEMENTS, |
| // format); |
| setCdataSectionElements(OutputKeys.CDATA_SECTION_ELEMENTS, format); |
| |
| setIndentAmount( |
| OutputPropertyUtils.getIntProperty( |
| OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, |
| format)); |
| setIndent( |
| OutputPropertyUtils.getBooleanProperty(OutputKeys.INDENT, format)); |
| |
| { |
| String sep = |
| format.getProperty(OutputPropertiesFactory.S_KEY_LINE_SEPARATOR); |
| if (sep != null) { |
| m_lineSep = sep.toCharArray(); |
| m_lineSepLen = sep.length(); |
| } |
| } |
| |
| boolean shouldNotWriteXMLHeader = |
| OutputPropertyUtils.getBooleanProperty( |
| OutputKeys.OMIT_XML_DECLARATION, |
| format); |
| setOmitXMLDeclaration(shouldNotWriteXMLHeader); |
| setDoctypeSystem(format.getProperty(OutputKeys.DOCTYPE_SYSTEM)); |
| String doctypePublic = format.getProperty(OutputKeys.DOCTYPE_PUBLIC); |
| setDoctypePublic(doctypePublic); |
| |
| // if standalone was explicitly specified |
| if (format.get(OutputKeys.STANDALONE) != null) |
| { |
| String val = format.getProperty(OutputKeys.STANDALONE); |
| if (defaultProperties) |
| setStandaloneInternal(val); |
| else |
| setStandalone(val); |
| } |
| |
| setMediaType(format.getProperty(OutputKeys.MEDIA_TYPE)); |
| |
| if (null != doctypePublic) |
| { |
| if (doctypePublic.startsWith("-//W3C//DTD XHTML")) |
| m_spaceBeforeClose = true; |
| } |
| |
| /* |
| * This code is added for XML 1.1 Version output. |
| */ |
| String version = getVersion(); |
| if (null == version) |
| { |
| version = format.getProperty(OutputKeys.VERSION); |
| setVersion(version); |
| } |
| |
| // initCharsMap(); |
| String encoding = getEncoding(); |
| if (null == encoding) |
| { |
| encoding = |
| Encodings.getMimeEncoding( |
| format.getProperty(OutputKeys.ENCODING)); |
| setEncoding(encoding); |
| } |
| |
| m_isUTF8 = encoding.equals(Encodings.DEFAULT_MIME_ENCODING); |
| |
| // Access this only from the Hashtable level... we don't want to |
| // get default properties. |
| String entitiesFileName = |
| (String) format.get(OutputPropertiesFactory.S_KEY_ENTITIES); |
| |
| if (null != entitiesFileName) |
| { |
| |
| String method = |
| (String) format.get(OutputKeys.METHOD); |
| |
| m_charInfo = CharInfo.getCharInfo(entitiesFileName, method); |
| } |
| |
| } |
| |
| /** |
| * Initialize the serializer with the specified writer and output format. |
| * Must be called before calling any of the serialize methods. |
| * |
| * @param writer The writer to use |
| * @param format The output format |
| */ |
| private synchronized void init(Writer writer, Properties format) |
| { |
| init(writer, format, false, false); |
| } |
| /** |
| * Initialize the serializer with the specified output stream and output |
| * format. Must be called before calling any of the serialize methods. |
| * |
| * @param output The output stream to use |
| * @param format The output format |
| * @param defaultProperties true if the properties are the default |
| * properties |
| * |
| * @throws UnsupportedEncodingException The encoding specified in the |
| * output format is not supported |
| */ |
| protected synchronized void init( |
| OutputStream output, |
| Properties format, |
| boolean defaultProperties) |
| throws UnsupportedEncodingException |
| { |
| |
| String encoding = getEncoding(); |
| if (encoding == null) |
| { |
| // if not already set then get it from the properties |
| encoding = |
| Encodings.getMimeEncoding( |
| format.getProperty(OutputKeys.ENCODING)); |
| setEncoding(encoding); |
| } |
| |
| if (encoding.equalsIgnoreCase("UTF-8")) |
| { |
| m_isUTF8 = true; |
| // if (output instanceof java.io.BufferedOutputStream) |
| // { |
| // init(new WriterToUTF8(output), format, defaultProperties, true); |
| // } |
| // else if (output instanceof java.io.FileOutputStream) |
| // { |
| // init(new WriterToUTF8Buffered(output), format, defaultProperties, true); |
| // } |
| // else |
| // { |
| // // Not sure what to do in this case. I'm going to be conservative |
| // // and not buffer. |
| // init(new WriterToUTF8(output), format, defaultProperties, true); |
| // } |
| |
| |
| init( |
| new WriterToUTF8Buffered(output), |
| format, |
| defaultProperties, |
| true); |
| |
| |
| } |
| else if ( |
| encoding.equals("WINDOWS-1250") |
| || encoding.equals("US-ASCII") |
| || encoding.equals("ASCII")) |
| { |
| init(new WriterToASCI(output), format, defaultProperties, true); |
| } |
| else |
| { |
| Writer osw; |
| |
| try |
| { |
| osw = Encodings.getWriter(output, encoding); |
| } |
| catch (UnsupportedEncodingException uee) |
| { |
| System.out.println( |
| "Warning: encoding \"" |
| + encoding |
| + "\" not supported" |
| + ", using " |
| + Encodings.DEFAULT_MIME_ENCODING); |
| |
| encoding = Encodings.DEFAULT_MIME_ENCODING; |
| setEncoding(encoding); |
| osw = Encodings.getWriter(output, encoding); |
| } |
| |
| init(osw, format, defaultProperties, true); |
| } |
| |
| } |
| |
| /** |
| * Returns the output format for this serializer. |
| * |
| * @return The output format in use |
| */ |
| public Properties getOutputFormat() |
| { |
| return m_format; |
| } |
| |
| /** |
| * Specifies a writer to which the document should be serialized. |
| * This method should not be called while the serializer is in |
| * the process of serializing a document. |
| * |
| * @param writer The output writer stream |
| */ |
| public void setWriter(Writer writer) |
| { |
| // if we are tracing events we need to trace what |
| // characters are written to the output writer. |
| if (m_tracer != null |
| && !(writer instanceof SerializerTraceWriter) ) |
| m_writer = new SerializerTraceWriter(writer, m_tracer); |
| else |
| m_writer = writer; |
| } |
| |
| /** |
| * Set if the operating systems end-of-line line separator should |
| * be used when serializing. If set false NL character |
| * (decimal 10) is left alone, otherwise the new-line will be replaced on |
| * output with the systems line separator. For example on UNIX this is |
| * NL, while on Windows it is two characters, CR NL, where CR is the |
| * carriage-return (decimal 13). |
| * |
| * @param use_sytem_line_break True if an input NL is replaced with the |
| * operating systems end-of-line separator. |
| * @return The previously set value of the serializer. |
| */ |
| public boolean setLineSepUse(boolean use_sytem_line_break) |
| { |
| boolean oldValue = m_lineSepUse; |
| m_lineSepUse = use_sytem_line_break; |
| return oldValue; |
| } |
| |
| /** |
| * Specifies an output stream to which the document should be |
| * serialized. This method should not be called while the |
| * serializer is in the process of serializing a document. |
| * <p> |
| * The encoding specified in the output properties is used, or |
| * if no encoding was specified, the default for the selected |
| * output method. |
| * |
| * @param output The output stream |
| */ |
| public void setOutputStream(OutputStream output) |
| { |
| |
| try |
| { |
| Properties format; |
| if (null == m_format) |
| format = |
| OutputPropertiesFactory.getDefaultMethodProperties( |
| Method.XML); |
| else |
| format = m_format; |
| init(output, format, true); |
| } |
| catch (UnsupportedEncodingException uee) |
| { |
| |
| // Should have been warned in init, I guess... |
| } |
| } |
| |
| /** |
| * @see SerializationHandler#setEscaping(boolean) |
| */ |
| public boolean setEscaping(boolean escape) |
| { |
| final boolean temp = m_escaping; |
| m_escaping = escape; |
| return temp; |
| |
| } |
| |
| |
| /** |
| * Might print a newline character and the indentation amount |
| * of the given depth. |
| * |
| * @param depth the indentation depth (element nesting depth) |
| * |
| * @throws org.xml.sax.SAXException if an error occurs during writing. |
| */ |
| protected void indent(int depth) throws IOException |
| { |
| |
| if (m_startNewLine) |
| outputLineSep(); |
| /* For m_indentAmount > 0 this extra test might be slower |
| * but Xalan's default value is 0, so this extra test |
| * will run faster in that situation. |
| */ |
| if (m_indentAmount > 0) |
| printSpace(depth * m_indentAmount); |
| |
| } |
| |
| /** |
| * Indent at the current element nesting depth. |
| * @throws IOException |
| */ |
| protected void indent() throws IOException |
| { |
| indent(m_elemContext.m_currentElemDepth); |
| } |
| /** |
| * Prints <var>n</var> spaces. |
| * @param n Number of spaces to print. |
| * |
| * @throws org.xml.sax.SAXException if an error occurs when writing. |
| */ |
| private void printSpace(int n) throws IOException |
| { |
| final java.io.Writer writer = m_writer; |
| for (int i = 0; i < n; i++) |
| { |
| writer.write(' '); |
| } |
| |
| } |
| |
| /** |
| * Report an attribute type declaration. |
| * |
| * <p>Only the effective (first) declaration for an attribute will |
| * be reported. The type will be one of the strings "CDATA", |
| * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", |
| * "ENTITIES", or "NOTATION", or a parenthesized token group with |
| * the separator "|" and all whitespace removed.</p> |
| * |
| * @param eName The name of the associated element. |
| * @param aName The name of the attribute. |
| * @param type A string representing the attribute type. |
| * @param valueDefault A string representing the attribute default |
| * ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if |
| * none of these applies. |
| * @param value A string representing the attribute's default value, |
| * or null if there is none. |
| * @exception SAXException The application may raise an exception. |
| */ |
| public void attributeDecl( |
| String eName, |
| String aName, |
| String type, |
| String valueDefault, |
| String value) |
| throws SAXException |
| { |
| // Do not inline external DTD |
| if (m_inExternalDTD) |
| return; |
| try |
| { |
| final java.io.Writer writer = m_writer; |
| DTDprolog(); |
| |
| writer.write("<!ATTLIST "); |
| writer.write(eName); |
| writer.write(' '); |
| |
| writer.write(aName); |
| writer.write(' '); |
| writer.write(type); |
| if (valueDefault != null) |
| { |
| writer.write(' '); |
| writer.write(valueDefault); |
| } |
| |
| //writer.write(" "); |
| //writer.write(value); |
| writer.write('>'); |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| } |
| |
| /** |
| * Get the character stream where the events will be serialized to. |
| * |
| * @return Reference to the result Writer, or null. |
| */ |
| public Writer getWriter() |
| { |
| return m_writer; |
| } |
| |
| /** |
| * Report a parsed external entity declaration. |
| * |
| * <p>Only the effective (first) declaration for each entity |
| * will be reported.</p> |
| * |
| * @param name The name of the entity. If it is a parameter |
| * entity, the name will begin with '%'. |
| * @param publicId The declared public identifier of the entity, or |
| * null if none was declared. |
| * @param systemId The declared system identifier of the entity. |
| * @exception SAXException The application may raise an exception. |
| * @see #internalEntityDecl |
| * @see org.xml.sax.DTDHandler#unparsedEntityDecl |
| */ |
| public void externalEntityDecl( |
| String name, |
| String publicId, |
| String systemId) |
| throws SAXException |
| { |
| try { |
| DTDprolog(); |
| |
| m_writer.write("<!ENTITY "); |
| m_writer.write(name); |
| if (publicId != null) { |
| m_writer.write(" PUBLIC \""); |
| m_writer.write(publicId); |
| |
| } |
| else { |
| m_writer.write(" SYSTEM \""); |
| m_writer.write(systemId); |
| } |
| m_writer.write("\" >"); |
| m_writer.write(m_lineSep, 0, m_lineSepLen); |
| } catch (IOException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| |
| } |
| |
| /** |
| * Tell if this character can be written without escaping. |
| */ |
| protected boolean escapingNotNeeded(char ch) |
| { |
| final boolean ret; |
| if (ch < 127) |
| { |
| // This is the old/fast code here, but is this |
| // correct for all encodings? |
| if (ch >= 0x20 || (0x0A == ch || 0x0D == ch || 0x09 == ch)) |
| ret= true; |
| else |
| ret = false; |
| } |
| else { |
| ret = m_encodingInfo.isInEncoding(ch); |
| } |
| return ret; |
| } |
| |
| /** |
| * Once a surrogate has been detected, write out the pair of |
| * characters if it is in the encoding, or if there is no |
| * encoding, otherwise write out an entity reference |
| * of the value of the unicode code point of the character |
| * represented by the high/low surrogate pair. |
| * <p> |
| * An exception is thrown if there is no low surrogate in the pair, |
| * because the array ends unexpectely, or if the low char is there |
| * but its value is such that it is not a low surrogate. |
| * |
| * @param c the first (high) part of the surrogate, which |
| * must be confirmed before calling this method. |
| * @param ch Character array. |
| * @param i position Where the surrogate was detected. |
| * @param end The end index of the significant characters. |
| * @return 0 if the pair of characters was written out as-is, |
| * the unicode code point of the character represented by |
| * the surrogate pair if an entity reference with that value |
| * was written out. |
| * |
| * @throws IOException |
| * @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected. |
| */ |
| protected int writeUTF16Surrogate(char c, char ch[], int i, int end) |
| throws IOException |
| { |
| int codePoint = 0; |
| if (i + 1 >= end) |
| { |
| throw new IOException( |
| Utils.messages.createMessage( |
| MsgKey.ER_INVALID_UTF16_SURROGATE, |
| new Object[] { Integer.toHexString((int) c)})); |
| } |
| |
| final char high = c; |
| final char low = ch[i+1]; |
| if (!Encodings.isLowUTF16Surrogate(low)) { |
| throw new IOException( |
| Utils.messages.createMessage( |
| MsgKey.ER_INVALID_UTF16_SURROGATE, |
| new Object[] { |
| Integer.toHexString((int) c) |
| + " " |
| + Integer.toHexString(low)})); |
| } |
| |
| final java.io.Writer writer = m_writer; |
| |
| // If we make it to here we have a valid high, low surrogate pair |
| if (m_encodingInfo.isInEncoding(c,low)) { |
| // If the character formed by the surrogate pair |
| // is in the encoding, so just write it out |
| writer.write(ch,i,2); |
| } |
| else { |
| // Don't know what to do with this char, it is |
| // not in the encoding and not a high char in |
| // a surrogate pair, so write out as an entity ref |
| final String encoding = getEncoding(); |
| if (encoding != null) { |
| /* The output encoding is known, |
| * so somthing is wrong. |
| */ |
| codePoint = Encodings.toCodePoint(high, low); |
| // not in the encoding, so write out a character reference |
| writer.write('&'); |
| writer.write('#'); |
| writer.write(Integer.toString(codePoint)); |
| writer.write(';'); |
| } else { |
| /* The output encoding is not known, |
| * so just write it out as-is. |
| */ |
| writer.write(ch, i, 2); |
| } |
| } |
| // non-zero only if character reference was written out. |
| return codePoint; |
| } |
| |
| /** |
| * Handle one of the default entities, return false if it |
| * is not a default entity. |
| * |
| * @param ch character to be escaped. |
| * @param i index into character array. |
| * @param chars non-null reference to character array. |
| * @param len length of chars. |
| * @param fromTextNode true if the characters being processed |
| * are from a text node, false if they are from an attribute value |
| * @param escLF true if the linefeed should be escaped. |
| * |
| * @return i+1 if the character was written, else i. |
| * |
| * @throws java.io.IOException |
| */ |
| protected int accumDefaultEntity( |
| java.io.Writer writer, |
| char ch, |
| int i, |
| char[] chars, |
| int len, |
| boolean fromTextNode, |
| boolean escLF) |
| throws IOException |
| { |
| |
| if (!escLF && CharInfo.S_LINEFEED == ch) |
| { |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| } |
| else |
| { |
| // if this is text node character and a special one of those, |
| // or if this is a character from attribute value and a special one of those |
| if ((fromTextNode && m_charInfo.isSpecialTextChar(ch)) || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch))) |
| { |
| String outputStringForChar = m_charInfo.getOutputStringForChar(ch); |
| |
| if (null != outputStringForChar) |
| { |
| writer.write(outputStringForChar); |
| } |
| else |
| return i; |
| } |
| else |
| return i; |
| } |
| |
| return i + 1; |
| |
| } |
| /** |
| * Normalize the characters, but don't escape. |
| * |
| * @param ch The characters from the XML document. |
| * @param start The start position in the array. |
| * @param length The number of characters to read from the array. |
| * @param isCData true if a CDATA block should be built around the characters. |
| * @param useSystemLineSeparator true if the operating systems |
| * end-of-line separator should be output rather than a new-line character. |
| * |
| * @throws IOException |
| * @throws org.xml.sax.SAXException |
| */ |
| void writeNormalizedChars( |
| char ch[], |
| int start, |
| int length, |
| boolean isCData, |
| boolean useSystemLineSeparator) |
| throws IOException, org.xml.sax.SAXException |
| { |
| final java.io.Writer writer = m_writer; |
| int end = start + length; |
| |
| for (int i = start; i < end; i++) |
| { |
| char c = ch[i]; |
| |
| if (CharInfo.S_LINEFEED == c && useSystemLineSeparator) |
| { |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| } |
| else if (isCData && (!escapingNotNeeded(c))) |
| { |
| // if (i != 0) |
| if (m_cdataTagOpen) |
| closeCDATA(); |
| |
| // This needs to go into a function... |
| if (Encodings.isHighUTF16Surrogate(c)) |
| { |
| writeUTF16Surrogate(c, ch, i, end); |
| i++ ; // process two input characters |
| } |
| else |
| { |
| writer.write("&#"); |
| |
| String intStr = Integer.toString((int) c); |
| |
| writer.write(intStr); |
| writer.write(';'); |
| } |
| |
| // if ((i != 0) && (i < (end - 1))) |
| // if (!m_cdataTagOpen && (i < (end - 1))) |
| // { |
| // writer.write(CDATA_DELIMITER_OPEN); |
| // m_cdataTagOpen = true; |
| // } |
| } |
| else if ( |
| isCData |
| && ((i < (end - 2)) |
| && (']' == c) |
| && (']' == ch[i + 1]) |
| && ('>' == ch[i + 2]))) |
| { |
| writer.write(CDATA_CONTINUE); |
| |
| i += 2; |
| } |
| else |
| { |
| if (escapingNotNeeded(c)) |
| { |
| if (isCData && !m_cdataTagOpen) |
| { |
| writer.write(CDATA_DELIMITER_OPEN); |
| m_cdataTagOpen = true; |
| } |
| writer.write(c); |
| } |
| |
| // This needs to go into a function... |
| else if (Encodings.isHighUTF16Surrogate(c)) |
| { |
| if (m_cdataTagOpen) |
| closeCDATA(); |
| writeUTF16Surrogate(c, ch, i, end); |
| i++; // process two input characters |
| } |
| else |
| { |
| if (m_cdataTagOpen) |
| closeCDATA(); |
| writer.write("&#"); |
| |
| String intStr = Integer.toString((int) c); |
| |
| writer.write(intStr); |
| writer.write(';'); |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * Ends an un-escaping section. |
| * |
| * @see #startNonEscaping |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| public void endNonEscaping() throws org.xml.sax.SAXException |
| { |
| m_disableOutputEscapingStates.pop(); |
| } |
| |
| /** |
| * Starts an un-escaping section. All characters printed within an un- |
| * escaping section are printed as is, without escaping special characters |
| * into entity references. Only XML and HTML serializers need to support |
| * this method. |
| * <p> The contents of the un-escaping section will be delivered through the |
| * regular <tt>characters</tt> event. |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| public void startNonEscaping() throws org.xml.sax.SAXException |
| { |
| m_disableOutputEscapingStates.push(true); |
| } |
| |
| /** |
| * Receive notification of cdata. |
| * |
| * <p>The Parser will call this method to report each chunk of |
| * character data. SAX parsers may return all contiguous character |
| * data in a single chunk, or they may split it into several |
| * chunks; however, all of the characters in any single event |
| * must come from the same external entity, so that the Locator |
| * provides useful information.</p> |
| * |
| * <p>The application must not attempt to read from the array |
| * outside of the specified range.</p> |
| * |
| * <p>Note that some parsers will report whitespace using the |
| * ignorableWhitespace() method rather than this one (validating |
| * parsers must do so).</p> |
| * |
| * @param ch The characters from the XML document. |
| * @param start The start position in the array. |
| * @param length The number of characters to read from the array. |
| * @throws org.xml.sax.SAXException Any SAX exception, possibly |
| * wrapping another exception. |
| * @see #ignorableWhitespace |
| * @see org.xml.sax.Locator |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| protected void cdata(char ch[], int start, final int length) |
| throws org.xml.sax.SAXException |
| { |
| |
| try |
| { |
| final int old_start = start; |
| if (m_elemContext.m_startTagOpen) |
| { |
| closeStartTag(); |
| m_elemContext.m_startTagOpen = false; |
| } |
| m_ispreserve = true; |
| |
| if (shouldIndent()) |
| indent(); |
| |
| boolean writeCDataBrackets = |
| (((length >= 1) && escapingNotNeeded(ch[start]))); |
| |
| /* Write out the CDATA opening delimiter only if |
| * we are supposed to, and if we are not already in |
| * the middle of a CDATA section |
| */ |
| if (writeCDataBrackets && !m_cdataTagOpen) |
| { |
| m_writer.write(CDATA_DELIMITER_OPEN); |
| m_cdataTagOpen = true; |
| } |
| |
| // writer.write(ch, start, length); |
| if (isEscapingDisabled()) |
| { |
| charactersRaw(ch, start, length); |
| } |
| else |
| writeNormalizedChars(ch, start, length, true, m_lineSepUse); |
| |
| /* used to always write out CDATA closing delimiter here, |
| * but now we delay, so that we can merge CDATA sections on output. |
| * need to write closing delimiter later |
| */ |
| if (writeCDataBrackets) |
| { |
| /* if the CDATA section ends with ] don't leave it open |
| * as there is a chance that an adjacent CDATA sections |
| * starts with ]>. |
| * We don't want to merge ]] with > , or ] with ]> |
| */ |
| if (ch[start + length - 1] == ']') |
| closeCDATA(); |
| } |
| |
| // time to fire off CDATA event |
| if (m_tracer != null) |
| super.fireCDATAEvent(ch, old_start, length); |
| } |
| catch (IOException ioe) |
| { |
| throw new org.xml.sax.SAXException( |
| Utils.messages.createMessage( |
| MsgKey.ER_OIERROR, |
| null), |
| ioe); |
| //"IO error", ioe); |
| } |
| } |
| |
| /** |
| * Tell if the character escaping should be disabled for the current state. |
| * |
| * @return true if the character escaping should be disabled. |
| */ |
| private boolean isEscapingDisabled() |
| { |
| return m_disableOutputEscapingStates.peekOrFalse(); |
| } |
| |
| /** |
| * If available, when the disable-output-escaping attribute is used, |
| * output raw text without escaping. |
| * |
| * @param ch The characters from the XML document. |
| * @param start The start position in the array. |
| * @param length The number of characters to read from the array. |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| protected void charactersRaw(char ch[], int start, int length) |
| throws org.xml.sax.SAXException |
| { |
| |
| if (m_inEntityRef) |
| return; |
| try |
| { |
| if (m_elemContext.m_startTagOpen) |
| { |
| closeStartTag(); |
| m_elemContext.m_startTagOpen = false; |
| } |
| |
| m_ispreserve = true; |
| |
| m_writer.write(ch, start, length); |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| |
| } |
| |
| /** |
| * Receive notification of character data. |
| * |
| * <p>The Parser will call this method to report each chunk of |
| * character data. SAX parsers may return all contiguous character |
| * data in a single chunk, or they may split it into several |
| * chunks; however, all of the characters in any single event |
| * must come from the same external entity, so that the Locator |
| * provides useful information.</p> |
| * |
| * <p>The application must not attempt to read from the array |
| * outside of the specified range.</p> |
| * |
| * <p>Note that some parsers will report whitespace using the |
| * ignorableWhitespace() method rather than this one (validating |
| * parsers must do so).</p> |
| * |
| * @param chars The characters from the XML document. |
| * @param start The start position in the array. |
| * @param length The number of characters to read from the array. |
| * @throws org.xml.sax.SAXException Any SAX exception, possibly |
| * wrapping another exception. |
| * @see #ignorableWhitespace |
| * @see org.xml.sax.Locator |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| public void characters(final char chars[], final int start, final int length) |
| throws org.xml.sax.SAXException |
| { |
| // It does not make sense to continue with rest of the method if the number of |
| // characters to read from array is 0. |
| // Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node |
| // is created if string is empty. |
| if (length == 0 || (m_inEntityRef && !m_expandDTDEntities)) |
| return; |
| if (m_elemContext.m_startTagOpen) |
| { |
| closeStartTag(); |
| m_elemContext.m_startTagOpen = false; |
| } |
| else if (m_needToCallStartDocument) |
| { |
| startDocumentInternal(); |
| } |
| |
| if (m_cdataStartCalled || m_elemContext.m_isCdataSection) |
| { |
| /* either due to startCDATA() being called or due to |
| * cdata-section-elements atribute, we need this as cdata |
| */ |
| cdata(chars, start, length); |
| |
| return; |
| } |
| |
| if (m_cdataTagOpen) |
| closeCDATA(); |
| // the check with _escaping is a bit of a hack for XLSTC |
| |
| if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping)) |
| { |
| charactersRaw(chars, start, length); |
| |
| // time to fire off characters generation event |
| if (m_tracer != null) |
| super.fireCharEvent(chars, start, length); |
| |
| return; |
| } |
| |
| if (m_elemContext.m_startTagOpen) |
| { |
| closeStartTag(); |
| m_elemContext.m_startTagOpen = false; |
| } |
| |
| |
| try |
| { |
| int i; |
| char ch1; |
| int startClean; |
| |
| // skip any leading whitspace |
| // don't go off the end and use a hand inlined version |
| // of isWhitespace(ch) |
| final int end = start + length; |
| int lastDirty = start - 1; // last character that needed processing |
| for (i = start; |
| ((i < end) |
| && ((ch1 = chars[i]) == 0x20 |
| || (ch1 == 0xA && m_lineSepUse) |
| || ch1 == 0xD |
| || ch1 == 0x09)); |
| i++) |
| { |
| /* |
| * We are processing leading whitespace, but are doing the same |
| * processing for dirty characters here as for non-whitespace. |
| * |
| */ |
| if (!m_charInfo.isTextASCIIClean(ch1)) |
| { |
| lastDirty = processDirty(chars,end, i,ch1, lastDirty, true); |
| i = lastDirty; |
| } |
| } |
| /* If there is some non-whitespace, mark that we may need |
| * to preserve this. This is only important if we have indentation on. |
| */ |
| if (i < end) |
| m_ispreserve = true; |
| |
| |
| // int lengthClean; // number of clean characters in a row |
| // final boolean[] isAsciiClean = m_charInfo.getASCIIClean(); |
| |
| final boolean isXML10 = XMLVERSION10.equals(getVersion()); |
| // we've skipped the leading whitespace, now deal with the rest |
| for (; i < end; i++) |
| { |
| { |
| // A tight loop to skip over common clean chars |
| // This tight loop makes it easier for the JIT |
| // to optimize. |
| char ch2; |
| while (i<end |
| && ((ch2 = chars[i])<127) |
| && m_charInfo.isTextASCIIClean(ch2)) |
| i++; |
| if (i == end) |
| break; |
| } |
| |
| final char ch = chars[i]; |
| /* The check for isCharacterInC0orC1Ranger and |
| * isNELorLSEPCharacter has been added |
| * to support Control Characters in XML 1.1 |
| */ |
| if (!isCharacterInC0orC1Range(ch) && |
| (isXML10 || !isNELorLSEPCharacter(ch)) && |
| (escapingNotNeeded(ch) && (!m_charInfo.isSpecialTextChar(ch))) |
| || ('"' == ch)) |
| { |
| ; // a character needing no special processing |
| } |
| else |
| { |
| lastDirty = processDirty(chars,end, i, ch, lastDirty, true); |
| i = lastDirty; |
| } |
| } |
| |
| // we've reached the end. Any clean characters at the |
| // end of the array than need to be written out? |
| startClean = lastDirty + 1; |
| if (i > startClean) |
| { |
| int lengthClean = i - startClean; |
| m_writer.write(chars, startClean, lengthClean); |
| } |
| |
| // For indentation purposes, mark that we've just writen text out |
| m_isprevtext = true; |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| |
| // time to fire off characters generation event |
| if (m_tracer != null) |
| super.fireCharEvent(chars, start, length); |
| } |
| /** |
| * This method checks if a given character is between C0 or C1 range |
| * of Control characters. |
| * This method is added to support Control Characters for XML 1.1 |
| * If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method |
| * return false. Since they are whitespace characters, no special processing is needed. |
| * |
| * @param ch |
| * @return boolean |
| */ |
| private static boolean isCharacterInC0orC1Range(char ch) |
| { |
| if(ch == 0x09 || ch == 0x0A || ch == 0x0D) |
| return false; |
| else |
| return (ch >= 0x7F && ch <= 0x9F)|| (ch >= 0x01 && ch <= 0x1F); |
| } |
| /** |
| * This method checks if a given character either NEL (0x85) or LSEP (0x2028) |
| * These are new end of line charcters added in XML 1.1. These characters must be |
| * written as Numeric Character References (NCR) in XML 1.1 output document. |
| * |
| * @param ch |
| * @return boolean |
| */ |
| private static boolean isNELorLSEPCharacter(char ch) |
| { |
| return (ch == 0x85 || ch == 0x2028); |
| } |
| /** |
| * Process a dirty character and any preeceding clean characters |
| * that were not yet processed. |
| * @param chars array of characters being processed |
| * @param end one (1) beyond the last character |
| * in chars to be processed |
| * @param i the index of the dirty character |
| * @param ch the character in chars[i] |
| * @param lastDirty the last dirty character previous to i |
| * @param fromTextNode true if the characters being processed are |
| * from a text node, false if they are from an attribute value. |
| * @return the index of the last character processed |
| */ |
| private int processDirty( |
| char[] chars, |
| int end, |
| int i, |
| char ch, |
| int lastDirty, |
| boolean fromTextNode) throws IOException |
| { |
| int startClean = lastDirty + 1; |
| // if we have some clean characters accumulated |
| // process them before the dirty one. |
| if (i > startClean) |
| { |
| int lengthClean = i - startClean; |
| m_writer.write(chars, startClean, lengthClean); |
| } |
| |
| // process the "dirty" character |
| if (CharInfo.S_LINEFEED == ch && fromTextNode) |
| { |
| m_writer.write(m_lineSep, 0, m_lineSepLen); |
| } |
| else |
| { |
| startClean = |
| accumDefaultEscape( |
| m_writer, |
| (char)ch, |
| i, |
| chars, |
| end, |
| fromTextNode, |
| false); |
| i = startClean - 1; |
| } |
| // Return the index of the last character that we just processed |
| // which is a dirty character. |
| return i; |
| } |
| |
| /** |
| * Receive notification of character data. |
| * |
| * @param s The string of characters to process. |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| public void characters(String s) throws org.xml.sax.SAXException |
| { |
| if (m_inEntityRef && !m_expandDTDEntities) |
| return; |
| final int length = s.length(); |
| if (length > m_charsBuff.length) |
| { |
| m_charsBuff = new char[length * 2 + 1]; |
| } |
| s.getChars(0, length, m_charsBuff, 0); |
| characters(m_charsBuff, 0, length); |
| } |
| |
| /** |
| * Escape and writer.write a character. |
| * |
| * @param ch character to be escaped. |
| * @param i index into character array. |
| * @param chars non-null reference to character array. |
| * @param len length of chars. |
| * @param fromTextNode true if the characters being processed are |
| * from a text node, false if the characters being processed are from |
| * an attribute value. |
| * @param escLF true if the linefeed should be escaped. |
| * |
| * @return i+1 if a character was written, i+2 if two characters |
| * were written out, else return i. |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| protected int accumDefaultEscape( |
| Writer writer, |
| char ch, |
| int i, |
| char[] chars, |
| int len, |
| boolean fromTextNode, |
| boolean escLF) |
| throws IOException |
| { |
| |
| int pos = accumDefaultEntity(writer, ch, i, chars, len, fromTextNode, escLF); |
| |
| if (i == pos) |
| { |
| if (Encodings.isHighUTF16Surrogate(ch)) |
| { |
| |
| // Should be the UTF-16 low surrogate of the hig/low pair. |
| char next; |
| // Unicode code point formed from the high/low pair. |
| int codePoint = 0; |
| |
| if (i + 1 >= len) |
| { |
| throw new IOException( |
| Utils.messages.createMessage( |
| MsgKey.ER_INVALID_UTF16_SURROGATE, |
| new Object[] { Integer.toHexString(ch)})); |
| //"Invalid UTF-16 surrogate detected: " |
| |
| //+Integer.toHexString(ch)+ " ?"); |
| } |
| else |
| { |
| next = chars[++i]; |
| |
| if (!(Encodings.isLowUTF16Surrogate(next))) |
| throw new IOException( |
| Utils.messages.createMessage( |
| MsgKey |
| .ER_INVALID_UTF16_SURROGATE, |
| new Object[] { |
| Integer.toHexString(ch) |
| + " " |
| + Integer.toHexString(next)})); |
| //"Invalid UTF-16 surrogate detected: " |
| |
| //+Integer.toHexString(ch)+" "+Integer.toHexString(next)); |
| codePoint = Encodings.toCodePoint(ch,next); |
| } |
| |
| writer.write("&#"); |
| writer.write(Integer.toString(codePoint)); |
| writer.write(';'); |
| pos += 2; // count the two characters that went into writing out this entity |
| } |
| else |
| { |
| /* This if check is added to support control characters in XML 1.1. |
| * If a character is a Control Character within C0 and C1 range, it is desirable |
| * to write it out as Numeric Character Reference(NCR) regardless of XML Version |
| * being used for output document. |
| */ |
| if (isCharacterInC0orC1Range(ch) || |
| (XMLVERSION11.equals(getVersion()) && isNELorLSEPCharacter(ch))) |
| { |
| writer.write("&#"); |
| writer.write(Integer.toString(ch)); |
| writer.write(';'); |
| } |
| else if ((!escapingNotNeeded(ch) || |
| ( (fromTextNode && m_charInfo.isSpecialTextChar(ch)) |
| || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch)))) |
| && m_elemContext.m_currentElemDepth > 0) |
| { |
| writer.write("&#"); |
| writer.write(Integer.toString(ch)); |
| writer.write(';'); |
| } |
| else |
| { |
| writer.write(ch); |
| } |
| pos++; // count the single character that was processed |
| } |
| |
| } |
| return pos; |
| } |
| |
| /** |
| * Receive notification of the beginning of an element, although this is a |
| * SAX method additional namespace or attribute information can occur before |
| * or after this call, that is associated with this element. |
| * |
| * |
| * @param namespaceURI The Namespace URI, or the empty string if the |
| * element has no Namespace URI or if Namespace |
| * processing is not being performed. |
| * @param localName The local name (without prefix), or the |
| * empty string if Namespace processing is not being |
| * performed. |
| * @param name The element type name. |
| * @param atts The attributes attached to the element, if any. |
| * @throws org.xml.sax.SAXException Any SAX exception, possibly |
| * wrapping another exception. |
| * @see org.xml.sax.ContentHandler#startElement |
| * @see org.xml.sax.ContentHandler#endElement |
| * @see org.xml.sax.AttributeList |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| public void startElement( |
| String namespaceURI, |
| String localName, |
| String name, |
| Attributes atts) |
| throws org.xml.sax.SAXException |
| { |
| if (m_inEntityRef) |
| return; |
| |
| if (m_needToCallStartDocument) |
| { |
| startDocumentInternal(); |
| m_needToCallStartDocument = false; |
| } |
| else if (m_cdataTagOpen) |
| closeCDATA(); |
| try |
| { |
| if ((true == m_needToOutputDocTypeDecl) |
| && (null != getDoctypeSystem())) |
| { |
| outputDocTypeDecl(name, true); |
| } |
| |
| m_needToOutputDocTypeDecl = false; |
| |
| /* before we over-write the current elementLocalName etc. |
| * lets close out the old one (if we still need to) |
| */ |
| if (m_elemContext.m_startTagOpen) |
| { |
| closeStartTag(); |
| m_elemContext.m_startTagOpen = false; |
| } |
| |
| if (namespaceURI != null) |
| ensurePrefixIsDeclared(namespaceURI, name); |
| |
| m_ispreserve = false; |
| |
| if (shouldIndent() && m_startNewLine) |
| { |
| indent(); |
| } |
| |
| m_startNewLine = true; |
| |
| final java.io.Writer writer = m_writer; |
| writer.write('<'); |
| writer.write(name); |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| |
| // process the attributes now, because after this SAX call they might be gone |
| if (atts != null) |
| addAttributes(atts); |
| |
| m_elemContext = m_elemContext.push(namespaceURI,localName,name); |
| m_isprevtext = false; |
| |
| if (m_tracer != null){ |
| firePseudoAttributes(); |
| } |
| |
| } |
| |
| /** |
| * Receive notification of the beginning of an element, additional |
| * namespace or attribute information can occur before or after this call, |
| * that is associated with this element. |
| * |
| * |
| * @param elementNamespaceURI The Namespace URI, or the empty string if the |
| * element has no Namespace URI or if Namespace |
| * processing is not being performed. |
| * @param elementLocalName The local name (without prefix), or the |
| * empty string if Namespace processing is not being |
| * performed. |
| * @param elementName The element type name. |
| * @throws org.xml.sax.SAXException Any SAX exception, possibly |
| * wrapping another exception. |
| * @see org.xml.sax.ContentHandler#startElement |
| * @see org.xml.sax.ContentHandler#endElement |
| * @see org.xml.sax.AttributeList |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| 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); |
| } |
| |
| /** |
| * Output the doc type declaration. |
| * |
| * @param name non-null reference to document type name. |
| * NEEDSDOC @param closeDecl |
| * |
| * @throws java.io.IOException |
| */ |
| void outputDocTypeDecl(String name, boolean closeDecl) throws SAXException |
| { |
| if (m_cdataTagOpen) |
| closeCDATA(); |
| try |
| { |
| final java.io.Writer writer = m_writer; |
| writer.write("<!DOCTYPE "); |
| writer.write(name); |
| |
| String doctypePublic = getDoctypePublic(); |
| if (null != doctypePublic) |
| { |
| writer.write(" PUBLIC \""); |
| writer.write(doctypePublic); |
| writer.write('\"'); |
| } |
| |
| String doctypeSystem = getDoctypeSystem(); |
| if (null != doctypeSystem) |
| { |
| char quote = JdkXmlUtils.getQuoteChar(doctypeSystem); |
| if (null == doctypePublic) { |
| writer.write(" SYSTEM"); |
| } |
| writer.write(" "); |
| writer.write(quote); |
| |
| writer.write(doctypeSystem); |
| writer.write(quote); |
| if (closeDecl) |
| { |
| writer.write(">"); |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| closeDecl = false; // done closing |
| } |
| } |
| boolean dothis = false; |
| if (dothis) |
| { |
| // at one point this code seemed right, |
| // but not anymore - Brian M. |
| if (closeDecl) |
| { |
| writer.write('>'); |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| } |
| } |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| } |
| |
| /** |
| * Process the attributes, which means to write out the currently |
| * collected attributes to the writer. The attributes are not |
| * cleared by this method |
| * |
| * @param writer the writer to write processed attributes to. |
| * @param nAttrs the number of attributes in m_attributes |
| * to be processed |
| * |
| * @throws java.io.IOException |
| * @throws org.xml.sax.SAXException |
| */ |
| public void processAttributes(java.io.Writer writer, int nAttrs) throws IOException, SAXException |
| { |
| /* real SAX attributes are not passed in, so process the |
| * attributes that were collected after the startElement call. |
| * _attribVector is a "cheap" list for Stream serializer output |
| * accumulated over a series of calls to attribute(name,value) |
| */ |
| String encoding = getEncoding(); |
| for (int i = 0; i < nAttrs; i++) |
| { |
| // elementAt is JDK 1.1.8 |
| final String name = m_attributes.getQName(i); |
| final String value = m_attributes.getValue(i); |
| writer.write(' '); |
| writer.write(name); |
| writer.write("=\""); |
| writeAttrString(writer, value, encoding); |
| writer.write('\"'); |
| } |
| } |
| |
| /** |
| * Returns the specified <var>string</var> after substituting <VAR>specials</VAR>, |
| * and UTF-16 surrogates for chracter references <CODE>&#xnn</CODE>. |
| * |
| * @param string String to convert to XML format. |
| * @param encoding CURRENTLY NOT IMPLEMENTED. |
| * |
| * @throws java.io.IOException |
| */ |
| public void writeAttrString( |
| Writer writer, |
| String string, |
| String encoding) |
| throws IOException |
| { |
| final int len = string.length(); |
| if (len > m_attrBuff.length) |
| { |
| m_attrBuff = new char[len*2 + 1]; |
| } |
| string.getChars(0,len, m_attrBuff, 0); |
| final char[] stringChars = m_attrBuff; |
| |
| for (int i = 0; i < len; ) |
| { |
| char ch = stringChars[i]; |
| if (escapingNotNeeded(ch) && (!m_charInfo.isSpecialAttrChar(ch))) |
| { |
| writer.write(ch); |
| i++; |
| } |
| else |
| { // I guess the parser doesn't normalize cr/lf in attributes. -sb |
| // if ((CharInfo.S_CARRIAGERETURN == ch) |
| // && ((i + 1) < len) |
| // && (CharInfo.S_LINEFEED == stringChars[i + 1])) |
| // { |
| // i++; |
| // ch = CharInfo.S_LINEFEED; |
| // } |
| |
| i = accumDefaultEscape(writer, ch, i, stringChars, len, false, true); |
| } |
| } |
| |
| } |
| |
| /** |
| * Receive notification of the end of an element. |
| * |
| * |
| * @param namespaceURI The Namespace URI, or the empty string if the |
| * element has no Namespace URI or if Namespace |
| * processing is not being performed. |
| * @param localName The local name (without prefix), or the |
| * empty string if Namespace processing is not being |
| * performed. |
| * @param name The element type name |
| * @throws org.xml.sax.SAXException Any SAX exception, possibly |
| * wrapping another exception. |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| public void endElement(String namespaceURI, String localName, String name) |
| throws org.xml.sax.SAXException |
| { |
| |
| if (m_inEntityRef) |
| return; |
| |
| // namespaces declared at the current depth are no longer valid |
| // so get rid of them |
| m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null); |
| |
| try |
| { |
| final java.io.Writer writer = m_writer; |
| if (m_elemContext.m_startTagOpen) |
| { |
| if (m_tracer != null) |
| super.fireStartElem(m_elemContext.m_elementName); |
| int nAttrs = m_attributes.getLength(); |
| if (nAttrs > 0) |
| { |
| processAttributes(m_writer, nAttrs); |
| // clear attributes object for re-use with next element |
| m_attributes.clear(); |
| } |
| if (m_spaceBeforeClose) |
| writer.write(" />"); |
| else |
| writer.write("/>"); |
| /* don't need to pop cdataSectionState because |
| * this element ended so quickly that we didn't get |
| * to push the state. |
| */ |
| |
| } |
| else |
| { |
| if (m_cdataTagOpen) |
| closeCDATA(); |
| |
| if (shouldIndent()) |
| indent(m_elemContext.m_currentElemDepth - 1); |
| writer.write('<'); |
| writer.write('/'); |
| writer.write(name); |
| writer.write('>'); |
| } |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| |
| if (!m_elemContext.m_startTagOpen && m_doIndent) |
| { |
| m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop(); |
| } |
| |
| m_isprevtext = false; |
| |
| // fire off the end element event |
| if (m_tracer != null) |
| super.fireEndElem(name); |
| m_elemContext = m_elemContext.m_prev; |
| } |
| |
| /** |
| * Receive notification of the end of an element. |
| * @param name The element type name |
| * @throws org.xml.sax.SAXException Any SAX exception, possibly |
| * wrapping another exception. |
| */ |
| public void endElement(String name) throws org.xml.sax.SAXException |
| { |
| endElement(null, null, name); |
| } |
| |
| /** |
| * Begin the scope of a prefix-URI Namespace mapping |
| * just before another element is about to start. |
| * This call will close any open tags so that the prefix mapping |
| * will not apply to the current element, but the up comming child. |
| * |
| * @see org.xml.sax.ContentHandler#startPrefixMapping |
| * |
| * @param prefix The Namespace prefix being declared. |
| * @param uri The Namespace URI the prefix is mapped to. |
| * |
| * @throws org.xml.sax.SAXException The client may throw |
| * an exception during processing. |
| * |
| */ |
| public void startPrefixMapping(String prefix, String uri) |
| throws org.xml.sax.SAXException |
| { |
| // the "true" causes the flush of any open tags |
| startPrefixMapping(prefix, uri, true); |
| } |
| |
| /** |
| * Handle a prefix/uri mapping, which is associated with a startElement() |
| * that is soon to follow. Need to close any open start tag to make |
| * sure than any name space attributes due to this event are associated wih |
| * the up comming element, not the current one. |
| * @see ExtendedContentHandler#startPrefixMapping |
| * |
| * @param prefix The Namespace prefix being declared. |
| * @param uri The Namespace URI the prefix is mapped to. |
| * @param shouldFlush true if any open tags need to be closed first, this |
| * will impact which element the mapping applies to (open parent, or its up |
| * comming child) |
| * @return returns true if the call made a change to the current |
| * namespace information, false if it did not change anything, e.g. if the |
| * prefix/namespace mapping was already in scope from before. |
| * |
| * @throws org.xml.sax.SAXException The client may throw |
| * an exception during processing. |
| * |
| * |
| */ |
| 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) |
| { |
| /* Brian M.: 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 XSLTC 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; |
| } |
| |
| /** |
| * Receive notification of an XML comment anywhere in the document. This |
| * callback will be used for comments inside or outside the document |
| * element, including comments in the external DTD subset (if read). |
| * @param ch An array holding the characters in the comment. |
| * @param start The starting position in the array. |
| * @param length The number of characters to use from the array. |
| * @throws org.xml.sax.SAXException The application may raise an exception. |
| */ |
| public void comment(char ch[], int start, int length) |
| throws org.xml.sax.SAXException |
| { |
| |
| int start_old = start; |
| if (m_inEntityRef) |
| return; |
| if (m_elemContext.m_startTagOpen) |
| { |
| closeStartTag(); |
| m_elemContext.m_startTagOpen = false; |
| } |
| else if (m_needToCallStartDocument) |
| { |
| startDocumentInternal(); |
| m_needToCallStartDocument = false; |
| } |
| |
| try |
| { |
| if (shouldIndent() && m_isStandalone) |
| indent(); |
| |
| final int limit = start + length; |
| boolean wasDash = false; |
| if (m_cdataTagOpen) |
| closeCDATA(); |
| |
| if (shouldIndent() && !m_isStandalone) |
| indent(); |
| |
| final java.io.Writer writer = m_writer; |
| writer.write(COMMENT_BEGIN); |
| // Detect occurrences of two consecutive dashes, handle as necessary. |
| for (int i = start; i < limit; i++) |
| { |
| if (wasDash && ch[i] == '-') |
| { |
| writer.write(ch, start, i - start); |
| writer.write(" -"); |
| start = i + 1; |
| } |
| wasDash = (ch[i] == '-'); |
| } |
| |
| // if we have some chars in the comment |
| if (length > 0) |
| { |
| // Output the remaining characters (if any) |
| final int remainingChars = (limit - start); |
| if (remainingChars > 0) |
| writer.write(ch, start, remainingChars); |
| // Protect comment end from a single trailing dash |
| if (ch[limit - 1] == '-') |
| writer.write(' '); |
| } |
| writer.write(COMMENT_END); |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| |
| /* |
| * 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; |
| // time to generate comment event |
| if (m_tracer != null) |
| super.fireCommentEvent(ch, start_old,length); |
| } |
| |
| /** |
| * Report the end of a CDATA section. |
| * @throws org.xml.sax.SAXException The application may raise an exception. |
| * |
| * @see #startCDATA |
| */ |
| public void endCDATA() throws org.xml.sax.SAXException |
| { |
| if (m_cdataTagOpen) |
| closeCDATA(); |
| m_cdataStartCalled = false; |
| } |
| |
| /** |
| * Report the end of DTD declarations. |
| * @throws org.xml.sax.SAXException The application may raise an exception. |
| * @see #startDTD |
| */ |
| public void endDTD() throws org.xml.sax.SAXException |
| { |
| try |
| { |
| // Don't output doctype declaration until startDocumentInternal |
| // has been called. Otherwise, it can appear before XML decl. |
| if (m_needToCallStartDocument) { |
| return; |
| } |
| |
| if (m_needToOutputDocTypeDecl) |
| { |
| outputDocTypeDecl(m_elemContext.m_elementName, false); |
| m_needToOutputDocTypeDecl = false; |
| } |
| final java.io.Writer writer = m_writer; |
| if (!m_inDoctype) |
| writer.write("]>"); |
| else |
| { |
| writer.write('>'); |
| } |
| |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| |
| } |
| |
| /** |
| * End the scope of a prefix-URI Namespace mapping. |
| * @see org.xml.sax.ContentHandler#endPrefixMapping |
| * |
| * @param prefix The prefix that was being mapping. |
| * @throws org.xml.sax.SAXException The client may throw |
| * an exception during processing. |
| */ |
| public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException |
| { // do nothing |
| } |
| |
| /** |
| * Receive notification of ignorable whitespace in element content. |
| * |
| * Not sure how to get this invoked quite yet. |
| * |
| * @param ch The characters from the XML document. |
| * @param start The start position in the array. |
| * @param length The number of characters to read from the array. |
| * @throws org.xml.sax.SAXException Any SAX exception, possibly |
| * wrapping another exception. |
| * @see #characters |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| public void ignorableWhitespace(char ch[], int start, int length) |
| throws org.xml.sax.SAXException |
| { |
| |
| if (0 == length) |
| return; |
| characters(ch, start, length); |
| } |
| |
| /** |
| * Receive notification of a skipped entity. |
| * @see org.xml.sax.ContentHandler#skippedEntity |
| * |
| * @param name The name of the skipped entity. If it is a |
| * parameter entity, the name will begin with '%', |
| * and if it is the external DTD subset, it will be the string |
| * "[dtd]". |
| * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping |
| * another exception. |
| */ |
| public void skippedEntity(String name) throws org.xml.sax.SAXException |
| { // TODO: Should handle |
| } |
| |
| /** |
| * Report the start of a CDATA section. |
| * |
| * @throws org.xml.sax.SAXException The application may raise an exception. |
| * @see #endCDATA |
| */ |
| public void startCDATA() throws org.xml.sax.SAXException |
| { |
| m_cdataStartCalled = true; |
| } |
| |
| /** |
| * Report the beginning of an entity. |
| * |
| * The start and end of the document entity are not reported. |
| * The start and end of the external DTD subset are reported |
| * using the pseudo-name "[dtd]". All other events must be |
| * properly nested within start/end entity events. |
| * |
| * @param name The name of the entity. If it is a parameter |
| * entity, the name will begin with '%'. |
| * @throws org.xml.sax.SAXException The application may raise an exception. |
| * @see #endEntity |
| * @see org.xml.sax.ext.DeclHandler#internalEntityDecl |
| * @see org.xml.sax.ext.DeclHandler#externalEntityDecl |
| */ |
| public void startEntity(String name) throws org.xml.sax.SAXException |
| { |
| if (name.equals("[dtd]")) |
| m_inExternalDTD = true; |
| |
| if (!m_expandDTDEntities && !m_inExternalDTD) { |
| /* Only leave the entity as-is if |
| * we've been told not to expand them |
| * and this is not the magic [dtd] name. |
| */ |
| startNonEscaping(); |
| characters("&" + name + ';'); |
| endNonEscaping(); |
| } |
| |
| m_inEntityRef = true; |
| } |
| |
| /** |
| * For the enclosing elements starting tag write out |
| * out any attributes followed by ">" |
| * |
| * @throws org.xml.sax.SAXException |
| */ |
| protected void closeStartTag() throws SAXException |
| { |
| if (m_elemContext.m_startTagOpen) |
| { |
| |
| try |
| { |
| if (m_tracer != null) |
| super.fireStartElem(m_elemContext.m_elementName); |
| int nAttrs = m_attributes.getLength(); |
| if (nAttrs > 0) |
| { |
| processAttributes(m_writer, nAttrs); |
| // clear attributes object for re-use with next element |
| m_attributes.clear(); |
| } |
| m_writer.write('>'); |
| } |
| catch (IOException e) |
| { |
| throw new SAXException(e); |
| } |
| |
| /* whether Xalan or XSLTC, we have the prefix mappings now, so |
| * lets determine if the current element is specified in the cdata- |
| * section-elements list. |
| */ |
| if (m_cdataSectionElements != null) |
| m_elemContext.m_isCdataSection = isCdataSection(); |
| |
| if (m_doIndent) |
| { |
| m_isprevtext = false; |
| m_preserves.push(m_ispreserve); |
| } |
| } |
| |
| } |
| |
| /** |
| * Report the start of DTD declarations, if any. |
| * |
| * Any declarations are assumed to be in the internal subset unless |
| * otherwise indicated. |
| * |
| * @param name The document type name. |
| * @param publicId The declared public identifier for the |
| * external DTD subset, or null if none was declared. |
| * @param systemId The declared system identifier for the |
| * external DTD subset, or null if none was declared. |
| * @throws org.xml.sax.SAXException The application may raise an |
| * exception. |
| * @see #endDTD |
| * @see #startEntity |
| */ |
| public void startDTD(String name, String publicId, String systemId) |
| throws org.xml.sax.SAXException |
| { |
| setDoctypeSystem(systemId); |
| setDoctypePublic(publicId); |
| |
| m_elemContext.m_elementName = name; |
| m_inDoctype = true; |
| } |
| |
| /** |
| * Returns the m_indentAmount. |
| * @return int |
| */ |
| public int getIndentAmount() |
| { |
| return m_indentAmount; |
| } |
| |
| /** |
| * Sets the m_indentAmount. |
| * |
| * @param m_indentAmount The m_indentAmount to set |
| */ |
| public void setIndentAmount(int m_indentAmount) |
| { |
| this.m_indentAmount = m_indentAmount; |
| } |
| |
| /** |
| * Tell if, based on space preservation constraints and the doIndent property, |
| * if an indent should occur. |
| * |
| * @return True if an indent should occur. |
| */ |
| protected boolean shouldIndent() |
| { |
| return m_doIndent && (!m_ispreserve && !m_isprevtext) && (m_elemContext.m_currentElemDepth > 0 || m_isStandalone); |
| } |
| |
| /** |
| * Searches for the list of qname properties with the specified key in the |
| * property list. If the key is not found in this property list, the default |
| * property list, and its defaults, recursively, are then checked. The |
| * method returns <code>null</code> if the property is not found. |
| * |
| * @param key the property key. |
| * @param props the list of properties to search in. |
| * |
| * Sets the vector of local-name/URI pairs of the cdata section elements |
| * specified in the cdata-section-elements property. |
| * |
| * This method is essentially a copy of getQNameProperties() from |
| * OutputProperties. Eventually this method should go away and a call |
| * to setCdataSectionElements(Vector v) should be made directly. |
| */ |
| private void setCdataSectionElements(String key, Properties props) |
| { |
| |
| String s = props.getProperty(key); |
| |
| if (null != s) |
| { |
| // Vector of URI/LocalName pairs |
| Vector v = new Vector(); |
| int l = s.length(); |
| boolean inCurly = false; |
| StringBuffer buf = new StringBuffer(); |
| |
| // parse through string, breaking on whitespaces. I do this instead |
| // of a tokenizer so I can track whitespace inside of curly brackets, |
| // which theoretically shouldn't happen if they contain legal URLs. |
| for (int i = 0; i < l; i++) |
| { |
| char c = s.charAt(i); |
| |
| if (Character.isWhitespace(c)) |
| { |
| if (!inCurly) |
| { |
| if (buf.length() > 0) |
| { |
| addCdataSectionElement(buf.toString(), v); |
| buf.setLength(0); |
| } |
| continue; |
| } |
| } |
| else if ('{' == c) |
| inCurly = true; |
| else if ('}' == c) |
| inCurly = false; |
| |
| buf.append(c); |
| } |
| |
| if (buf.length() > 0) |
| { |
| addCdataSectionElement(buf.toString(), v); |
| buf.setLength(0); |
| } |
| // call the official, public method to set the collected names |
| setCdataSectionElements(v); |
| } |
| |
| } |
| |
| /** |
| * Adds a URI/LocalName pair of strings to the list. |
| * |
| * @param URI_and_localName String of the form "{uri}local" or "local" |
| * |
| * @return a QName object |
| */ |
| private void addCdataSectionElement(String URI_and_localName, Vector v) |
| { |
| |
| StringTokenizer tokenizer = |
| new StringTokenizer(URI_and_localName, "{}", false); |
| String s1 = tokenizer.nextToken(); |
| String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null; |
| |
| if (null == s2) |
| { |
| // add null URI and the local name |
| v.addElement(null); |
| v.addElement(s1); |
| } |
| else |
| { |
| // add URI, then local name |
| v.addElement(s1); |
| v.addElement(s2); |
| } |
| } |
| |
| /** |
| * Remembers the cdata sections specified in the cdata-section-elements. |
| * The "official way to set URI and localName pairs. |
| * This method should be used by both Xalan and XSLTC. |
| * |
| * @param URI_and_localNames a vector of pairs of Strings (URI/local) |
| */ |
| public void setCdataSectionElements(Vector URI_and_localNames) |
| { |
| m_cdataSectionElements = URI_and_localNames; |
| } |
| |
| /** |
| * Makes sure that the namespace URI for the given qualified attribute name |
| * is declared. |
| * @param ns the namespace URI |
| * @param rawName the qualified name |
| * @return returns null if no action is taken, otherwise it returns the |
| * prefix used in declaring the namespace. |
| * @throws SAXException |
| */ |
| protected String ensureAttributesNamespaceIsDeclared( |
| String ns, |
| String localName, |
| String rawName) |
| throws org.xml.sax.SAXException |
| { |
| |
| if (ns != null && ns.length() > 0) |
| { |
| |
| // extract the prefix in front of the raw name |
| int index = 0; |
| String prefixFromRawName = |
| (index = rawName.indexOf(":")) < 0 |
| ? "" |
| : rawName.substring(0, index); |
| |
| if (index > 0) |
| { |
| // we have a prefix, lets see if it maps to a namespace |
| String uri = m_prefixMap.lookupNamespace(prefixFromRawName); |
| if (uri != null && uri.equals(ns)) |
| { |
| // the prefix in the raw name is already maps to the given namespace uri |
| // so we don't need to do anything |
| return null; |
| } |
| else |
| { |
| // The uri does not map to the prefix in the raw name, |
| // so lets make the mapping. |
| this.startPrefixMapping(prefixFromRawName, ns, false); |
| this.addAttribute( |
| "http://www.w3.org/2000/xmlns/", |
| prefixFromRawName, |
| "xmlns:" + prefixFromRawName, |
| "CDATA", |
| ns, false); |
| return prefixFromRawName; |
| } |
| } |
| else |
| { |
| // we don't have a prefix in the raw name. |
| // Does the URI map to a prefix already? |
| String prefix = m_prefixMap.lookupPrefix(ns); |
| if (prefix == null) |
| { |
| // uri is not associated with a prefix, |
| // so lets generate a new prefix to use |
| prefix = m_prefixMap.generateNextPrefix(); |
| this.startPrefixMapping(prefix, ns, false); |
| this.addAttribute( |
| "http://www.w3.org/2000/xmlns/", |
| prefix, |
| "xmlns:" + prefix, |
| "CDATA", |
| ns, false); |
| } |
| |
| return prefix; |
| |
| } |
| } |
| return null; |
| } |
| |
| 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); |
| |
| // 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); |
| } |
| |
| } |
| } |
| } |
| |
| /** |
| * This method flushes any pending events, which can be startDocument() |
| * closing the opening tag of an element, or closing an open CDATA section. |
| */ |
| public void flushPending() throws SAXException |
| { |
| if (m_needToCallStartDocument) |
| { |
| startDocumentInternal(); |
| m_needToCallStartDocument = false; |
| } |
| if (m_elemContext.m_startTagOpen) |
| { |
| closeStartTag(); |
| m_elemContext.m_startTagOpen = false; |
| } |
| |
| if (m_cdataTagOpen) |
| { |
| closeCDATA(); |
| m_cdataTagOpen = false; |
| } |
| } |
| |
| public void setContentHandler(ContentHandler ch) |
| { |
| // this method is really only useful in the ToSAXHandler classes but it is |
| // in the interface. If the method defined here is ever called |
| // we are probably in trouble. |
| } |
| |
| /** |
| * Adds the given attribute to the set of attributes, even if there is |
| * no currently open element. This is useful if a SAX startPrefixMapping() |
| * should need to add an attribute before the element name is seen. |
| * |
| * This method is a copy of its super classes method, except that some |
| * tracing of events is done. This is so the tracing is only done for |
| * stream serializers, not for SAX ones. |
| * |
| * @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. |
| * @return true if the attribute value was added, |
| * false if the attribute already existed and the value was |
| * replaced with the new value. |
| */ |
| public boolean addAttributeAlways( |
| String uri, |
| String localName, |
| String rawName, |
| String type, |
| String value, |
| boolean xslAttribute) |
| { |
| boolean was_added; |
| int index; |
| //if (uri == null || localName == null || uri.length() == 0) |
| index = m_attributes.getIndex(rawName); |
| // Don't use 'localName' as it gives incorrect value, rely only on 'rawName' |
| /*else { |
| index = m_attributes.getIndex(uri, localName); |
| }*/ |
| if (index >= 0) |
| { |
| String old_value = null; |
| if (m_tracer != null) |
| { |
| old_value = m_attributes.getValue(index); |
| if (value.equals(old_value)) |
| old_value = null; |
| } |
| |
| /* We've seen the attribute before. |
| * We may have a null uri or localName, but all we really |
| * want to re-set is the value anyway. |
| */ |
| m_attributes.setValue(index, value); |
| was_added = false; |
| if (old_value != null){ |
| firePseudoAttributes(); |
| } |
| |
| } |
| else |
| { |
| // the attribute doesn't exist yet, create it |
| if (xslAttribute) |
| { |
| /* |
| * This attribute is from an xsl:attribute element so we take some care in |
| * adding it, e.g. |
| * <elem1 foo:attr1="1" xmlns:foo="uri1"> |
| * <xsl:attribute name="foo:attr2">2</xsl:attribute> |
| * </elem1> |
| * |
| * We are adding attr1 and attr2 both as attributes of elem1, |
| * and this code is adding attr2 (the xsl:attribute ). |
| * We could have a collision with the prefix like in the example above. |
| */ |
| |
| // In the example above, is there a prefix like foo ? |
| final int colonIndex = rawName.indexOf(':'); |
| if (colonIndex > 0) |
| { |
| String prefix = rawName.substring(0,colonIndex); |
| NamespaceMappings.MappingRecord existing_mapping = m_prefixMap.getMappingFromPrefix(prefix); |
| |
| /* Before adding this attribute (foo:attr2), |
| * is the prefix for it (foo) already mapped at the current depth? |
| */ |
| if (existing_mapping != null |
| && existing_mapping.m_declarationDepth == m_elemContext.m_currentElemDepth |
| && !existing_mapping.m_uri.equals(uri)) |
| { |
| /* |
| * There is an existing mapping of this prefix, |
| * it differs from the one we need, |
| * and unfortunately it is at the current depth so we |
| * can not over-ride it. |
| */ |
| |
| /* |
| * Are we lucky enough that an existing other prefix maps to this URI ? |
| */ |
| prefix = m_prefixMap.lookupPrefix(uri); |
| if (prefix == null) |
| { |
| /* Unfortunately there is no existing prefix that happens to map to ours, |
| * so to avoid a prefix collision we must generated a new prefix to use. |
| * This is OK because the prefix URI mapping |
| * defined in the xsl:attribute is short in scope, |
| * just the xsl:attribute element itself, |
| * and at this point in serialization the body of the |
| * xsl:attribute, if any, is just a String. Right? |
| * . . . I sure hope so - Brian M. |
| */ |
| prefix = m_prefixMap.generateNextPrefix(); |
| } |
| |
| rawName = prefix + ':' + localName; |
| } |
| } |
| |
| try |
| { |
| /* This is our last chance to make sure the namespace for this |
| * attribute is declared, especially if we just generated an alternate |
| * prefix to avoid a collision (the new prefix/rawName will go out of scope |
| * soon and be lost ... last chance here. |
| */ |
| String prefixUsed = |
| ensureAttributesNamespaceIsDeclared( |
| uri, |
| localName, |
| rawName); |
| } |
| catch (SAXException e) |
| { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| m_attributes.addAttribute(uri, localName, rawName, type, value); |
| was_added = true; |
| if (m_tracer != null){ |
| firePseudoAttributes(); |
| } |
| } |
| return was_added; |
| } |
| |
| /** |
| * To fire off the pseudo characters of attributes, as they currently |
| * exist. This method should be called everytime an attribute is added, |
| * or when an attribute value is changed, or an element is created. |
| */ |
| |
| protected void firePseudoAttributes() |
| { |
| if (m_tracer != null) |
| { |
| try |
| { |
| // flush out the "<elemName" if not already flushed |
| m_writer.flush(); |
| |
| // make a StringBuffer to write the name="value" pairs to. |
| StringBuffer sb = new StringBuffer(); |
| int nAttrs = m_attributes.getLength(); |
| if (nAttrs > 0) |
| { |
| // make a writer that internally appends to the same |
| // StringBuffer |
| java.io.Writer writer = |
| new ToStream.WritertoStringBuffer(sb); |
| |
| processAttributes(writer, nAttrs); |
| // Don't clear the attributes! |
| // We only want to see what would be written out |
| // at this point, we don't want to loose them. |
| } |
| sb.append('>'); // the potential > after the attributes. |
| // convert the StringBuffer to a char array and |
| // emit the trace event that these characters "might" |
| // be written |
| char ch[] = sb.toString().toCharArray(); |
| m_tracer.fireGenerateEvent( |
| SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS, |
| ch, |
| 0, |
| ch.length); |
| } |
| catch (IOException ioe) |
| { |
| // ignore ? |
| } |
| catch (SAXException se) |
| { |
| // ignore ? |
| } |
| } |
| } |
| |
| /** |
| * This inner class is used only to collect attribute values |
| * written by the method writeAttrString() into a string buffer. |
| * In this manner trace events, and the real writing of attributes will use |
| * the same code. |
| */ |
| private class WritertoStringBuffer extends java.io.Writer |
| { |
| final private StringBuffer m_stringbuf; |
| /** |
| * @see java.io.Writer#write(char[], int, int) |
| */ |
| WritertoStringBuffer(StringBuffer sb) |
| { |
| m_stringbuf = sb; |
| } |
| |
| public void write(char[] arg0, int arg1, int arg2) throws IOException |
| { |
| m_stringbuf.append(arg0, arg1, arg2); |
| } |
| /** |
| * @see java.io.Writer#flush() |
| */ |
| public void flush() throws IOException |
| { |
| } |
| /** |
| * @see java.io.Writer#close() |
| */ |
| public void close() throws IOException |
| { |
| } |
| |
| public void write(int i) |
| { |
| m_stringbuf.append((char) i); |
| } |
| |
| public void write(String s) |
| { |
| m_stringbuf.append(s); |
| } |
| } |
| |
| /** |
| * @see SerializationHandler#setTransformer(Transformer) |
| */ |
| public void setTransformer(Transformer transformer) { |
| super.setTransformer(transformer); |
| if (m_tracer != null |
| && !(m_writer instanceof SerializerTraceWriter) ) |
| m_writer = new SerializerTraceWriter(m_writer, m_tracer); |
| |
| |
| } |
| /** |
| * 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()) |
| { |
| resetToStream(); |
| wasReset = true; |
| } |
| return wasReset; |
| } |
| |
| /** |
| * Reset all of the fields owned by ToStream class |
| * |
| */ |
| private void resetToStream() |
| { |
| this.m_cdataStartCalled = false; |
| /* The stream is being reset. It is one of |
| * ToXMLStream, ToHTMLStream ... and this type can't be changed |
| * so neither should m_charInfo which is associated with the |
| * type of Stream. Just leave m_charInfo as-is for the next re-use. |
| * |
| */ |
| // this.m_charInfo = null; // don't set to null |
| |
| this.m_disableOutputEscapingStates.clear(); |
| |
| this.m_escaping = true; |
| // Leave m_format alone for now - Brian M. |
| // this.m_format = null; |
| this.m_inDoctype = false; |
| this.m_ispreserve = false; |
| this.m_ispreserve = false; |
| this.m_isprevtext = false; |
| this.m_isUTF8 = false; // ?? used anywhere ?? |
| this.m_preserves.clear(); |
| this.m_shouldFlush = true; |
| this.m_spaceBeforeClose = false; |
| this.m_startNewLine = false; |
| this.m_lineSepUse = true; |
| // DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !! |
| // this.m_writer = null; |
| this.m_expandDTDEntities = true; |
| |
| } |
| |
| /** |
| * Sets the character encoding coming from the xsl:output encoding stylesheet attribute. |
| * @param encoding the character encoding |
| */ |
| public void setEncoding(String encoding) |
| { |
| String old = getEncoding(); |
| super.setEncoding(encoding); |
| if (old == null || !old.equals(encoding)) { |
| // If we have changed the setting of the |
| m_encodingInfo = Encodings.getEncodingInfo(encoding); |
| |
| if (encoding != null && m_encodingInfo.name == null) { |
| // We tried to get an EncodingInfo for Object for the given |
| // encoding, but it came back with an internall null name |
| // so the encoding is not supported by the JDK, issue a message. |
| String msg = Utils.messages.createMessage( |
| MsgKey.ER_ENCODING_NOT_SUPPORTED,new Object[]{ encoding }); |
| try |
| { |
| // Prepare to issue the warning message |
| Transformer tran = super.getTransformer(); |
| if (tran != null) { |
| 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); |
| } |
| else |
| System.out.println(msg); |
| } |
| catch (Exception e){} |
| } |
| } |
| return; |
| } |
| |
| /** |
| * Simple stack for boolean values. |
| * |
| * This class is a copy of the one in com.sun.org.apache.xml.internal.utils. |
| * It exists to cut the serializers dependancy on that package. |
| * A minor changes from that package are: |
| * doesn't implement Clonable |
| * |
| * @xsl.usage internal |
| */ |
| static final class BoolStack |
| { |
| |
| /** Array of boolean values */ |
| private boolean m_values[]; |
| |
| /** Array size allocated */ |
| private int m_allocatedSize; |
| |
| /** Index into the array of booleans */ |
| private int m_index; |
| |
| /** |
| * Default constructor. Note that the default |
| * block size is very small, for small lists. |
| */ |
| public BoolStack() |
| { |
| this(32); |
| } |
| |
| /** |
| * Construct a IntVector, using the given block size. |
| * |
| * @param size array size to allocate |
| */ |
| public BoolStack(int size) |
| { |
| |
| m_allocatedSize = size; |
| m_values = new boolean[size]; |
| m_index = -1; |
| } |
| |
| /** |
| * Get the length of the list. |
| * |
| * @return Current length of the list |
| */ |
| public final int size() |
| { |
| return m_index + 1; |
| } |
| |
| /** |
| * Clears the stack. |
| * |
| */ |
| public final void clear() |
| { |
| m_index = -1; |
| } |
| |
| /** |
| * Pushes an item onto the top of this stack. |
| * |
| * |
| * @param val the boolean to be pushed onto this stack. |
| * @return the <code>item</code> argument. |
| */ |
| public final boolean push(boolean val) |
| { |
| |
| if (m_index == m_allocatedSize - 1) |
| grow(); |
| |
| return (m_values[++m_index] = val); |
| } |
| |
| /** |
| * Removes the object at the top of this stack and returns that |
| * object as the value of this function. |
| * |
| * @return The object at the top of this stack. |
| * @throws EmptyStackException if this stack is empty. |
| */ |
| public final boolean pop() |
| { |
| return m_values[m_index--]; |
| } |
| |
| /** |
| * Removes the object at the top of this stack and returns the |
| * next object at the top as the value of this function. |
| * |
| * |
| * @return Next object to the top or false if none there |
| */ |
| public final boolean popAndTop() |
| { |
| |
| m_index--; |
| |
| return (m_index >= 0) ? m_values[m_index] : false; |
| } |
| |
| /** |
| * Set the item at the top of this stack |
| * |
| * |
| * @param b Object to set at the top of this stack |
| */ |
| public final void setTop(boolean b) |
| { |
| m_values[m_index] = b; |
| } |
| |
| /** |
| * Looks at the object at the top of this stack without removing it |
| * from the stack. |
| * |
| * @return the object at the top of this stack. |
| * @throws EmptyStackException if this stack is empty. |
| */ |
| public final boolean peek() |
| { |
| return m_values[m_index]; |
| } |
| |
| /** |
| * Looks at the object at the top of this stack without removing it |
| * from the stack. If the stack is empty, it returns false. |
| * |
| * @return the object at the top of this stack. |
| */ |
| public final boolean peekOrFalse() |
| { |
| return (m_index > -1) ? m_values[m_index] : false; |
| } |
| |
| /** |
| * Looks at the object at the top of this stack without removing it |
| * from the stack. If the stack is empty, it returns true. |
| * |
| * @return the object at the top of this stack. |
| */ |
| public final boolean peekOrTrue() |
| { |
| return (m_index > -1) ? m_values[m_index] : true; |
| } |
| |
| /** |
| * Tests if this stack is empty. |
| * |
| * @return <code>true</code> if this stack is empty; |
| * <code>false</code> otherwise. |
| */ |
| public boolean isEmpty() |
| { |
| return (m_index == -1); |
| } |
| |
| /** |
| * Grows the size of the stack |
| * |
| */ |
| private void grow() |
| { |
| |
| m_allocatedSize *= 2; |
| |
| boolean newVector[] = new boolean[m_allocatedSize]; |
| |
| System.arraycopy(m_values, 0, newVector, 0, m_index + 1); |
| |
| m_values = newVector; |
| } |
| } |
| |
| // Implement DTDHandler |
| /** |
| * If this method is called, the serializer is used as a |
| * DTDHandler, which changes behavior how the serializer |
| * handles document entities. |
| * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String) |
| */ |
| public void notationDecl(String name, String pubID, String sysID) throws SAXException { |
| // TODO Auto-generated method stub |
| try { |
| DTDprolog(); |
| |
| m_writer.write("<!NOTATION "); |
| m_writer.write(name); |
| if (pubID != null) { |
| m_writer.write(" PUBLIC \""); |
| m_writer.write(pubID); |
| |
| } |
| else { |
| m_writer.write(" SYSTEM \""); |
| m_writer.write(sysID); |
| } |
| m_writer.write("\" >"); |
| m_writer.write(m_lineSep, 0, m_lineSepLen); |
| } catch (IOException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * If this method is called, the serializer is used as a |
| * DTDHandler, which changes behavior how the serializer |
| * handles document entities. |
| * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String) |
| */ |
| public void unparsedEntityDecl(String name, String pubID, String sysID, String notationName) throws SAXException { |
| // TODO Auto-generated method stub |
| try { |
| DTDprolog(); |
| |
| m_writer.write("<!ENTITY "); |
| m_writer.write(name); |
| if (pubID != null) { |
| m_writer.write(" PUBLIC \""); |
| m_writer.write(pubID); |
| |
| } |
| else { |
| m_writer.write(" SYSTEM \""); |
| m_writer.write(sysID); |
| } |
| m_writer.write("\" NDATA "); |
| m_writer.write(notationName); |
| m_writer.write(" >"); |
| m_writer.write(m_lineSep, 0, m_lineSepLen); |
| } catch (IOException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * A private helper method to output the |
| * @throws SAXException |
| * @throws IOException |
| */ |
| private void DTDprolog() throws SAXException, IOException { |
| final java.io.Writer writer = m_writer; |
| if (m_needToOutputDocTypeDecl) |
| { |
| outputDocTypeDecl(m_elemContext.m_elementName, false); |
| m_needToOutputDocTypeDecl = false; |
| } |
| if (m_inDoctype) |
| { |
| writer.write(" ["); |
| writer.write(m_lineSep, 0, m_lineSepLen); |
| m_inDoctype = false; |
| } |
| } |
| |
| /** |
| * If set to false the serializer does not expand DTD entities, |
| * but leaves them as is, the default value is true; |
| */ |
| public void setDTDEntityExpansion(boolean expand) { |
| m_expandDTDEntities = expand; |
| } |
| } |