package client.net.sf.saxon.ce.tree.linked;
import client.net.sf.saxon.ce.event.Builder;
import client.net.sf.saxon.ce.event.PipelineConfiguration;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.trans.XPathException;
import java.util.ArrayList;
/**
* The LinkedTreeBuilder class is responsible for taking a stream of Receiver events and constructing
* a Document tree using the linked tree implementation.
* @author Michael H. Kay
*/
public class LinkedTreeBuilder extends Builder
{
private ParentNodeImpl currentNode;
private boolean contentStarted = false; // provides a minimal check on correct sequence of calls
private NodeFactory nodeFactory;
private int[] size = new int[100]; // stack of number of children for each open node
private int depth = 0;
private ArrayList<NodeImpl[]> arrays = new ArrayList<NodeImpl[]>(20); // reusable arrays for creating nodes
private int elementNameCode;
private AttributeCollection attributes;
private NamespaceBinding[] namespaces;
private int namespacesUsed;
private boolean allocateSequenceNumbers = true;
private int nextNodeNumber = 1;
/**
* create a Builder and initialise variables
*/
public LinkedTreeBuilder() {
nodeFactory = new DefaultNodeFactory();
// System.err.println("new TreeBuilder " + this);
}
/**
* Get the current root node. This will normally be a document node, but if the root of the tree
* is an element node, it can be an element.
* @return the root of the tree that is currently being built, or that has been most recently built
* using this builder
*/
public NodeInfo getCurrentRoot() {
NodeInfo physicalRoot = currentRoot;
if (physicalRoot instanceof DocumentImpl && ((DocumentImpl)physicalRoot).isImaginary()) {
return ((DocumentImpl)physicalRoot).getDocumentElement();
} else {
return physicalRoot;
}
}
public void reset() {
super.reset();
currentNode = null;
nodeFactory = null;
depth = 0;
allocateSequenceNumbers = true;
nextNodeNumber = 1;
}
/**
* Set whether the builder should allocate sequence numbers to elements as they are added to the
* tree. This is normally done, because it provides a quick way of comparing document order. But
* nodes added using XQuery update are not sequence-numbered.
* @param allocate true if sequence numbers are to be allocated
*/
public void setAllocateSequenceNumbers(boolean allocate) {
allocateSequenceNumbers = allocate;
}
/**
* Set the Node Factory to use. If none is specified, the Builder uses its own.
* @param factory the node factory to be used. This allows custom objects to be used to represent
* the elements in the tree.
*/
public void setNodeFactory(NodeFactory factory) {
nodeFactory = factory;
}
/**
* Open the stream of Receiver events
*/
public void open () {
started = true;
depth = 0;
size[depth] = 0;
super.open();
}
/**
* Start of a document node.
* This event is ignored: we simply add the contained elements to the current document
*/
public void startDocument() throws XPathException {
DocumentImpl doc = new DocumentImpl();
currentRoot = doc;
doc.setSystemId(getSystemId());
doc.setBaseURI(getBaseURI());
doc.setConfiguration(config);
currentNode = doc;
depth = 0;
size[depth] = 0;
doc.setRawSequenceNumber(0);
contentStarted = true;
}
/**
* Notify the end of the document
*/
public void endDocument() throws XPathException {
//System.err.println("End document depth=" + depth);
currentNode.compact(size[depth]);
}
/**
* Close the stream of Receiver events
*/
public void close () throws XPathException {
// System.err.println("TreeBuilder: " + this + " End document");
if (currentNode==null) {
return; // can be called twice on an error path
}
currentNode.compact(size[depth]);
currentNode = null;
// we're not going to use this Builder again so give the garbage collector
// something to play with
arrays = null;
super.close();
nodeFactory = null;
}
/**
* Notify the start of an element
*/
public void startElement(int nameCode, int properties) throws XPathException {
//System.err.println("LinkedTreeBuilder: " + this + " Start element depth=" + depth);
if (currentNode == null) {
startDocument();
((DocumentImpl)currentRoot).setImaginary(true);
}
elementNameCode = nameCode;
namespacesUsed = 0;
attributes = null;
contentStarted = false;
}
public void namespace (NamespaceBinding nsBinding, int properties) {
if (contentStarted) {
throw new IllegalStateException("namespace() called after startContent()");
}
if (namespaces==null) {
namespaces = new NamespaceBinding[5];
}
if (namespacesUsed == namespaces.length) {
NamespaceBinding[] ns2 = new NamespaceBinding[namespaces.length * 2];
System.arraycopy(namespaces, 0, ns2, 0, namespacesUsed);
namespaces = ns2;
}
namespaces[namespacesUsed++] = nsBinding;
}
public void attribute(int nameCode, CharSequence value)
throws XPathException {
if (contentStarted) {
throw new IllegalStateException("attribute() called after startContent()");
}
if (attributes==null) {
attributes = new AttributeCollection(config);
}
attributes.addAttribute(nameCode, value.toString());
}
public void startContent() throws XPathException {
// System.err.println("TreeBuilder: " + this + " startContent()");
if (contentStarted) {
throw new IllegalStateException("startContent() called more than once");
}
contentStarted = true;
if (attributes == null) {
attributes = AttributeCollection.EMPTY_ATTRIBUTE_COLLECTION;
} else {
attributes.compact();
}
NamespaceBinding[] nslist = namespaces;
if (nslist == null || namespacesUsed == 0) {
nslist = NamespaceBinding.EMPTY_ARRAY;
}
ElementImpl elem = nodeFactory.makeElementNode(
currentNode, elementNameCode, StandardNames.XS_UNTYPED,
attributes, nslist, namespacesUsed,
pipe,
getSystemId(), (allocateSequenceNumbers ? nextNodeNumber++ : -1));
namespacesUsed = 0;
attributes = null;
// the initial array used for pointing to children will be discarded when the exact number
// of children in known. Therefore, it can be reused. So we allocate an initial array from
// a pool of reusable arrays. A nesting depth of >20 is so rare that we don't bother.
while (depth >= arrays.size()) {
arrays.add(new NodeImpl[20]);
}
elem.setChildren(arrays.get(depth));
currentNode.addChild(elem, size[depth]++);
if (depth >= size.length - 1) {
int[] newsize = new int[size.length * 2];
System.arraycopy(size, 0, newsize, 0, size.length);
size = newsize;
}
size[++depth] = 0;
namespacesUsed = 0;
if (currentNode instanceof DocumentInfo) {
((DocumentImpl)currentNode).setDocumentElement(elem);
}
currentNode = elem;
}
/**
* Notify the end of an element
*/
public void endElement () throws XPathException {
//System.err.println("End element depth=" + depth);
if (!contentStarted) {
throw new IllegalStateException("missing call on startContent()");
}
currentNode.compact(size[depth]);
depth--;
currentNode = (ParentNodeImpl)currentNode.getParent();
}
/**
* Notify a text node. Adjacent text nodes must have already been merged
*/
public void characters(CharSequence chars) throws XPathException {
// System.err.println("Characters: " + chars.toString() + " depth=" + depth);
if (!contentStarted) {
throw new IllegalStateException("missing call on startContent()");
}
if (chars.length()>0) {
NodeInfo prev = currentNode.getNthChild(size[depth]-1);
if (prev instanceof TextImpl) {
// concatenate adjacent text nodes
((TextImpl)prev).appendStringValue(chars.toString());
} else {
TextImpl n = new TextImpl(chars.toString());
currentNode.addChild(n, size[depth]++);
}
}
}
/**
* Notify a processing instruction
*/
public void processingInstruction(String name, CharSequence remainder) {
if (!contentStarted) {
throw new IllegalStateException("missing call on startContent()");
}
int nameCode = namePool.allocate("", "", name);
ProcInstImpl pi = new ProcInstImpl(nameCode, remainder.toString());
currentNode.addChild(pi, size[depth]++);
}
/**
* Notify a comment
*/
public void comment(CharSequence chars) throws XPathException {
if (!contentStarted) {
throw new IllegalStateException("missing call on startContent()");
}
CommentImpl comment = new CommentImpl(chars.toString());
currentNode.addChild(comment, size[depth]++);
}
/**
* Get the current document or element node
* @return the most recently started document or element node (to which children are currently being added)
* In the case of elements, this is only available after startContent() has been called
*/
public ParentNodeImpl getCurrentParentNode() {
return currentNode;
}
/**
* Get the current text, comment, or processing instruction node
* @return if any text, comment, or processing instruction nodes have been added to the current parent
* node, then return that text, comment, or PI; otherwise return null
*/
public NodeImpl getCurrentLeafNode() {
return (NodeImpl)currentNode.getLastChild();
}
/**
* graftElement() allows an element node to be transferred from one tree to another.
* This is a dangerous internal interface which is used only to contruct a stylesheet
* tree from a stylesheet using the "literal result element as stylesheet" syntax.
* The supplied element is grafted onto the current element as its only child.
* @param element the element to be grafted in as a new child.
*/
public void graftElement(ElementImpl element) throws XPathException {
currentNode.addChild(element, size[depth]++);
}
//////////////////////////////////////////////////////////////////////////////
// Inner class DefaultNodeFactory. This creates the nodes in the tree.
// It can be overridden, e.g. when building the stylesheet tree
//////////////////////////////////////////////////////////////////////////////
private static class DefaultNodeFactory implements NodeFactory {
public ElementImpl makeElementNode(
NodeInfo parent,
int nameCode,
int typeCode,
AttributeCollection attlist,
NamespaceBinding[] namespaces,
int namespacesUsed,
PipelineConfiguration pipe,
String baseURI,
int sequenceNumber)
{
ElementImpl e = new ElementImpl();
if (namespacesUsed > 0) {
e.setNamespaceDeclarations(namespaces, namespacesUsed);
}
e.initialise(nameCode, attlist, parent, sequenceNumber);
e.setLocation(baseURI);
return e;
}
}
}
// 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.