net.sf.saxon.dom.DOMSender Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of saxon-he Show documentation
Show all versions of saxon-he Show documentation
An OSGi bundle for Saxon-HE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.dom;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.event.SaxonLocator;
import net.sf.saxon.event.SourceLocationProvider;
import net.sf.saxon.om.FingerprintedQName;
import net.sf.saxon.om.NameChecker;
import net.sf.saxon.om.NamespaceBinding;
import net.sf.saxon.om.NodeName;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.Untyped;
import org.w3c.dom.*;
import org.xml.sax.helpers.NamespaceSupport;
import java.util.HashMap;
import java.util.Map;
/**
* DOMSender.java: pseudo-SAX driver for a DOM source document.
* This class takes an existing
* DOM Document and walks around it in a depth-first traversal,
* calling a Receiver to process the nodes as it does so
*/
public class DOMSender implements SaxonLocator, SourceLocationProvider {
/*@NotNull*/ private Receiver receiver;
/*@NotNull*/ protected Node root;
private NamespaceSupport nsSupport = new NamespaceSupport();
private String[] parts = new String[3];
private String[] elparts = new String[3];
private HashMap nsDeclarations = new HashMap(10);
protected String systemId;
/**
* Create a DOMSender that will send events representing the nodes in a tree
* to a nominated receiver
* @param startNode the root node of the tree to be send. Usually a document or element node.
* @param receiver the object to be notified of the resulting events. The supplied Receiver must
* be initialized with a PipelineConfiguration.The PipelineConfiguration
* of the Receiver will be modified to set this DOMSender as its LocationProvider.
*/
public DOMSender(Node startNode, Receiver receiver) {
if (startNode == null) {
throw new NullPointerException("startNode");
}
if (receiver == null) {
throw new NullPointerException("receiver");
}
this.root = startNode;
this.receiver = receiver;
}
/**
* Set the systemId of the source document (which will also be
* used for the destination)
* @param systemId the systemId of the source document
*/
public void setSystemId(String systemId) {
this.systemId = systemId;
}
/**
* Walk a tree (traversing the nodes depth first).
* @throws IllegalStateException if the
* start node is of a node kind other than document, document fragment, element, text,
* comment, or processing instruction (for example, if it is an attribute node).
* @throws net.sf.saxon.trans.XPathException On any error in the document
*/
public void send() throws XPathException {
receiver.setSystemId(systemId);
receiver.getPipelineConfiguration().setLocationProvider(this);
receiver.open();
switch (root.getNodeType()) {
case Node.DOCUMENT_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
receiver.startDocument(0);
walkNode(root);
receiver.endDocument();
break;
case Node.ELEMENT_NODE:
sendElement((Element)root);
break;
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
receiver.characters(((CharacterData)root).getData(), 0, 0);
break;
case Node.COMMENT_NODE:
receiver.comment(((Comment)root).getData(), 0, 0);
break;
case Node.PROCESSING_INSTRUCTION_NODE:
receiver.processingInstruction(
((ProcessingInstruction)root).getTarget(),
((ProcessingInstruction)root).getData(), 0, 0);
break;
default:
throw new IllegalStateException("DOMSender: unsupported kind of start node (" + root.getNodeType() + ")");
}
receiver.close();
}
/**
* Walk a tree starting from a particular element node. This has to make
* sure that all the namespace declarations in scope for the element are
* treated as if they were namespace declarations on the element itself.
* @param startNode the start element node from which the walk will start
* @throws net.sf.saxon.trans.XPathException if a dynamic error occurs
*/
private void sendElement(Element startNode) throws XPathException {
Element node = startNode;
boolean hasNamespaceDeclarations = gatherNamespaces(node, false);
while (true) {
gatherNamespaces(node, true);
Node parent = node.getParentNode();
if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
node = (Element)parent;
} else {
break;
}
}
outputElement(startNode, hasNamespaceDeclarations);
}
/**
* Walk an element of a document (traversing the children depth first)
* @param node The DOM Element object to walk
* @exception net.sf.saxon.trans.XPathException On any error in the document
*
*/
private void walkNode (Node node) throws XPathException {
// See https://saxonica.plan.io/issues/1955
if (node.hasChildNodes()) {
for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
switch (child.getNodeType()) {
case Node.DOCUMENT_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
break; // should not happen
case Node.ELEMENT_NODE:
Element element = (Element)child;
boolean hasNamespaces = gatherNamespaces(element, false);
outputElement(element, hasNamespaces);
nsSupport.popContext();
break;
case Node.ATTRIBUTE_NODE: // have already dealt with attributes
break;
case Node.PROCESSING_INSTRUCTION_NODE:
receiver.processingInstruction(
((ProcessingInstruction)child).getTarget(),
((ProcessingInstruction)child).getData(),
0, 0);
break;
case Node.COMMENT_NODE: {
String text = ((Comment)child).getData();
if (text!=null) {
receiver.comment(text, 0, 0);
}
break;
}
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE: {
String text = ((CharacterData)child).getData();
if (text!=null) {
receiver.characters(text, 0, 0);
}
break;
}
case Node.ENTITY_REFERENCE_NODE:
walkNode(child);
break;
default:
break; // should not happen
}
}
}
}
private void outputElement(Element element, boolean hasNamespaceDeclarations) throws XPathException {
String[] elparts2 = nsSupport.processName(element.getTagName(), elparts, false);
if (elparts2==null) {
throw new XPathException("Undeclared namespace in " + element.getTagName());
}
String uri = elparts2[0];
String local = elparts2[1];
String prefix = NameChecker.getPrefix(elparts2[2]);
receiver.startElement(new FingerprintedQName(prefix, uri, local), Untyped.getInstance(), 0, 0);
for (Map.Entry decl : nsDeclarations.entrySet()) {
receiver.namespace(new NamespaceBinding(decl.getKey(), decl.getValue()), 0);
}
NamedNodeMap atts = element.getAttributes();
if (atts != null) {
final int len = atts.getLength();
for (int a2=0; a2=0) {
try {
String prefix = att.getPrefix();
String uri = att.getNamespaceURI();
//System.err.println("Implicit Namespace: " + prefix + "=" + uri);
if (nsDeclarations.get(prefix)==null) {
nsSupport.declarePrefix(prefix, uri);
nsDeclarations.put(prefix, uri);
}
} catch (Throwable err) {
// it must be a level 1 DOM
}
}
}
return hasNamespaceDeclarations;
}
// Implement the SAX Locator interface. This is needed to pass the base URI of nodes
// to the receiver. We don't attempt to preserve the original base URI of each individual
// node as it is copied, only the base URI of the document as a whole.
public int getColumnNumber() {
return -1;
}
public int getLineNumber() {
return -1;
}
public String getPublicId() {
return null;
}
public String getSystemId() {
return systemId;
}
public String getSystemId(long locationId) {
return getSystemId();
}
public int getLineNumber(long locationId) {
return getLineNumber();
}
public int getColumnNumber(long locationId) {
return getColumnNumber();
}
}