package client.net.sf.saxon.ce.tree.linked;
import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.event.Builder;
import client.net.sf.saxon.ce.event.Receiver;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.iter.AxisIterator;
import client.net.sf.saxon.ce.tree.iter.NodeListIterator;
import client.net.sf.saxon.ce.tree.util.FastStringBuffer;
import client.net.sf.saxon.ce.type.Type;
import client.net.sf.saxon.ce.value.Whitespace;
import java.util.ArrayList;
import java.util.HashMap;
/**
* A node in the XML parse tree representing the Document itself (or equivalently, the root
* node of the Document).
*
* <p>A DocumentImpl object may either represent a real document node, or it may represent an imaginary
* container for a parentless element.</p>
* @author Michael H. Kay
*/
public final class DocumentImpl extends ParentNodeImpl implements DocumentInfo {
//private static int nextDocumentNumber = 0;
private ElementImpl documentElement;
private HashMap<String, NodeInfo> idTable;
private int documentNumber;
private String baseURI;
private HashMap<Integer, ArrayList<NodeImpl>> elementList;
private HashMap<String, Object> userData;
private Configuration config;
private LineNumberMap lineNumberMap;
private SystemIdMap systemIdMap = new SystemIdMap();
private boolean imaginary;
/**
* Create a DocumentImpl
*/
public DocumentImpl() {
setRawParent(null);
}
/**
* Set the Configuration that contains this document
* @param config the Saxon configuration
*/
public void setConfiguration(Configuration config) {
this.config = config;
documentNumber = config.getDocumentNumberAllocator().allocateDocumentNumber();
}
/**
* Get the configuration previously set using setConfiguration
* @return the Saxon configuration
*/
public Configuration getConfiguration() {
return config;
}
/**
* Get the name pool used for the names in this document
*/
public NamePool getNamePool() {
return config.getNamePool();
}
/**
* Get a Builder suitable for building nodes that can be attached to this document.
* @return a new TreeBuilder
*/
public Builder newBuilder() {
LinkedTreeBuilder builder = new LinkedTreeBuilder();
builder.setAllocateSequenceNumbers(false);
return builder;
}
/**
* Set whether this is an imaginary document node
* @param imaginary if true, this is an imaginary node - the tree is really rooted at the topmost element
*/
public void setImaginary(boolean imaginary) {
this.imaginary = imaginary;
}
/**
* Ask whether this is an imaginary document node
* @return true if this is an imaginary node - the tree is really rooted at the topmost element
*/
public boolean isImaginary() {
return imaginary;
}
/**
* Get the unique document number
*/
public int getDocumentNumber() {
return documentNumber;
}
/**
* Set the top-level element of the document (variously called the root element or the
* document element). Note that a DocumentImpl may represent the root of a result tree
* fragment, in which case there is no document element.
* @param e the top-level element
*/
void setDocumentElement(ElementImpl e) {
documentElement = e;
}
/**
* Copy the system ID and line number map from another document
* (used when grafting a simplified stylesheet)
* @param original the document whose system ID and line number maps are to be grafted
* onto this tree
*/
public void graftLocationMap(DocumentImpl original) {
systemIdMap = original.systemIdMap;
lineNumberMap = original.lineNumberMap;
}
/**
* Set the system id (base URI) of this node
*/
public void setSystemId(String uri) {
if (uri==null) {
uri = "";
}
systemIdMap.setSystemId(getRawSequenceNumber(), uri);
}
/**
* Get the system id of this root node
*/
public String getSystemId() {
return systemIdMap.getSystemId(getRawSequenceNumber());
}
/**
* Set the base URI of this document node
* @param uri the new base URI
*/
public void setBaseURI(String uri) {
baseURI = uri;
}
/**
* Get the base URI of this root node.
* @return the base URI
*/
public String getBaseURI() {
if (baseURI != null) {
return baseURI;
}
return getSystemId();
}
/**
* Set the system id of an element in the document
* @param seq the sequence number of the element
* @param uri the system identifier (base URI) of the element
*/
void setSystemId(int seq, String uri) {
if (uri==null) {
uri = "";
}
systemIdMap.setSystemId(seq, uri);
}
/**
* Get the system id of an element in the document
* @param seq the sequence number of the element
* @return the systemId (base URI) of the element
*/
String getSystemId(int seq) {
return systemIdMap.getSystemId(seq);
}
/**
* Set line numbering on
*/
public void setLineNumbering() {
lineNumberMap = new LineNumberMap();
lineNumberMap.setLineAndColumn(getRawSequenceNumber(), 0, -1);
}
/**
* Set the line number for an element. Ignored if line numbering is off.
* @param sequence the sequence number of the element
* @param line the line number of the element
* @param column the column number of the element
*/
void setLineAndColumn(int sequence, int line, int column) {
if (lineNumberMap != null && sequence >= 0) {
lineNumberMap.setLineAndColumn(sequence, line, column);
}
}
/**
* Get the line number for an element.
* @param sequence the sequence number of the element
* @return the line number for an element. Return -1 if line numbering is off, or if
* the element was added subsequent to document creation by use of XQuery update
*/
int getLineNumber(int sequence) {
if (lineNumberMap != null && sequence >= 0) {
return lineNumberMap.getLineNumber(sequence);
}
return -1;
}
/**
* Get the column number for an element.
* @param sequence the sequence number of the element
* @return the column number for an element. Return -1 if line numbering is off, or if
* the element was added subsequent to document creation by use of XQuery update
*/
int getColumnNumber(int sequence) {
if (lineNumberMap != null && sequence >= 0) {
return lineNumberMap.getColumnNumber(sequence);
}
return -1;
}
/**
* Get the line number of this root node.
* @return 0 always
*/
public int getLineNumber() {
return 0;
}
/**
* Return the type of node.
* @return Type.DOCUMENT (always)
*/
public final int getNodeKind() {
return Type.DOCUMENT;
}
/**
* Get next sibling - always null
* @return null
*/
public final NodeInfo getNextSibling() {
return null;
}
/**
* Get previous sibling - always null
* @return null
*/
public final NodeInfo getPreviousSibling() {
return null;
}
/**
* Get the root (outermost) element.
* @return the Element node for the outermost element of the document.
*/
public ElementImpl getDocumentElement() {
return documentElement;
}
/**
* Get the root node
* @return the NodeInfo representing the root of this tree
*/
public NodeInfo getRoot() {
return this;
}
/**
* Get the root (document) node
* @return the DocumentInfo representing this document
*/
public DocumentInfo getDocumentRoot() {
return this;
}
/**
* Get the physical root of the tree. This may be an imaginary document node: this method
* should be used only when control information held at the physical root is required
* @return the document node, which may be imaginary
*/
public DocumentImpl getPhysicalRoot() {
return this;
}
/**
* Get a character string that uniquely identifies this node
* @param buffer a buffer into which will be placed a string based on the document number
*
*/
public void generateId(FastStringBuffer buffer) {
buffer.append('d');
buffer.append(Long.toString(documentNumber));
}
/**
* Get a list of all elements with a given name fingerprint
* @param fingerprint the fingerprint of the required element name
* @return an iterator over all the elements with this name
*/
AxisIterator getAllElements(int fingerprint) {
if (elementList==null) {
elementList = new HashMap<Integer, ArrayList<NodeImpl>>(100);
}
ArrayList<NodeImpl> list = elementList.get(fingerprint);
if (list==null) {
list = new ArrayList<NodeImpl>(100);
NodeImpl next = getNextInDocument(this);
while (next!=null) {
if (next.getNodeKind()==Type.ELEMENT &&
next.getFingerprint() == fingerprint) {
list.add(next);
}
next = next.getNextInDocument(this);
}
elementList.put(fingerprint, list);
}
return new NodeListIterator(list);
}
/**
* Index all the ID attributes. This is done the first time the id() function
* is used on this document, or the first time that id() is called after a sequence of updates
*/
private void indexIDs() {
if (idTable!=null) {
return; // ID's are already indexed
}
idTable = new HashMap<String, NodeInfo>(256);
NodeImpl curr = this;
NodeImpl root = curr;
while(curr!=null) {
if (curr.getNodeKind()==Type.ELEMENT) {
//noinspection ConstantConditions
ElementImpl e = (ElementImpl)curr;
AttributeCollection atts = e.getAttributeList();
for (int i=0; i<atts.getLength(); i++) {
if (atts.isId(i) && NameChecker.isValidNCName(Whitespace.trim(atts.getValue(i)))) {
// don't index any invalid IDs - these can arise when using a non-validating parser
registerID(e, Whitespace.trim(atts.getValue(i)));
}
}
}
curr = curr.getNextInDocument(root);
}
}
/**
* Register a unique element ID. Does nothing if there is already an element with that ID.
* @param e The Element having a particular unique ID value
* @param id The unique ID value
*/
protected void registerID(NodeInfo e, String id) {
// the XPath spec (5.2.1) says ignore the second ID if it's not unique
if (idTable == null) {
idTable = new HashMap<String, NodeInfo>(256);
}
Object old = idTable.get(id);
if (old==null) {
idTable.put(id, e);
}
}
/**
* Get the element with a given ID.
* @param id The unique ID of the required element, previously registered using registerID()
* @return The NodeInfo for the given ID if one has been registered, otherwise null.
*/
public NodeInfo selectID(String id) {
if (idTable==null) {
indexIDs();
}
return idTable.get(id);
}
/**
* Get the type annotation of this node, if any. By convention for a document node this is
* XS_ANY_TYPE if the document is validated, or XS_UNTYPED otherwise
* @return the type annotation, as the integer name code of the type name
*/
public int getTypeAnnotation() {
if (documentElement == null || documentElement.getTypeAnnotation() == StandardNames.XS_UNTYPED) {
return StandardNames.XS_UNTYPED;
} else {
return StandardNames.XS_ANY_TYPE;
}
}
/**
* Copy this node to a given outputter
*/
public void copy(Receiver out, int copyOptions) throws XPathException {
out.startDocument();
// copy the children
NodeImpl next = (NodeImpl)getFirstChild();
while (next!=null) {
next.copy(out, copyOptions);
next = (NodeImpl)next.getNextSibling();
}
out.endDocument();
}
/**
* Set user data on the document node. The user data can be retrieved subsequently
* using {@link #getUserData}
* @param key A string giving the name of the property to be set. Clients are responsible
* for choosing a key that is likely to be unique. Must not be null.
* @param value The value to be set for the property. May be null, which effectively
* removes the existing value for the property.
*/
public void setUserData(String key, Object value) {
if (userData == null) {
userData = new HashMap(4);
}
if (value == null) {
userData.remove(key);
} else {
userData.put(key, value);
}
}
/**
* Get user data held in the document node. This retrieves properties previously set using
* {@link #setUserData}
* @param key A string giving the name of the property to be retrieved.
* @return the value of the property, or null if the property has not been defined.
*/
public Object getUserData(String key) {
if (userData == null) {
return null;
} else {
return userData.get(key);
}
}
}
// 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.