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