org.dom4j.io.SAXWriter Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
*
* This software is open source.
* See the bottom of this file for the licence.
*/
package org.dom4j.io;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.dom4j.Attribute;
import org.dom4j.Branch;
import org.dom4j.CDATA;
import org.dom4j.CharacterData;
import org.dom4j.Comment;
import org.dom4j.Document;
import org.dom4j.DocumentType;
import org.dom4j.Element;
import org.dom4j.Entity;
import org.dom4j.Namespace;
import org.dom4j.Node;
import org.dom4j.ProcessingInstruction;
import org.dom4j.Text;
import org.dom4j.tree.NamespaceStack;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.LocatorImpl;
/**
*
* SAXWriter
writes a DOM4J tree to a SAX ContentHandler.
*
*
* @author James Strachan
* @version $Revision: 1.24 $
*/
public class SAXWriter implements XMLReader {
protected static final String[] LEXICAL_HANDLER_NAMES = {
"http://xml.org/sax/properties/lexical-handler",
"http://xml.org/sax/handlers/LexicalHandler" };
protected static final String FEATURE_NAMESPACE_PREFIXES
= "http://xml.org/sax/features/namespace-prefixes";
protected static final String FEATURE_NAMESPACES
= "http://xml.org/sax/features/namespaces";
/** ContentHandler
to which SAX events are raised */
private ContentHandler contentHandler;
/** DTDHandler
fired when a document has a DTD */
private DTDHandler dtdHandler;
/** EntityResolver
fired when a document has a DTD */
private EntityResolver entityResolver;
private ErrorHandler errorHandler;
/** LexicalHandler
fired on Entity and CDATA sections */
private LexicalHandler lexicalHandler;
/** AttributesImpl
used when generating the Attributes */
private AttributesImpl attributes = new AttributesImpl();
/** Stores the features */
private Map features = new HashMap();
/** Stores the properties */
private Map properties = new HashMap();
/** Whether namespace declarations are exported as attributes or not */
private boolean declareNamespaceAttributes;
public SAXWriter() {
properties.put(FEATURE_NAMESPACE_PREFIXES, Boolean.FALSE);
properties.put(FEATURE_NAMESPACE_PREFIXES, Boolean.TRUE);
}
public SAXWriter(ContentHandler contentHandler) {
this();
this.contentHandler = contentHandler;
}
public SAXWriter(ContentHandler contentHandler,
LexicalHandler lexicalHandler) {
this();
this.contentHandler = contentHandler;
this.lexicalHandler = lexicalHandler;
}
public SAXWriter(ContentHandler contentHandler,
LexicalHandler lexicalHandler, EntityResolver entityResolver) {
this();
this.contentHandler = contentHandler;
this.lexicalHandler = lexicalHandler;
this.entityResolver = entityResolver;
}
/**
* A polymorphic method to write any Node to this SAX stream
*
* @param node
* DOCUMENT ME!
*
* @throws SAXException
* DOCUMENT ME!
*/
public void write(Node node) throws SAXException {
int nodeType = node.getNodeType();
switch (nodeType) {
case Node.ELEMENT_NODE:
write((Element) node);
break;
case Node.ATTRIBUTE_NODE:
write((Attribute) node);
break;
case Node.TEXT_NODE:
write(node.getText());
break;
case Node.CDATA_SECTION_NODE:
write((CDATA) node);
break;
case Node.ENTITY_REFERENCE_NODE:
write((Entity) node);
break;
case Node.PROCESSING_INSTRUCTION_NODE:
write((ProcessingInstruction) node);
break;
case Node.COMMENT_NODE:
write((Comment) node);
break;
case Node.DOCUMENT_NODE:
write((Document) node);
break;
case Node.DOCUMENT_TYPE_NODE:
write((DocumentType) node);
break;
case Node.NAMESPACE_NODE:
// Will be output with attributes
// write((Namespace) node);
break;
default:
throw new SAXException("Invalid node type: " + node);
}
}
/**
* Generates SAX events for the given Document and all its content
*
* @param document
* is the Document to parse
*
* @throws SAXException
* if there is a SAX error processing the events
*/
public void write(Document document) throws SAXException {
if (document != null) {
checkForNullHandlers();
documentLocator(document);
startDocument();
entityResolver(document);
dtdHandler(document);
writeContent(document, new NamespaceStack());
endDocument();
}
}
/**
* Generates SAX events for the given Element and all its content
*
* @param element
* is the Element to parse
*
* @throws SAXException
* if there is a SAX error processing the events
*/
public void write(Element element) throws SAXException {
write(element, new NamespaceStack());
}
/**
*
* Writes the opening tag of an {@link Element}, including its {@link
* Attribute}s but without its content.
*
*
* @param element
* Element
to output.
*
* @throws SAXException
* DOCUMENT ME!
*/
public void writeOpen(Element element) throws SAXException {
startElement(element, null);
}
/**
*
* Writes the closing tag of an {@link Element}
*
*
* @param element
* Element
to output.
*
* @throws SAXException
* DOCUMENT ME!
*/
public void writeClose(Element element) throws SAXException {
endElement(element);
}
/**
* Generates SAX events for the given text
*
* @param text
* is the text to send to the SAX ContentHandler
*
* @throws SAXException
* if there is a SAX error processing the events
*/
public void write(String text) throws SAXException {
if (text != null) {
char[] chars = text.toCharArray();
contentHandler.characters(chars, 0, chars.length);
}
}
/**
* Generates SAX events for the given CDATA
*
* @param cdata
* is the CDATA to parse
*
* @throws SAXException
* if there is a SAX error processing the events
*/
public void write(CDATA cdata) throws SAXException {
String text = cdata.getText();
if (lexicalHandler != null) {
lexicalHandler.startCDATA();
write(text);
lexicalHandler.endCDATA();
} else {
write(text);
}
}
/**
* Generates SAX events for the given Comment
*
* @param comment
* is the Comment to parse
*
* @throws SAXException
* if there is a SAX error processing the events
*/
public void write(Comment comment) throws SAXException {
if (lexicalHandler != null) {
String text = comment.getText();
char[] chars = text.toCharArray();
lexicalHandler.comment(chars, 0, chars.length);
}
}
/**
* Generates SAX events for the given Entity
*
* @param entity
* is the Entity to parse
*
* @throws SAXException
* if there is a SAX error processing the events
*/
public void write(Entity entity) throws SAXException {
String text = entity.getText();
if (lexicalHandler != null) {
String name = entity.getName();
lexicalHandler.startEntity(name);
write(text);
lexicalHandler.endEntity(name);
} else {
write(text);
}
}
/**
* Generates SAX events for the given ProcessingInstruction
*
* @param pi
* is the ProcessingInstruction to parse
*
* @throws SAXException
* if there is a SAX error processing the events
*/
public void write(ProcessingInstruction pi) throws SAXException {
String target = pi.getTarget();
String text = pi.getText();
contentHandler.processingInstruction(target, text);
}
/**
* Should namespace declarations be converted to "xmlns" attributes. This
* property defaults to false
as per the SAX specification.
* This property is set via the SAX feature
* "http://xml.org/sax/features/namespace-prefixes"
*
* @return DOCUMENT ME!
*/
public boolean isDeclareNamespaceAttributes() {
return declareNamespaceAttributes;
}
/**
* Sets whether namespace declarations should be exported as "xmlns"
* attributes or not. This property is set from the SAX feature
* "http://xml.org/sax/features/namespace-prefixes"
*
* @param declareNamespaceAttrs
* DOCUMENT ME!
*/
public void setDeclareNamespaceAttributes(boolean declareNamespaceAttrs) {
this.declareNamespaceAttributes = declareNamespaceAttrs;
}
// XMLReader methods
// -------------------------------------------------------------------------
/**
* DOCUMENT ME!
*
* @return the ContentHandler
called when SAX events are
* raised
*/
public ContentHandler getContentHandler() {
return contentHandler;
}
/**
* Sets the ContentHandler
called when SAX events are raised
*
* @param contentHandler
* is the ContentHandler
called when SAX events
* are raised
*/
public void setContentHandler(ContentHandler contentHandler) {
this.contentHandler = contentHandler;
}
/**
* DOCUMENT ME!
*
* @return the DTDHandler
*/
public DTDHandler getDTDHandler() {
return dtdHandler;
}
/**
* Sets the DTDHandler
.
*
* @param handler
* DOCUMENT ME!
*/
public void setDTDHandler(DTDHandler handler) {
this.dtdHandler = handler;
}
/**
* DOCUMENT ME!
*
* @return the ErrorHandler
*/
public ErrorHandler getErrorHandler() {
return errorHandler;
}
/**
* Sets the ErrorHandler
.
*
* @param errorHandler
* DOCUMENT ME!
*/
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
/**
* DOCUMENT ME!
*
* @return the EntityResolver
used when a Document contains a
* DTD
*/
public EntityResolver getEntityResolver() {
return entityResolver;
}
/**
* Sets the EntityResolver
.
*
* @param entityResolver
* is the EntityResolver
*/
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;
}
/**
* DOCUMENT ME!
*
* @return the LexicalHandler
used when a Document contains a
* DTD
*/
public LexicalHandler getLexicalHandler() {
return lexicalHandler;
}
/**
* Sets the LexicalHandler
.
*
* @param lexicalHandler
* is the LexicalHandler
*/
public void setLexicalHandler(LexicalHandler lexicalHandler) {
this.lexicalHandler = lexicalHandler;
}
/**
* Sets the XMLReader
used to write SAX events to
*
* @param xmlReader
* is the XMLReader
*/
public void setXMLReader(XMLReader xmlReader) {
setContentHandler(xmlReader.getContentHandler());
setDTDHandler(xmlReader.getDTDHandler());
setEntityResolver(xmlReader.getEntityResolver());
setErrorHandler(xmlReader.getErrorHandler());
}
/**
* Looks up the value of a feature.
*
* @param name
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws SAXNotRecognizedException
* DOCUMENT ME!
* @throws SAXNotSupportedException
* DOCUMENT ME!
*/
public boolean getFeature(String name) throws SAXNotRecognizedException,
SAXNotSupportedException {
Boolean answer = (Boolean) features.get(name);
return (answer != null) && answer.booleanValue();
}
/**
* This implementation does actually use any features but just stores them
* for later retrieval
*
* @param name
* DOCUMENT ME!
* @param value
* DOCUMENT ME!
*
* @throws SAXNotRecognizedException
* DOCUMENT ME!
* @throws SAXNotSupportedException
* DOCUMENT ME!
*/
public void setFeature(String name, boolean value)
throws SAXNotRecognizedException, SAXNotSupportedException {
if (FEATURE_NAMESPACE_PREFIXES.equals(name)) {
setDeclareNamespaceAttributes(value);
} else if (FEATURE_NAMESPACE_PREFIXES.equals(name)) {
if (!value) {
String msg = "Namespace feature is always supported in dom4j";
throw new SAXNotSupportedException(msg);
}
}
features.put(name, (value) ? Boolean.TRUE : Boolean.FALSE);
}
/**
* Sets the given SAX property
*
* @param name
* DOCUMENT ME!
* @param value
* DOCUMENT ME!
*/
public void setProperty(String name, Object value) {
for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
setLexicalHandler((LexicalHandler) value);
return;
}
}
properties.put(name, value);
}
/**
* Gets the given SAX property
*
* @param name
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws SAXNotRecognizedException
* DOCUMENT ME!
* @throws SAXNotSupportedException
* DOCUMENT ME!
*/
public Object getProperty(String name) throws SAXNotRecognizedException,
SAXNotSupportedException {
for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
return getLexicalHandler();
}
}
return properties.get(name);
}
/**
* This method is not supported.
*
* @param systemId
* DOCUMENT ME!
*
* @throws SAXNotSupportedException
* DOCUMENT ME!
*/
public void parse(String systemId) throws SAXNotSupportedException {
throw new SAXNotSupportedException("This XMLReader can only accept"
+ " InputSource objects");
}
/**
* Parses an XML document. This method can only accept DocumentInputSource
* inputs otherwise a {@link SAXNotSupportedException}exception is thrown.
*
* @param input
* DOCUMENT ME!
*
* @throws SAXException
* DOCUMENT ME!
* @throws SAXNotSupportedException
* if the input source is not wrapping a dom4j document
*/
public void parse(InputSource input) throws SAXException {
if (input instanceof DocumentInputSource) {
DocumentInputSource documentInput = (DocumentInputSource) input;
Document document = documentInput.getDocument();
write(document);
} else {
throw new SAXNotSupportedException(
"This XMLReader can only accept "
+ " InputSource objects");
}
}
// Implementation methods
// -------------------------------------------------------------------------
protected void writeContent(Branch branch, NamespaceStack namespaceStack)
throws SAXException {
for (Iterator iter = branch.nodeIterator(); iter.hasNext();) {
Object object = iter.next();
if (object instanceof Element) {
write((Element) object, namespaceStack);
} else if (object instanceof CharacterData) {
if (object instanceof Text) {
Text text = (Text) object;
write(text.getText());
} else if (object instanceof CDATA) {
write((CDATA) object);
} else if (object instanceof Comment) {
write((Comment) object);
} else {
throw new SAXException("Invalid Node in DOM4J content: "
+ object + " of type: " + object.getClass());
}
} else if (object instanceof String) {
write((String) object);
} else if (object instanceof Entity) {
write((Entity) object);
} else if (object instanceof ProcessingInstruction) {
write((ProcessingInstruction) object);
} else if (object instanceof Namespace) {
write((Namespace) object);
} else {
throw new SAXException("Invalid Node in DOM4J content: "
+ object);
}
}
}
/**
* The {@link org.xml.sax.Locator}is only really useful when parsing a
* textual document as its main purpose is to identify the line and column
* number. Since we are processing an in memory tree which will probably
* have its line number information removed, we'll just use -1 for the line
* and column numbers.
*
* @param document
* DOCUMENT ME!
*
* @throws SAXException
* DOCUMENT ME!
*/
protected void documentLocator(Document document) throws SAXException {
LocatorImpl locator = new LocatorImpl();
String publicID = null;
String systemID = null;
DocumentType docType = document.getDocType();
if (docType != null) {
publicID = docType.getPublicID();
systemID = docType.getSystemID();
}
if (publicID != null) {
locator.setPublicId(publicID);
}
if (systemID != null) {
locator.setSystemId(systemID);
}
locator.setLineNumber(-1);
locator.setColumnNumber(-1);
contentHandler.setDocumentLocator(locator);
}
protected void entityResolver(Document document) throws SAXException {
if (entityResolver != null) {
DocumentType docType = document.getDocType();
if (docType != null) {
String publicID = docType.getPublicID();
String systemID = docType.getSystemID();
if ((publicID != null) || (systemID != null)) {
try {
entityResolver.resolveEntity(publicID, systemID);
} catch (IOException e) {
throw new SAXException("Could not resolve publicID: "
+ publicID + " systemID: " + systemID, e);
}
}
}
}
}
/**
* We do not yet support DTD or XML Schemas so this method does nothing
* right now.
*
* @param document
* DOCUMENT ME!
*
* @throws SAXException
* DOCUMENT ME!
*/
protected void dtdHandler(Document document) throws SAXException {
}
protected void startDocument() throws SAXException {
contentHandler.startDocument();
}
protected void endDocument() throws SAXException {
contentHandler.endDocument();
}
protected void write(Element element, NamespaceStack namespaceStack)
throws SAXException {
int stackSize = namespaceStack.size();
AttributesImpl namespaceAttributes = startPrefixMapping(element,
namespaceStack);
startElement(element, namespaceAttributes);
writeContent(element, namespaceStack);
endElement(element);
endPrefixMapping(namespaceStack, stackSize);
}
/**
* Fires a SAX startPrefixMapping event for all the namespaceStack which
* have just come into scope
*
* @param element
* DOCUMENT ME!
* @param namespaceStack
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws SAXException
* DOCUMENT ME!
*/
protected AttributesImpl startPrefixMapping(Element element,
NamespaceStack namespaceStack) throws SAXException {
AttributesImpl namespaceAttributes = null;
// start with the namespace of the element
Namespace elementNamespace = element.getNamespace();
if ((elementNamespace != null)
&& !isIgnoreableNamespace(elementNamespace, namespaceStack)) {
namespaceStack.push(elementNamespace);
contentHandler.startPrefixMapping(elementNamespace.getPrefix(),
elementNamespace.getURI());
namespaceAttributes = addNamespaceAttribute(namespaceAttributes,
elementNamespace);
}
List declaredNamespaces = element.declaredNamespaces();
for (int i = 0, size = declaredNamespaces.size(); i < size; i++) {
Namespace namespace = (Namespace) declaredNamespaces.get(i);
if (!isIgnoreableNamespace(namespace, namespaceStack)) {
namespaceStack.push(namespace);
contentHandler.startPrefixMapping(namespace.getPrefix(),
namespace.getURI());
namespaceAttributes = addNamespaceAttribute(
namespaceAttributes, namespace);
}
}
return namespaceAttributes;
}
/**
* Fires a SAX endPrefixMapping event for all the namespaceStack which have
* gone out of scope
*
* @param stack
* DOCUMENT ME!
* @param stackSize
* DOCUMENT ME!
*
* @throws SAXException
* DOCUMENT ME!
*/
protected void endPrefixMapping(NamespaceStack stack, int stackSize)
throws SAXException {
while (stack.size() > stackSize) {
Namespace namespace = stack.pop();
if (namespace != null) {
contentHandler.endPrefixMapping(namespace.getPrefix());
}
}
}
protected void startElement(Element element,
AttributesImpl namespaceAttributes) throws SAXException {
contentHandler.startElement(element.getNamespaceURI(), element
.getName(), element.getQualifiedName(), createAttributes(
element, namespaceAttributes));
}
protected void endElement(Element element) throws SAXException {
contentHandler.endElement(element.getNamespaceURI(), element.getName(),
element.getQualifiedName());
}
protected Attributes createAttributes(Element element,
Attributes namespaceAttributes) throws SAXException {
attributes.clear();
if (namespaceAttributes != null) {
attributes.setAttributes(namespaceAttributes);
}
for (Iterator iter = element.attributeIterator(); iter.hasNext();) {
Attribute attribute = (Attribute) iter.next();
attributes.addAttribute(attribute.getNamespaceURI(), attribute
.getName(), attribute.getQualifiedName(), "CDATA",
attribute.getValue());
}
return attributes;
}
/**
* If isDelcareNamespaceAttributes() is enabled then this method will add
* the given namespace declaration to the supplied attributes object,
* creating one if it does not exist.
*
* @param attrs
* DOCUMENT ME!
* @param namespace
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
protected AttributesImpl addNamespaceAttribute(AttributesImpl attrs,
Namespace namespace) {
if (declareNamespaceAttributes) {
if (attrs == null) {
attrs = new AttributesImpl();
}
String prefix = namespace.getPrefix();
String qualifiedName = "xmlns";
if ((prefix != null) && (prefix.length() > 0)) {
qualifiedName = "xmlns:" + prefix;
}
String uri = "";
String localName = prefix;
String type = "CDATA";
String value = namespace.getURI();
attrs.addAttribute(uri, localName, qualifiedName, type, value);
}
return attrs;
}
/**
* DOCUMENT ME!
*
* @param namespace
* DOCUMENT ME!
* @param namespaceStack
* DOCUMENT ME!
*
* @return true if the given namespace is an ignorable namespace (such as
* Namespace.NO_NAMESPACE or Namespace.XML_NAMESPACE) or if the
* namespace has already been declared in the current scope
*/
protected boolean isIgnoreableNamespace(Namespace namespace,
NamespaceStack namespaceStack) {
if (namespace.equals(Namespace.NO_NAMESPACE)
|| namespace.equals(Namespace.XML_NAMESPACE)) {
return true;
}
String uri = namespace.getURI();
if ((uri == null) || (uri.length() <= 0)) {
return true;
}
return namespaceStack.contains(namespace);
}
/**
* Ensures non-null content handlers?
*/
protected void checkForNullHandlers() {
}
}
/*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain copyright statements and
* notices. Redistributions must also contain a copy of this document.
*
* 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 name "DOM4J" must not be used to endorse or promote products derived
* from this Software without prior written permission of MetaStuff, Ltd. For
* written permission, please contact [email protected].
*
* 4. Products derived from this Software may not be called "DOM4J" nor may
* "DOM4J" appear in their names without prior written permission of MetaStuff,
* Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
*
* 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
*
* THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``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 METASTUFF, LTD. 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.
*
* Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
*/
© 2015 - 2025 Weber Informatics LLC | Privacy Policy