/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed 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.
*
* CVS $Id: DocumentImpl.java,v 1.16 2004/02/24 15:32:27 vgritsenko Exp $
*/
package org.apache.xindice.xml.dom;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.util.XindiceException;
import org.apache.xindice.xml.NodeSource;
import org.apache.xindice.xml.SymbolTable;
import org.apache.xindice.xml.dom.traversal.TreeWalkerImpl;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.w3c.dom.traversal.TreeWalker;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* DocumentImpl
*
* @version CVS $Revision: 1.16 $, $Date: 2004/02/24 15:32:27 $
*/
public final class DocumentImpl extends ContainerNodeImpl implements CompressedDocument, DBDocument, DocumentTraversal {
private static final Log log = LogFactory.getLog(DocumentImpl.class);
private DocumentType docType = null;
private String version = null;
private String actualEncoding = null;
private String encoding = null;
private boolean standalone = false;
private boolean strictErrorChecking = false;
private SymbolTable symbols = null;
private boolean readOnly = false;
public DocumentImpl() {
super(null, true);
}
public DocumentImpl(byte[] data, int pos, int len) {
super(null, data, pos, len);
}
public DocumentImpl(byte[] data) {
this(data, 0, data.length);
}
public DocumentImpl(byte[] data, SymbolTable symbols, NodeSource source) {
this(data);
this.symbols = symbols;
this.source = source;
}
public DocumentImpl(Document doc) throws XindiceException {
super(null, true);
boolean compress = true;
if (doc instanceof CompressedDocument) {
CompressedDocument c = (CompressedDocument) doc;
symbols = c.getSymbols();
if (!c.isDirty()) {
data = c.getDataBytes();
pos = c.getDataPos();
len = c.getDataLen();
compress = false;
}
}
if (compress) {
if (symbols == null) {
symbols = new SymbolTable();
}
data = DOMCompressor.Compress(doc, symbols);
pos = 0;
len = data.length;
}
if (doc instanceof DBDocument) {
DBDocument d = (DBDocument) doc;
source = d.getSource();
}
}
public boolean isReadOnly() {
return readOnly;
}
protected void checkLoaded() {
if (loaded) {
return;
} else {
loaded = true;
}
try {
if (data != null) {
loadChildren(symbols);
}
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("ignored exception", e);
}
}
}
public boolean isCaching() {
String cache = DBDocument.CACHE;
int size = childNodes.getLength();
for (int i = 0; i < size; i++) {
Node n = childNodes.item(i);
if (n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE && n.getNodeName().equals(CACHE_CONTROL)) {
cache = n.getNodeValue().trim();
break;
}
}
return (cache != null && cache.equals(CACHE));
}
public void setCaching(boolean caching) {
int size = childNodes.getLength();
for (int i = 0; i < size; i++) {
Node n = childNodes.item(i);
if (n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE && n.getNodeName().equals(CACHE_CONTROL)) {
n.setNodeValue(caching ? CACHE : NOCACHE);
return;
}
}
ProcessingInstruction pi = createProcessingInstruction(CACHE_CONTROL, caching ? CACHE : NOCACHE);
insertBefore(pi, getDocumentElement());
}
public SymbolTable getSymbols() {
return symbols;
}
public void setSymbols(SymbolTable symbols) {
this.symbols = symbols;
}
public void expandSource() {
ElementImpl e = (ElementImpl) getDocumentElement();
if (e != null) {
e.expandSource();
}
}
public Node getNodeAtPos(int pos) {
return null; // TODO: This
}
public short getNodeType() {
return Node.DOCUMENT_NODE;
}
public String getNodeName() {
return "#document";
}
/**
* The Document Type Declaration (see <code>DocumentType</code>) associated
* with this document. For HTML documents as well as XML documents without
* a document type declaration this returns <code>null</code>. The DOM Level
* 1 does not support editing the Document Type Declaration, therefore
* <code>docType</code> cannot be altered in any way.
*/
public DocumentType getDoctype() {
return docType;
}
public void setDoctype(DocumentType docType) {
this.docType = docType;
}
/**
* The <code>DOMImplementation</code> object that handles this document. A
* DOM application may use objects from multiple implementations.
*/
public DOMImplementation getImplementation() {
return DOMImplementationImpl.getInstance();
}
/**
* Creates a <code>Text</code> node given the specified string.
* @param data The data for the node.
* @return The new <code>Text</code> object.
*/
public Text createTextNode(String data) {
return new TextImpl(this, data);
}
/**
* This is a convenience attribute that allows direct access to the child
* node that is the root element of the document. For HTML documents, this
* is the element with the tagName "HTML".
*/
public Element getDocumentElement() {
checkLoaded();
Iterator enum = childNodes.iterator();
while (enum.hasNext()) {
Node node = (Node) enum.next();
if (node.getNodeType() == Node.ELEMENT_NODE) {
return (Element) node;
}
}
return null;
}
/**
* Creates a <code>CDATASection</code> node whose value is the specified
* string.
* @param data The data for the <code>CDATASection</code> contents.
* @return The new <code>CDATASection</code> object.
* @exception DOMException
* NOT_SUPPORTED_ERR: Raised if this document is an HTML document.
*/
public CDATASection createCDATASection(String data) throws DOMException {
return new CDATASectionImpl(this, data);
}
/**
* Creates an element of the type specified. Note that the instance returned
* implements the Element interface, so attributes can be specified
* directly on the returned object.
* @param tagName The name of the element type to instantiate. For XML, this
* is case-sensitive. For HTML, the <code>tagName</code> parameter may
* be provided in any case, but it must be mapped to the canonical
* uppercase form by the DOM implementation.
* @return A new <code>Element</code> object.
* @exception DOMException
* INVALID_CHARACTER_ERR: Raised if the specified name contains an
* invalid character.
*/
public Element createElement(String tagName) throws DOMException {
return new ElementImpl(this, tagName);
}
/**
* Creates an empty <code>DocumentFragment</code> object.
* @return A new <code>DocumentFragment</code>.
*/
public DocumentFragment createDocumentFragment() {
return new DocumentFragmentImpl(this);
}
/**
* Creates an <code>Attr</code> of the given name. Note that the
* <code>Attr</code> instance can then be set on an <code>Element</code>
* using the <code>setAttribute</code> method.
* @param name The name of the attribute.
* @return A new <code>Attr</code> object.
* @exception DOMException
* INVALID_CHARACTER_ERR: Raised if the specified name contains an
* invalid character.
*/
public Attr createAttribute(String name) throws DOMException {
return new AttrImpl(this, name);
}
/**
* Creates a <code>Comment</code> node given the specified string.
* @param data The data for the node.
* @return The new <code>Comment</code> object.
*/
public Comment createComment(String data) {
return new CommentImpl(this, data);
}
/**
* Creates a <code>ProcessingInstruction</code> node given the specified
* name and data strings.
* @param target The target part of the processing instruction.
* @param data The data for the node.
* @return The new <code>ProcessingInstruction</code> object.
* @exception DOMException
* INVALID_CHARACTER_ERR: Raised if an invalid character is specified.
* <br>NOT_SUPPORTED_ERR: Raised if this document is an HTML document.
*/
public ProcessingInstruction createProcessingInstruction(String target, String data) throws DOMException {
return new ProcessingInstructionImpl(this, target, data);
}
/**
* Creates an EntityReference object.
* @param name The name of the entity to reference.
* @return The new <code>EntityReference</code> object.
* @exception DOMException
* INVALID_CHARACTER_ERR: Raised if the specified name contains an
* invalid character.
* <br>NOT_SUPPORTED_ERR: Raised if this document is an HTML document.
*/
public EntityReference createEntityReference(String name) throws DOMException {
return new EntityReferenceImpl(this, name);
}
private void importNamespaces(Node source, Node target) {
if (target.getNodeType() == Node.ELEMENT_NODE) {
// Retrieve Namespace definitions in scope
Set set = new HashSet();
Node n = source;
Element elem = (Element) target;
while (n != null) {
NamedNodeMap nm = n.getAttributes();
for (int i = 0; i < nm.getLength(); i++) {
Attr a = (Attr) nm.item(i);
String name = a.getNodeName();
if ((name.startsWith("xmlns:") || name.equals("xmlns")) && !set.contains(name)) {
set.add(name);
elem.setAttribute(name, a.getValue());
}
}
n = n.getParentNode();
if (n.getNodeType() == DOCUMENT_NODE || n.getNodeType() == DOCUMENT_FRAGMENT_NODE) {
n = null;
}
}
}
}
public Node importNode(Node importedNode, boolean deep) {
return importNode(importedNode, deep, true);
}
private Node importNode(Node importedNode, boolean deep, boolean importNamespaces) {
try {
// If we're a Xindice DOM Node and share the same symbol table,
// then we're golden
if (importedNode instanceof NodeImpl) {
NodeImpl impl = (NodeImpl) importedNode;
DocumentImpl docImpl = (DocumentImpl) impl.getOwnerDocument();
if (docImpl.getSymbols() != null && (docImpl.getSymbols() == symbols)) {
NodeImpl clone = (NodeImpl) impl.cloneNode(deep);
clone.setParentNode(this);
if (importNamespaces)
{
importNamespaces(importedNode, clone);
}
return clone;
}
}
// Crap, we have to do a full graph copy
Node result = null;
switch (importedNode.getNodeType()) {
case Node.ATTRIBUTE_NODE:
result = createAttribute(importedNode.getNodeName());
break;
case Node.CDATA_SECTION_NODE:
result = createCDATASection(importedNode.getNodeValue());
break;
case Node.COMMENT_NODE:
result = createComment(importedNode.getNodeValue());
break;
case Node.ELEMENT_NODE:
Element selem = (Element) importedNode;
Element elem = createElement(selem.getTagName());
NamedNodeMap attrs = selem.getAttributes();
int size = attrs.getLength();
for (int i = 0; i < size; i++) {
Attr a = (Attr) attrs.item(i);
Attr ai = createAttribute(a.getName());
ai.setValue(a.getValue());
elem.setAttributeNode(ai);
}
result = elem;
if (importNamespaces)
{
importNamespaces(importedNode, result);
}
break;
case Node.ENTITY_REFERENCE_NODE:
result = createEntityReference(importedNode.getNodeValue());
break;
case Node.PROCESSING_INSTRUCTION_NODE:
result = createProcessingInstruction(importedNode.getNodeName(), importedNode.getNodeValue());
break;
case Node.TEXT_NODE:
result = createTextNode(importedNode.getNodeValue());
break;
default :
}
if (deep && result != null) {
NodeList list = importedNode.getChildNodes();
int size = list.getLength();
for (int i = 0; i < size; i++) {
Node n = list.item(i);
result.appendChild(importNode(n, deep, false));
}
}
return result;
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("ignored exception", e);
}
return null;
}
}
public Element createElementNS(String namespaceURI, String qualifiedName) {
return new ElementImpl(this, qualifiedName);
}
public Attr createAttributeNS(String namespaceURI, String qualifiedName) {
return new AttrImpl(this, qualifiedName);
}
public NodeIterator createNodeIterator(Node root, int whatToShow, NodeFilter filter, boolean entityReferenceExpansion) throws DOMException {
return new TreeWalkerImpl(root, whatToShow, filter, entityReferenceExpansion);
}
public TreeWalker createTreeWalker(Node root, int whatToShow, NodeFilter filter, boolean entityReferenceExpansion) throws DOMException {
return new TreeWalkerImpl(root, whatToShow, filter, entityReferenceExpansion);
}
// DOM Level 3 Stuff
public Node adoptNode(Node src) {
// If we're a Xindice DOM Node and share the same symbol table
// or the adopted node has no symbol table, then we're golden
if (src instanceof NodeImpl) {
NodeImpl impl = (NodeImpl) src;
DocumentImpl docImpl = (DocumentImpl) impl.getOwnerDocument();
if (docImpl.getSymbols() == null || docImpl.getSymbols() == symbols) {
impl.getParentNode().removeChild(impl);
impl.setParentNode(this);
return impl;
}
}
return importNode(src, true);
}
public String getActualEncoding() {
checkLoaded();
return actualEncoding;
}
public void setActualEncoding(String actualEncoding) {
checkReadOnly();
checkLoaded();
this.actualEncoding = actualEncoding;
}
public String getEncoding() {
checkLoaded();
return encoding;
}
public void setEncoding(String encoding) {
checkReadOnly();
checkLoaded();
this.encoding = encoding;
}
public String getVersion() {
checkLoaded();
return version;
}
public void setVersion(String version) {
checkReadOnly();
checkLoaded();
this.version = version;
}
public boolean getStandalone() {
checkLoaded();
return standalone;
}
public void setStandalone(boolean standalone) {
checkReadOnly();
checkLoaded();
this.standalone = standalone;
}
public boolean getStrictErrorChecking() {
checkLoaded();
return strictErrorChecking;
}
public void setStrictErrorChecking(boolean strictErrorChecking) {
checkReadOnly();
checkLoaded();
this.strictErrorChecking = strictErrorChecking;
}
}