package org.apache.xindice.xml.dom;
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 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 end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xindice" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``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 THE APACHE SOFTWARE FOUNDATION 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999-2001, The dbXML
* Group, L.L.C., http://www.dbxmlgroup.com. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* $Id: DocumentImpl.java,v 1.5 2002/10/31 07:16:52 vladimir Exp $
*/
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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* DocumentImpl
*/
public final class DocumentImpl extends ContainerNodeImpl implements CompressedDocument, DBDocument, DocumentTraversal {
private static Log log = LogFactory.getLog("org.apache.xindice.xml");
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.isDebugEnabled()) {
log.debug("No message", 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) {
// TODO delete 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) ) {
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) {
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);
importNamespaces(importedNode, clone);
return clone;
}
}
// Crap, we have to do a full graph copy
Node result = null;
switch ( importedNode.getNodeType() ) {
case Node.ATTRIBUTE_NODE:
Attr sattr = (Attr)importedNode;
Attr attr = createAttribute(sattr.getName());
attr.setValue(sattr.getValue());
result = attr;
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;
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));
}
}
return result;
}
catch ( Exception e ) {
if (log.isDebugEnabled()) {
log.debug("No message", 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;
}
}