blob: 5d38068161047ab07b2b30b290f85e83565a8a12 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xalan" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, Lotus
* Development Corporation., http://www.lotus.com. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.xalan.serialize;
import java.io.Writer;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Stack;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Properties;
import java.util.BitSet;
import org.xml.sax.*;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.ext.DeclHandler;
import org.w3c.dom.Node;
import org.apache.xalan.serialize.Serializer;
import org.apache.xalan.serialize.DOMSerializer;
import org.apache.xml.utils.QName;
import org.apache.xalan.templates.OutputProperties;
import org.apache.xml.utils.BoolStack;
import org.apache.xml.utils.TreeWalker;
import org.apache.xml.utils.WrappedRuntimeException;
import org.apache.xml.utils.SystemIDResolver;
import org.apache.xalan.res.XSLTErrorResources;
import org.apache.xalan.res.XSLMessages;
import org.apache.xpath.res.XPATHErrorResources;
import javax.xml.transform.Result;
import javax.xml.transform.OutputKeys;
/**
* <meta name="usage" content="general"/>
* SerializerToXML formats SAX-style events into XML.
*/
public class SerializerToXML
implements ContentHandler, LexicalHandler, DeclHandler, Serializer,
DOMSerializer
{
/**
* The writer where the XML will be written.
*/
protected Writer m_writer = null;
/** True if we control the buffer, and we should flush the output on endDocument. */
boolean m_shouldFlush = true;
// /** The output stream where the result stream is written. */
// protected OutputStream m_outputStream = System.out;
/**
* The character encoding. Must match the encoding used for the printWriter.
*/
protected String m_encoding = null;
/**
* Assume java encoding names are the same as the ISO encoding names if this is true.
*/
static boolean javaEncodingIsISO = false;
/**
* Tells if we should write the XML declaration.
*/
public boolean m_shouldNotWriteXMLHeader = false;
/**
* Tells the XML version, for writing out to the XML decl.
*/
public String m_version = null;
/**
* A stack of Boolean objects that tell if the given element
* has children.
*/
protected BoolStack m_elemStack = new BoolStack();
/** Stack to keep track of disabling output escaping. */
protected BoolStack m_disableOutputEscapingStates = new BoolStack();
/** True will be pushed, if characters should be in CDATA section blocks. */
protected BoolStack m_cdataSectionStates = new BoolStack();
/** List of QNames obtained from the xsl:output properties. */
protected Vector m_cdataSectionNames = null;
/** True if the current characters should be in CDATA blocks. */
protected boolean m_inCData = false;
/**
* Tell if the character escaping should be disabled for the current state.
*
* @return true if the character escaping should be disabled.
*/
protected boolean isEscapingDisabled()
{
return m_disableOutputEscapingStates.peekOrFalse();
}
/**
* Tell if the characters in the current state should be put in
* cdata section blocks.
*
* @return true if the characters in the current state should be put in
* cdata section blocks.
*/
protected boolean isCDataSection()
{
return m_inCData || m_cdataSectionStates.peekOrFalse();
}
/**
* Use the system line seperator to write line breaks.
*/
protected final char[] m_lineSep =
System.getProperty("line.separator").toCharArray();
/**
* The length of the line seperator, since the write is done
* one character at a time.
*/
protected final int m_lineSepLen = m_lineSep.length;
/**
* Output a system-dependent line break.
*
* @throws org.xml.sax.SAXException
*/
protected final void outputLineSep() throws org.xml.sax.SAXException
{
try
{
m_writer.write(m_lineSep, 0, m_lineSepLen);
}
catch (IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* State flag to tell if preservation of whitespace
* is important.
*/
protected boolean m_ispreserve = false;
/**
* Stack to keep track of whether or not we need to
* preserve whitespace.
*/
protected BoolStack m_preserves = new BoolStack();
/**
* State flag that tells if the previous node processed
* was text, so we can tell if we should preserve whitespace.
*/
protected boolean m_isprevtext = false;
/**
* Flag to tell if indenting (pretty-printing) is on.
*/
protected boolean m_doIndent = false;
/**
* Flag to keep track of the indent amount.
*/
protected int m_currentIndent = 0;
/**
* Amount to indent.
*/
public int m_indentAmount = 0;
/**
* Current level of indent.
*/
protected int level = 0;
/**
* Flag to signal that a newline should be added.
*/
boolean m_startNewLine;
/**
* Flag to tell that we need to add the doctype decl,
* which we can't do until the first element is
* encountered.
*/
boolean m_needToOutputDocTypeDecl = true;
/**
* The System ID for the doc type.
*/
String m_doctypeSystem;
/**
* The public ID for the doc type.
*/
String m_doctypePublic;
/**
* The standalone value for the doctype.
*/
boolean m_standalone = false;
/**
* True if standalone was specified.
*/
boolean m_standaloneWasSpecified = false;
/**
* The mediatype. Not used right now.
*/
String m_mediatype;
/**
* Tells if we're in an EntityRef event.
*/
protected boolean m_inEntityRef = false;
/**
* Tells if we're in an internal document type subset.
*/
private boolean m_inDoctype = false;
/**
* Map that tells which XML characters should have special treatment, and it
* provides character to entity name lookup.
*/
protected static CharInfo m_xmlcharInfo =
new CharInfo(CharInfo.XML_ENTITIES_RESOURCE);
/**
* Map that tells which characters should have special treatment, and it
* provides character to entity name lookup.
*/
protected CharInfo m_charInfo;
/** Table of user-specified char infos. */
private static Hashtable m_charInfos = null;
/**
* Flag to quickly tell if the encoding is UTF8.
*/
boolean m_isUTF8 = false;
/**
* The maximum character size before we have to resort
* to escaping.
*/
protected int m_maxCharacter = Encodings.getLastPrintable();
/**
* Add space before '/>' for XHTML.
*/
public boolean m_spaceBeforeClose = false;
/** The xsl:output properties. */
protected Properties m_format;
/** Indicate whether running in Debug mode */
private static final boolean DEBUG = false;
/** This flag is set while receiving events from the external DTD */
private boolean m_inExternalDTD = false;
/**
* Default constructor.
*/
public SerializerToXML()
{
m_charInfo = m_xmlcharInfo;
}
/**
* Copy properties from another SerializerToXML.
*
* @param xmlListener non-null reference to a SerializerToXML object.
*/
public void CopyFrom(SerializerToXML xmlListener)
{
m_writer = xmlListener.m_writer;
// m_outputStream = xmlListener.m_outputStream;
m_encoding = xmlListener.m_encoding;
javaEncodingIsISO = xmlListener.javaEncodingIsISO;
m_shouldNotWriteXMLHeader = xmlListener.m_shouldNotWriteXMLHeader;
// m_shouldNotWriteXMLHeader = xmlListener.m_shouldNotWriteXMLHeader;
m_elemStack = xmlListener.m_elemStack;
// m_lineSep = xmlListener.m_lineSep;
// m_lineSepLen = xmlListener.m_lineSepLen;
m_ispreserve = xmlListener.m_ispreserve;
m_preserves = xmlListener.m_preserves;
m_isprevtext = xmlListener.m_isprevtext;
m_doIndent = xmlListener.m_doIndent;
m_currentIndent = xmlListener.m_currentIndent;
m_indentAmount = xmlListener.m_indentAmount;
level = xmlListener.level;
m_startNewLine = xmlListener.m_startNewLine;
m_needToOutputDocTypeDecl = xmlListener.m_needToOutputDocTypeDecl;
m_doctypeSystem = xmlListener.m_doctypeSystem;
m_doctypePublic = xmlListener.m_doctypePublic;
m_standalone = xmlListener.m_standalone;
m_mediatype = xmlListener.m_mediatype;
m_maxCharacter = xmlListener.m_maxCharacter;
m_spaceBeforeClose = xmlListener.m_spaceBeforeClose;
m_inCData = xmlListener.m_inCData;
// m_pos = xmlListener.m_pos;
m_pos = 0;
}
/**
* 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
*/
public synchronized void init(Writer writer, Properties format)
{
init(writer, format, false);
}
/**
* 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
* @param shouldFlush True if the writer should be flushed at EndDocument.
*/
private synchronized void init(Writer writer, Properties format,
boolean shouldFlush)
{
m_shouldFlush = shouldFlush;
m_writer = writer;
m_format = format;
m_cdataSectionNames =
OutputProperties.getQNameProperties(OutputKeys.CDATA_SECTION_ELEMENTS,
format);
m_indentAmount =
OutputProperties.getIntProperty(OutputProperties.S_KEY_INDENT_AMOUNT,
format);
m_doIndent = OutputProperties.getBooleanProperty(OutputKeys.INDENT,
format);
m_shouldNotWriteXMLHeader =
OutputProperties.getBooleanProperty(OutputKeys.OMIT_XML_DECLARATION,
format);
m_doctypeSystem = format.getProperty(OutputKeys.DOCTYPE_SYSTEM);
m_doctypePublic = format.getProperty(OutputKeys.DOCTYPE_PUBLIC);
m_standaloneWasSpecified = (null != format.get(OutputKeys.STANDALONE));
m_standalone = OutputProperties.getBooleanProperty(OutputKeys.STANDALONE,
format);
m_mediatype = format.getProperty(OutputKeys.MEDIA_TYPE);
if (null != m_doctypePublic)
{
if (m_doctypePublic.startsWith("-//W3C//DTD XHTML"))
m_spaceBeforeClose = true;
}
// initCharsMap();
if (null == m_encoding)
m_encoding =
Encodings.getMimeEncoding(format.getProperty(OutputKeys.ENCODING));
m_isUTF8 = m_encoding.equals(Encodings.DEFAULT_MIME_ENCODING);
m_maxCharacter = Encodings.getLastPrintable(m_encoding);
// Access this only from the Hashtable level... we don't want to
// get default properties.
String entitiesFileName =
(String) format.get(OutputProperties.S_KEY_ENTITIES);
if (null != entitiesFileName)
{
try
{
m_charInfo = null;
if (null == m_charInfos)
{
synchronized (m_xmlcharInfo)
{
if (null == m_charInfos) // secondary check
m_charInfos = new Hashtable();
}
}
else
{
m_charInfo = (CharInfo) m_charInfos.get(entitiesFileName);
}
if (null == m_charInfo)
m_charInfo = new CharInfo(entitiesFileName);
if (null == m_charInfo)
{
String absoluteEntitiesFileName;
if (entitiesFileName.indexOf(':') < 0)
{
absoluteEntitiesFileName =
SystemIDResolver.getAbsoluteURIFromRelative(entitiesFileName);
}
else
{
absoluteEntitiesFileName =
SystemIDResolver.getAbsoluteURI(entitiesFileName, null);
}
m_charInfo = new CharInfo(absoluteEntitiesFileName);
m_charInfos.put(entitiesFileName, m_charInfo);
}
}
catch (javax.xml.transform.TransformerException te)
{
throw new org.apache.xml.utils.WrappedRuntimeException(te);
}
}
}
/**
* 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
* @throws UnsupportedEncodingException The encoding specified
* in the output format is not supported
*/
public synchronized void init(OutputStream output, Properties format)
throws UnsupportedEncodingException
{
if (null == format)
{
OutputProperties op = new OutputProperties(Method.XML);
format = op.getProperties();
}
m_encoding =
Encodings.getMimeEncoding(format.getProperty(OutputKeys.ENCODING));
if (m_encoding.equalsIgnoreCase("UTF-8"))
{
m_isUTF8 = true;
if(output instanceof java.io.BufferedOutputStream)
{
init(new WriterToUTF8(output), format, true);
}
else if(output instanceof java.io.FileOutputStream)
{
init(new WriterToUTF8Buffered(output), format, true);
}
else
{
// Not sure what to do in this case. I'm going to be conservative
// and not buffer.
init(new WriterToUTF8(output), format, true);
}
}
else if (m_encoding.equals("WINDOWS-1250")
|| m_encoding.equals("US-ASCII") || m_encoding.equals("ASCII"))
{
init(new WriterToASCI(output), format, true);
}
else
{
Writer osw;
try
{
osw = Encodings.getWriter(output, m_encoding);
}
catch (UnsupportedEncodingException uee)
{
System.out.println("Warning: encoding \"" + m_encoding
+ "\" not supported" + ", using "
+ Encodings.DEFAULT_MIME_ENCODING);
m_encoding = Encodings.DEFAULT_MIME_ENCODING;
osw = Encodings.getWriter(output, m_encoding);
}
m_maxCharacter = Encodings.getLastPrintable(m_encoding);
init(osw, format, true);
}
}
/**
* Receive an object for locating the origin of SAX document events.
*
* @param locator An object that can return the location of
* any SAX document event.
* @see org.xml.sax.Locator
*/
public void setDocumentLocator(Locator locator)
{
// I don't do anything with this yet.
}
/**
* Output the doc type declaration.
*
* @param name non-null reference to document type name.
* NEEDSDOC @param closeDecl
*
* @throws org.xml.sax.SAXException
*/
void outputDocTypeDecl(String name, boolean closeDecl)
throws org.xml.sax.SAXException
{
try
{
final Writer writer = m_writer;
writer.write("<!DOCTYPE ");
writer.write(name);
if (null != m_doctypePublic)
{
writer.write(" PUBLIC \"");
writer.write(m_doctypePublic);
writer.write('\"');
}
if (null != m_doctypeSystem)
{
if (null == m_doctypePublic)
writer.write(" SYSTEM \"");
else
writer.write(" \"");
writer.write(m_doctypeSystem);
if (closeDecl)
{
writer.write("\">");
writer.write(m_lineSep, 0, m_lineSepLen);;
}
else
writer.write('\"');
}
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* 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 org.xml.sax.SAXException
{
try
{
final Writer writer = m_writer;
writer.write("<!ENTITY ");
writer.write(name);
writer.write(" \"");
writer.write(value);
writer.write("\">");
writer.write(m_lineSep, 0, m_lineSepLen);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* Receive notification of the beginning of a document.
*
* @throws org.xml.sax.SAXException Any SAX exception, possibly
* wrapping another exception.
*
* @throws org.xml.sax.SAXException
*/
public void startDocument() throws org.xml.sax.SAXException
{
if (m_inEntityRef)
return;
m_needToOutputDocTypeDecl = true;
m_startNewLine = false;
if (m_shouldNotWriteXMLHeader == false)
{
String encoding = Encodings.getMimeEncoding(m_encoding);
String version = (null == m_version) ? "1.0" : m_version;
String standalone;
if (m_standaloneWasSpecified)
{
standalone = " standalone=\"" + (m_standalone ? "yes" : "no") + "\"";
}
else
{
standalone = "";
}
try
{
final Writer writer = m_writer;
writer.write("<?xml version=\"");
writer.write(version);
writer.write("\" encoding=\"");
writer.write(encoding);
writer.write('\"');
writer.write(standalone);
writer.write("?>");
writer.write(m_lineSep, 0, m_lineSepLen);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
}
/**
* Receive notification of the end of a document.
*
* @throws org.xml.sax.SAXException Any SAX exception, possibly
* wrapping another exception.
*
* @throws org.xml.sax.SAXException
*/
public void endDocument() throws org.xml.sax.SAXException
{
if (m_doIndent &&!m_isprevtext)
{
outputLineSep();
}
flushWriter();
}
/**
* 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
{
m_doctypeSystem = systemId;
m_doctypePublic = publicId;
if ((true == m_needToOutputDocTypeDecl)) // && (null != m_doctypeSystem))
{
outputDocTypeDecl(name, false);
}
m_needToOutputDocTypeDecl = false;
m_inDoctype = true;
}
/**
* 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
{
if (!m_inDoctype)
m_writer.write("]>");
else
{
m_writer.write('>');
}
m_writer.write(m_lineSep, 0, m_lineSepLen);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
// Do nothing for now.
}
/**
* Begin the scope of a prefix-URI Namespace mapping.
* @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{}
/**
* 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{}
/**
* Tell if two strings are equal, without worry if the first string is null.
*
* @param p String reference, which may be null.
* @param t String reference, which may be null.
*
* @return true if strings are equal.
*/
protected static final boolean subPartMatch(String p, String t)
{
return (p == t) || ((null != p) && (p.equals(t)));
}
/**
* Push a boolean state based on if the name of the element
* is found in the list of qnames. A state is always pushed,
* one way or the other.
*
* @param namespaceURI Should be a non-null reference to the namespace URL
* of the element that owns the state, or empty string.
* @param localName Should be a non-null reference to the local name
* of the element that owns the state.
* @param qnames Vector of qualified names of elements, or null.
* @param state The stack where the state should be pushed.
*/
protected void pushState(String namespaceURI, String localName,
Vector qnames, BoolStack state)
{
boolean b;
if (null != qnames)
{
b = false;
if ((null != namespaceURI) && namespaceURI.length() == 0)
namespaceURI = null;
int nElems = qnames.size();
for (int i = 0; i < nElems; i++)
{
QName q = (QName) qnames.elementAt(i);
if (q.getLocalName().equals(localName)
&& subPartMatch(namespaceURI, q.getNamespaceURI()))
{
b = true;
break;
}
}
}
else
{
b = state.peekOrFalse();
}
state.push(b);
}
/**
* Receive notification of the beginning 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.
* @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 (DEBUG)
{
System.out.println("SerializerToXML - startElement: " + namespaceURI
+ ", " + localName);
int n = atts.getLength();
for (int i = 0; i < n; i++)
{
System.out.println("atts[" + i + "]: " + atts.getQName(i) + " = "
+ atts.getValue(i));
}
if (null == namespaceURI)
{
(new RuntimeException(localName
+ " has a null namespace!")).printStackTrace();
}
}
if (m_inEntityRef)
return;
if ((true == m_needToOutputDocTypeDecl) && (null != m_doctypeSystem))
{
outputDocTypeDecl(name, true);
}
m_needToOutputDocTypeDecl = false;
writeParentTagEnd();
pushState(namespaceURI, localName, m_cdataSectionNames,
m_cdataSectionStates);
// pushState(namespaceURI, localName, m_format.getNonEscapingElements(),
// m_disableOutputEscapingStates);
m_ispreserve = false;
// System.out.println(name+": m_doIndent = "+m_doIndent+", m_ispreserve = "+m_ispreserve+", m_isprevtext = "+m_isprevtext);
if (shouldIndent() && m_startNewLine)
{
indent(m_currentIndent);
}
m_startNewLine = true;
try
{
m_writer.write('<');
m_writer.write(name);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
int nAttrs = atts.getLength();
for (int i = 0; i < nAttrs; i++)
{
processAttribute(atts.getQName(i), atts.getValue(i));
}
// Flag the current element as not yet having any children.
openElementForChildren();
m_currentIndent += m_indentAmount;
m_isprevtext = false;
}
/**
* Check to see if a parent's ">" has been written, and, if
* it has not, write it.
*
* @throws org.xml.sax.SAXException
*/
protected void writeParentTagEnd() throws org.xml.sax.SAXException
{
// See if the parent element has already been flagged as having children.
if (!m_elemStack.peekOrTrue())
{
try
{
m_writer.write('>');
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
m_isprevtext = false;
m_elemStack.setTop(true);
m_preserves.push(m_ispreserve);
}
}
/**
* Flag the current element as not yet having any
* children.
*/
protected void openElementForChildren()
{
// Flag the current element as not yet having any children.
m_elemStack.push(false);
}
/**
* Tell if child nodes have been added to the current
* element. Must be called in balance with openElementForChildren().
*
* @return true if child nodes were added.
*/
protected boolean childNodesWereAdded()
{
return m_elemStack.isEmpty() ? false : m_elemStack.pop();
}
/**
* 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;
m_currentIndent -= m_indentAmount;
boolean hasChildNodes = childNodesWereAdded();
try
{
final Writer writer = m_writer;
if (hasChildNodes)
{
if (shouldIndent())
indent(m_currentIndent);
writer.write('<');
writer.write('/');
writer.write(name);
writer.write('>');
}
else
{
if (m_spaceBeforeClose)
writer.write(" />");
else
writer.write("/>");
}
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
if (hasChildNodes)
{
m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
}
m_isprevtext = false;
// m_disableOutputEscapingStates.pop();
m_cdataSectionStates.pop();
}
/**
* Process an attribute.
* @param name The name of the attribute.
* @param value The value of the attribute.
*
* @throws org.xml.sax.SAXException
*/
protected void processAttribute(String name, String value)
throws org.xml.sax.SAXException
{
try
{
final Writer writer = m_writer;
writer.write(' ');
writer.write(name);
writer.write("=\"");
writeAttrString(value, m_encoding);
writer.write('\"');
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* 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);
}
/**
* Ends an un-escaping section.
*
* @see #startNonEscaping
*
* @throws org.xml.sax.SAXException
*/
public void endNonEscaping() throws org.xml.sax.SAXException
{
m_disableOutputEscapingStates.pop();
}
/**
* Starts a whitespace preserving section. All characters printed
* within a preserving section are printed without indentation and
* without consolidating multiple spaces. This is equivalent to
* the <tt>xml:space=&quot;preserve&quot;</tt> attribute. Only XML
* and HTML serializers need to support this method.
* <p>
* The contents of the whitespace preserving section will be delivered
* through the regular <tt>characters</tt> event.
*
* @throws org.xml.sax.SAXException
*/
public void startPreserving() throws org.xml.sax.SAXException
{
// Not sure this is really what we want. -sb
m_preserves.push(true);
m_ispreserve = true;
}
/**
* Ends a whitespace preserving section.
*
* @see #startPreserving
*
* @throws org.xml.sax.SAXException
*/
public void endPreserving() throws org.xml.sax.SAXException
{
// Not sure this is really what we want. -sb
m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
}
/**
* Receive notification of a processing instruction.
*
* @param target The processing instruction target.
* @param data The processing instruction data, or null if
* none was supplied.
* @throws org.xml.sax.SAXException Any SAX exception, possibly
* wrapping another exception.
*
* @throws org.xml.sax.SAXException
*/
public void processingInstruction(String target, String data)
throws org.xml.sax.SAXException
{
if (m_inEntityRef)
return;
if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING))
{
startNonEscaping();
}
else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING))
{
endNonEscaping();
}
else
{
try
{
final Writer writer = m_writer;
writeParentTagEnd();
if (shouldIndent())
indent(m_currentIndent);
writer.write('<');
writer.write('?');
writer.write(target);
if (data.length() > 0 &&!Character.isSpaceChar(data.charAt(0)))
writer.write(' ');
int indexOfQLT = data.indexOf("?>");
if (indexOfQLT >= 0)
{
// See XSLT spec on error recovery of "?>" in PIs.
if (indexOfQLT > 0)
{
writer.write(data.substring(0, indexOfQLT));
}
writer.write("? >"); // add space between.
if ((indexOfQLT + 2) < data.length())
{
writer.write(data.substring(indexOfQLT + 2));
}
}
else
{
writer.write(data);
}
writer.write('?');
writer.write('>');
// Always output a newline char if not inside of an
// element. The whitespace is not significant in that
// case.
if (m_elemStack.isEmpty())
writer.write(m_lineSep, 0, m_lineSepLen);
m_startNewLine = true;
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
}
/**
* Report 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
{
if (m_inEntityRef)
return;
writeParentTagEnd();
if (shouldIndent())
indent(m_currentIndent);
try
{
final Writer writer = m_writer;
writer.write("<!--");
writer.write(ch, start, length);
writer.write("-->");
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
m_startNewLine = true;
}
/**
* 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_inCData = true;
}
/**
* 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
{
m_inCData = false;
}
/**
* 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
*/
public void cdata(char ch[], int start, int length)
throws org.xml.sax.SAXException
{
try
{
writeParentTagEnd();
m_ispreserve = true;
if (shouldIndent())
indent(m_currentIndent);
boolean writeCDataBrackets = (((length >= 1) && canConvert(ch[start])));
if (writeCDataBrackets)
{
m_writer.write("<![CDATA[");
}
// m_writer.write(ch, start, length);
if (isEscapingDisabled())
{
charactersRaw(ch, start, length);
}
else
writeNormalizedChars(ch, start, length, true);
if (writeCDataBrackets)
{
m_writer.write("]]>");
}
}
catch (IOException ioe)
{
throw new org.xml.sax.SAXException(
XSLMessages.createXPATHMessage(XPATHErrorResources.ER_OIERROR, null),
ioe); //"IO error", ioe);
}
}
/** The current position in the m_charBuf or m_byteBuf. */
protected int m_pos = 0;
/**
* Append a character to the buffer.
*
* @param b byte to be written to result stream.
*
* @throws org.xml.sax.SAXException
*/
protected final void accum(char b) throws org.xml.sax.SAXException
{
try
{
m_writer.write(b);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* Append a character to the buffer.
*
* @param chars non-null reference to character array.
* @param start Start of characters to be written.
* @param length Number of characters to be written.
*
* @throws org.xml.sax.SAXException
*/
protected final void accum(char chars[], int start, int length)
throws org.xml.sax.SAXException
{
try
{
m_writer.write(chars, start, length);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* Append a character to the buffer.
*
* @param s non-null reference to string to be written to the character buffer.
*
* @throws org.xml.sax.SAXException
*/
protected final void accum(String s) throws org.xml.sax.SAXException
{
try
{
m_writer.write(s);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* Flush the formatter's result stream.
*
* @throws org.xml.sax.SAXException
*/
public final void flushWriter() throws org.xml.sax.SAXException
{
if (null != m_writer)
{
try
{
if (m_writer instanceof WriterToUTF8Buffered)
{
if(m_shouldFlush)
((WriterToUTF8Buffered) m_writer).flush();
else
((WriterToUTF8Buffered) m_writer).flushBuffer();
}
if (m_writer instanceof WriterToUTF8)
{
if(m_shouldFlush)
m_writer.flush();
}
else if (m_writer instanceof WriterToASCI)
{
if(m_shouldFlush)
m_writer.flush();
}
else
{
// Flush always.
// Not a great thing if the writer was created
// by this class, but don't have a choice.
m_writer.flush();
}
}
catch (IOException ioe)
{
throw new org.xml.sax.SAXException(ioe);
}
}
}
/**
* 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(char chars[], int start, int length)
throws org.xml.sax.SAXException
{
if(0 == length)
return;
// if (m_inEntityRef)
// return;
// else
if (m_inCData || m_cdataSectionStates.peekOrFalse())
{
cdata(chars, start, length);
return;
}
try
{
if (m_disableOutputEscapingStates.peekOrFalse())
{
charactersRaw(chars, start, length);
return;
}
final Writer writer = m_writer;
if (!m_elemStack.peekOrTrue())
{
writer.write('>');
m_isprevtext = false;
m_elemStack.setTop(true);
m_preserves.push(m_ispreserve);
}
int startClean = start;
int lengthClean = 0;
// int pos = 0;
int end = start + length;
boolean checkWhite = true;
final int maxCharacter = m_maxCharacter;
final BitSet specialsMap = m_charInfo.m_specialsMap;
for (int i = start; i < end; i++)
{
char ch = chars[i];
if (checkWhite
&& ((ch > 0x20)
||!((ch == 0x20) || (ch == 0x09) || (ch == 0xD)
|| (ch == 0xA))))
{
m_ispreserve = true;
checkWhite = false;
}
if ((canConvert(ch) && (!specialsMap.get(ch))) || ('"' == ch))
{
lengthClean++;
}
else
{
if (lengthClean > 0)
{
writer.write(chars, startClean, lengthClean);
lengthClean = 0;
}
if (CharInfo.S_LINEFEED == ch)
{
writer.write(m_lineSep, 0, m_lineSepLen);
startClean = i + 1;
}
else
{
startClean = accumDefaultEscape(ch, i, chars, end, false);
i = startClean - 1;
}
}
}
if (lengthClean > 0)
{
writer.write(chars, startClean, lengthClean);
}
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
m_isprevtext = true;
}
/**
* 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
*/
public void charactersRaw(char ch[], int start, int length)
throws org.xml.sax.SAXException
{
try
{
if (m_inEntityRef)
return;
writeParentTagEnd();
m_ispreserve = true;
m_writer.write(ch, start, length);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* 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;
}
/**
* Once a surrogate has been detected, get the pair as a single
* integer value.
*
* @param c the first part of the surrogate.
* @param ch Character array.
* @param i position Where the surrogate was detected.
* @param end The end index of the significant characters.
* @return i+1.
* @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected.
*/
int getURF16SurrogateValue(char c, char ch[], int i, int end)
throws org.xml.sax.SAXException
{
int next;
if (i + 1 >= end)
{
throw new org.xml.sax.SAXException(
XSLMessages.createXPATHMessage(
XPATHErrorResources.ER_INVALID_UTF16_SURROGATE,
new Object[]{ Integer.toHexString((int) c) })); //"Invalid UTF-16 surrogate detected: "
//+Integer.toHexString((int)c)+ " ?");
}
else
{
next = ch[++i];
if (!(0xdc00 <= next && next < 0xe000))
throw new org.xml.sax.SAXException(
XSLMessages.createXPATHMessage(
XPATHErrorResources.ER_INVALID_UTF16_SURROGATE,
new Object[]{
Integer.toHexString((int) c) + " "
+ Integer.toHexString(next) })); //"Invalid UTF-16 surrogate detected: "
//+Integer.toHexString((int)c)+" "+Integer.toHexString(next));
next = ((c - 0xd800) << 10) + next - 0xdc00 + 0x00010000;
}
return next;
}
/**
* Once a surrogate has been detected, write the pair as a single
* character reference.
*
* @param c the first part of the surrogate.
* @param ch Character array.
* @param i position Where the surrogate was detected.
* @param end The end index of the significant characters.
* @return i+1.
* @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, org.xml.sax.SAXException
{
// UTF-16 surrogate
int surrogateValue = getURF16SurrogateValue(c, ch, i, end);
i++;
m_writer.write('&');
m_writer.write('#');
// m_writer.write('x');
m_writer.write(Integer.toString(surrogateValue));
m_writer.write(';');
return i;
}
/**
* 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.
*
* @throws IOException
* @throws org.xml.sax.SAXException
*/
void writeNormalizedChars(char ch[], int start, int length, boolean isCData)
throws IOException, org.xml.sax.SAXException
{
int end = start + length;
for (int i = start; i < end; i++)
{
char c = ch[i];
if (CharInfo.S_LINEFEED == c)
{
m_writer.write(m_lineSep, 0, m_lineSepLen);
}
else if (isCData && (!canConvert(c)))
{
if (i != 0)
m_writer.write("]]>");
// This needs to go into a function...
if (isUTF16Surrogate(c))
{
i = writeUTF16Surrogate(c, ch, i, end);
}
else
{
m_writer.write("&#");
String intStr = Integer.toString((int) c);
m_writer.write(intStr);
m_writer.write(';');
}
if ((i != 0) && (i < (end - 1)))
m_writer.write("<![CDATA[");
}
else if (isCData
&& ((i < (end - 2)) && (']' == c) && (']' == ch[i + 1])
&& ('>' == ch[i + 2])))
{
m_writer.write("]]]]><![CDATA[>");
i += 2;
}
else
{
if (canConvert(c))
{
m_writer.write(c);
}
// This needs to go into a function...
else if (isUTF16Surrogate(c))
{
i = writeUTF16Surrogate(c, ch, i, end);
}
else
{
m_writer.write("&#");
String intStr = Integer.toString((int) c);
m_writer.write(intStr);
m_writer.write(';');
}
}
}
}
/**
* 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 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;
m_inEntityRef = true;
}
/**
* Report the end of an entity.
*
* @param name The name of the entity that is ending.
* @throws org.xml.sax.SAXException The application may raise an exception.
* @see #startEntity
*/
public void endEntity(String name) throws org.xml.sax.SAXException
{
if (name.equals("[dtd]"))
m_inExternalDTD = false;
m_inEntityRef = false;
}
/**
* Receive notivication of a entityReference.
*
* @param name The name of the entity.
*
* @throws org.xml.sax.SAXException
*/
public void entityReference(String name) throws org.xml.sax.SAXException
{
writeParentTagEnd();
if (shouldIndent())
indent(m_currentIndent);
try
{
final Writer writer = m_writer;
writer.write("&");
writer.write(name);
writer.write(";");
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
// 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 Writer writer = m_writer;
if (m_inDoctype)
{
writer.write(" [");
writer.write(m_lineSep, 0, m_lineSepLen);
m_inDoctype = false;
}
writer.write("<!ELEMENT ");
writer.write(name);
writer.write(' ');
writer.write(model);
writer.write('>');
writer.write(m_lineSep, 0, m_lineSepLen);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/** NEEDSDOC Field m_elemName */
private String m_elemName = "";
/**
* 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 Writer writer = m_writer;
if (m_inDoctype)
{
writer.write(" [");
writer.write(m_lineSep, 0, m_lineSepLen);
m_inDoctype = false;
}
writer.write("<!ATTLIST ");
writer.write(eName);
writer.write(" ");
writer.write(aName);
writer.write(" ");
writer.write(type);
if (valueDefault != null)
{
writer.write(" ");
writer.write(valueDefault);
}
//m_writer.write(" ");
//m_writer.write(value);
writer.write(">");
writer.write(m_lineSep, 0, m_lineSepLen);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* 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
{
if (m_inDoctype)
{
m_writer.write(" [");
m_writer.write(m_lineSep, 0, m_lineSepLen);
m_inDoctype = false;
}
outputEntityDecl(name, value);
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* 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{}
/**
* 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 escLF true if the linefeed should be escaped.
*
* @return i+1 if the character was written, else i.
*
* @throws org.xml.sax.SAXException
*/
protected int accumDefaultEntity(
char ch, int i, char[] chars, int len, boolean escLF)
throws org.xml.sax.SAXException
{
try
{
if (!escLF && CharInfo.S_LINEFEED == ch)
{
m_writer.write(m_lineSep, 0, m_lineSepLen);
}
else
{
if (m_charInfo.isSpecial(ch))
{
String entityRef = m_charInfo.getEntityNameForChar(ch);
if (null != entityRef)
{
final Writer writer = m_writer;
writer.write('&');
writer.write(entityRef);
writer.write(';');
}
else
return i;
}
else
return i;
}
return i + 1;
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* Escape and m_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 escLF true if the linefeed should be escaped.
*
* @return i+1 if the character was written, else i.
*
* @throws org.xml.sax.SAXException
*/
protected int accumDefaultEscape(
char ch, int i, char[] chars, int len, boolean escLF)
throws org.xml.sax.SAXException
{
int pos = accumDefaultEntity(ch, i, chars, len, escLF);
if (i == pos)
{
pos++;
try
{
if (0xd800 <= ch && ch < 0xdc00)
{
// UTF-16 surrogate
int next;
if (i + 1 >= len)
{
throw new org.xml.sax.SAXException(
XSLMessages.createXPATHMessage(
XPATHErrorResources.ER_INVALID_UTF16_SURROGATE,
new Object[]{ Integer.toHexString(ch) })); //"Invalid UTF-16 surrogate detected: "
//+Integer.toHexString(ch)+ " ?");
}
else
{
next = chars[++i];
if (!(0xdc00 <= next && next < 0xe000))
throw new org.xml.sax.SAXException(
XSLMessages.createXPATHMessage(
XPATHErrorResources.ER_INVALID_UTF16_SURROGATE,
new Object[]{
Integer.toHexString(ch) + " "
+ Integer.toHexString(next) })); //"Invalid UTF-16 surrogate detected: "
//+Integer.toHexString(ch)+" "+Integer.toHexString(next));
next = ((ch - 0xd800) << 10) + next - 0xdc00 + 0x00010000;
}
m_writer.write("&#");
m_writer.write(Integer.toString(next));
m_writer.write(";");
/*} else if (null != ctbc && !ctbc.canConvert(ch)) {
sb.append("&#x");
sb.append(Integer.toString((int)ch, 16));
sb.append(";");*/
}
else
{
if (!canConvert(ch) || (m_charInfo.isSpecial(ch)))
{
m_writer.write("&#");
m_writer.write(Integer.toString(ch));
m_writer.write(";");
}
else
{
m_writer.write(ch);
}
}
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
return pos;
}
/**
* Opaque reference to the sun.io.CharToByteConverter for this
* encoding.
*/
Object m_charToByteConverter = 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;
/**
* Tell if this character can be written without escaping.
*/
public boolean canConvert(char ch)
{
if(ch < 127)
{
if(ch >= 0x20 || (0x0A == ch || 0x0D == ch || 0x09 == ch) )
return true;
else
return false;
}
if(null == m_charToByteConverter && false == m_triedToGetConverter)
{
m_triedToGetConverter = true;
try
{
m_charToByteConverter = Encodings.getCharToByteConverter(m_encoding);
if(null != m_charToByteConverter)
{
Class argsTypes[] = new Class[1];
argsTypes[0] = Character.TYPE;
Class convClass = m_charToByteConverter.getClass();
m_canConvertMeth = convClass.getMethod("canConvert", argsTypes);
}
}
catch(Exception e)
{
// This is just an assert: no action at the moment.
System.err.println("Warning: "+e.getMessage());
}
}
if(null != m_charToByteConverter)
{
try
{
Object args[] = new Object[1];
args[0] = new Character( ch );
Boolean bool
= (Boolean)m_canConvertMeth.invoke(m_charToByteConverter, args);
return bool.booleanValue() ? !Character.isISOControl(ch) : false;
}
catch(java.lang.reflect.InvocationTargetException ite)
{
// This is just an assert: no action at the moment.
System.err.println("Warning: InvocationTargetException in canConvert!");
}
catch(java.lang.IllegalAccessException iae)
{
// This is just an assert: no action at the moment.
System.err.println("Warning: IllegalAccessException in canConvert!");
}
}
// fallback!
return ( ch <= m_maxCharacter );
}
/**
* Returns the specified <var>string</var> after substituting <VAR>specials</VAR>,
* and UTF-16 surrogates for chracter references <CODE>&amp;#xnn</CODE>.
*
* @param string String to convert to XML format.
* @param encoding CURRENTLY NOT IMPLEMENTED.
*
* @throws org.xml.sax.SAXException
*/
public void writeAttrString(String string, String encoding)
throws org.xml.sax.SAXException
{
try
{
final char[] stringChars = string.toCharArray();
final int len = stringChars.length;
final Writer writer = m_writer;
for (int i = 0; i < len; i++)
{
char ch = stringChars[i];
if (canConvert(ch) && (!m_charInfo.isSpecial(ch)))
{
writer.write(ch);
}
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;
}
accumDefaultEscape(ch, i, stringChars, len, true);
}
}
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* 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);
}
/**
* Prints <var>n</var> spaces.
* @param pw The character output stream to use.
* @param n Number of spaces to print.
*
* @throws org.xml.sax.SAXException if an error occurs when writing.
*/
public void printSpace(int n) throws org.xml.sax.SAXException
{
try
{
for (int i = 0; i < n; i++)
{
m_writer.write(' ');
}
}
catch(IOException ioe)
{
throw new SAXException(ioe);
}
}
/**
* Prints a newline character and <var>n</var> spaces.
* @param pw The character output stream to use.
* @param n Number of spaces to print.
*
* @throws org.xml.sax.SAXException if an error occurs during writing.
*/
public void indent(int n) throws org.xml.sax.SAXException
{
if (m_startNewLine)
outputLineSep();
if (m_doIndent)
{
printSpace(n);
}
}
/**
* 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
{
init(output, m_format);
}
catch (UnsupportedEncodingException uee)
{
// Should have been warned in init, I guess...
}
}
/**
* 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 WriterToUTF8)
return ((WriterToUTF8) m_writer).getOutputStream();
else if (m_writer instanceof WriterToASCI)
return ((WriterToASCI) m_writer).getOutputStream();
else
return null;
}
/**
* 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)
{
m_writer = writer;
}
/**
* 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;
}
/**
* 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);
m_shouldFlush = shouldFlush;
}
/**
* Returns the output format for this serializer.
*
* @return The output format in use
*/
public Properties getOutputFormat()
{
return m_format;
}
/**
* Return a {@link ContentHandler} interface into this serializer.
* If the serializer does not support the {@link ContentHandler}
* interface, it should return null.
*
* @return A {@link ContentHandler} interface into this serializer,
* or null if the serializer is not SAX 2 capable
* @throws IOException An I/O exception occured
*/
public ContentHandler asContentHandler() throws IOException
{
return this;
}
/**
* Return a {@link DOMSerializer} interface into this serializer.
* If the serializer does not support the {@link DOMSerializer}
* interface, it should return null.
*
* @return A {@link DOMSerializer} interface into this serializer,
* or null if the serializer is not DOM capable
* @throws IOException An I/O exception occured
*/
public DOMSerializer asDOMSerializer() throws IOException
{
return this; // for now
}
/**
* Resets the serializer. If this method returns true, the
* serializer may be used for subsequent serialization of new
* documents. It is possible to change the output format and
* output stream prior to serializing, or to use the existing
* output format and output stream.
*
* @return True if serializer has been reset and can be reused
*/
public boolean reset()
{
return false;
}
/**
* Serializes the DOM node. Throws an exception only if an I/O
* exception occured while serializing.
*
* @param elem The element to serialize
*
* @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,
new org.apache.xpath.DOM2Helper());
walker.traverse(node);
}
catch (org.xml.sax.SAXException se)
{
throw new WrappedRuntimeException(se);
}
}
} //ToXMLStringVisitor