blob: 1bb2b93e655c4be83d5211a8a9f1a1ef8e1c45d6 [file] [log] [blame]
/*
* $Id$
*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2000 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 "Crimson" 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, Sun Microsystems, Inc.,
* http://www.sun.com. For more information on the Apache Software
* Foundation, please see <http://www.apache.org/>.
*/
package org.apache.xerces.tree;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.OutputStream;
import java.io.Writer;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.XMLReader;
import org.apache.xerces.parsers.SAXParser;
import org.apache.xerces.tree.Resolver;
import org.apache.xerces.tree.MessageCatalog;
import org.apache.xerces.tree.XmlNames;
/**
* This class implements the DOM <em>Document</em> interface, and also
* provides static factory methods to create document instances. Instances
* represent the top level of an XML 1.0 document, typically consisting
* of processing instructions followed by one tree of XML data. These
* documents may be written out for transfer or storage using a variety
* of text encodings.
*
* <P> The static factory methods do not offer any customization options.
* in particular, they do not enforce XML Namespaces when parsing, do not
* offer customizable element factories, and discard certain information
* which is not intended to be significant to applications. If your
* application requires more sophisticated use of DOM, you may need
* to use SAX directly with an <em>XmlDocumentBuilder</em>.
*
* <P> Instances are factories for their subsidiary nodes, but applications
* may provide their own element factory to bind element tags to particular
* DOM implementation classes (which must subclass ElementNode). For
* example, a factory may use a set of classes which support the HTML DOM
* methods, or which support methods associated with XML vocabularies for
* specialized problem domains as found within Internet Commerce systems.
* For example, an element tag <code>&lt;PurchaseOrder&gt;</code>
* could be mapped to a <code>com.startup.commerce.PurchaseOrder</code>
* class. The factory can also use XML Namespace information, if desired.
*
* <P> Since DOM requires nodes to be owned exclusively by one document,
* they can't be moved from one document to another using DOM APIs. This
* class provides an <em>changeNodeOwner</em> functionality which may be
* used to change the document associated with a node, and with any of its
* children.
*
* <P> <em> Only the core DOM model is supported here, not the HTML support.
* Such support basically adds a set of convenience element types, and so
* can be implemented through element factories and document subclasses.</em>
*
* @see XmlDocumentBuilder
*
* @author David Brownell
* @author Rajiv Mordani
* @version $Revision$
*/
public class XmlDocument extends ParentNode
implements DocumentEx, DOMImplementation
{
// package private (with jdk 1.1 'javac' bug workaround)
static /* final */ String eol;
static {
String temp;
try { temp = System.getProperty ("line.separator", "\n"); }
catch (SecurityException e) { temp = "\n"; }
eol = temp;
}
static final MessageCatalog catalog = new Catalog ();
private Locale locale = Locale.getDefault ();
private String systemId;
private ElementFactory factory;
// package private
int mutationCount;
boolean replaceRootElement;
/** Constructs an empty document object. */
public XmlDocument ()
{
}
/**
* Construct an XML document from the data at the specified URI,
* optionally validating. This uses validating parser if
* validation is requested, otherwise uses non-validating
* parser. XML Namespace conformance is not tested when parsing.
*
* @param documentURI The URI (normally URL) of the document
* @param doValidate If true, validity errors are treated as fatal
*
* @exception IOException as appropriate
* @exception SAXException as appropriate
* @exception SAXParseException (with line number information)
* for parsing errors
* @exception IllegalStateException at least when the parser
* is configured incorrectly
*/
public static XmlDocument createXmlDocument (
String documentURI,
boolean doValidate
) throws IOException, SAXException
{
return createXmlDocument (new InputSource (documentURI), doValidate);
}
/**
* Construct an XML document from the data at the specified URI,
* using the nonvalidating parser. XML Namespace conformance
* is not tested when parsing.
*
* @param documentURI The URI (normally URL) of the document
*
* @exception IOException as appropriate
* @exception SAXException as appropriate
* @exception SAXParseException (with line number information)
* for parsing errors
* @exception IllegalStateException at least when the parser
* is configured incorrectly
*/
public static XmlDocument createXmlDocument (String documentURI)
throws IOException, SAXException
{
return createXmlDocument (new InputSource (documentURI), false);
}
/**
* Construct an XML document from input stream, optionally validating.
* This document must not require interpretation of relative URLs,
* since the base URL is not known. This uses the validating parser
* if validation is requested, otherwise uses the non-validating
* parser. XML Namespace conformance is not tested when parsing.
*
* @param in Holds xml document
* @param doValidate If true, validity errors are treated as fatal
*
* @exception IOException as appropriate
* @exception SAXException as appropriate
* @exception SAXParseException (with line number information)
* for parsing errors
* @exception IllegalStateException at least when the parser
* is configured incorrectly
*/
public static XmlDocument createXmlDocument (
InputStream in,
boolean doValidate
) throws IOException, SAXException
{
return createXmlDocument (new InputSource (in), doValidate);
}
/**
* Construct an XML document from the data in the specified input
* source, optionally validating. This uses the validating parser
* if validation is requested, otherwise uses the non-validating
* parser. XML Namespace conformance is not tested when parsing.
*
* @param in The input source of the document
* @param doValidate If true, validity errors are treated as fatal
*
* @exception IOException as appropriate
* @exception SAXException as appropriate
* @exception SAXParseException (with line number information)
* for parsing errors
* @exception IllegalStateException at least when the parser
* is configured incorrectly
*/
public static XmlDocument createXmlDocument(InputSource in,
boolean validate)
throws IOException, SAXException
{
//XMLReader xmlReader = XMLReaderFactory.createXMLReader();
XMLReader xmlReader = new SAXParser ();
String namespaces = "http://xml.org/sax/features/namespaces";
xmlReader.setFeature(namespaces, true);
String nsPrefixes = "http://xml.org/sax/features/namespace-prefixes";
xmlReader.setFeature(nsPrefixes, true);
// Validation
if (validate) {
xmlReader.setFeature(
"http://xml.org/sax/features/validation", true);
}
// XmlDocumentBuilder is a ContentHandler
XmlDocumentBuilder builder = new XmlDocumentBuilder();
builder.setDisableNamespaces(false);
// The main ContentHandler
xmlReader.setContentHandler(builder);
// org.xml.sax.ext.LexicalHandler
String lexHandler = "http://xml.org/sax/properties/lexical-handler";
xmlReader.setProperty(lexHandler, builder);
// Object prop = xmlReader.getProperty(lexHandler);
// System.out.println("LexicalHandler=" + prop);
// org.xml.sax.ext.DeclHandler
String declHandler
= "http://xml.org/sax/properties/declaration-handler";
xmlReader.setProperty(declHandler, builder);
// prop = xmlReader.getProperty(declHandler);
// System.out.println("DeclHandler=" + prop);
// DTDHandler
xmlReader.setDTDHandler(builder);
xmlReader.setEntityResolver (new Resolver ());
// Parse the input
xmlReader.parse(in);
return builder.getDocument();
}
/*
* Returns the top level document element.
* This is a convenience function in the big picture;
* use the DOM version instead, at least for now.
*/
// package private
ElementNode getDocument ()
{
// We ignore comments, PIs, whitespace, etc
// and return the first (only!) element.
for (int i = 0; true; i++) {
Node n = item (i);
if (n == null)
return null;
if (n instanceof ElementNode)
return (ElementNode) n;
}
}
/**
* Returns the locale to be used for diagnostic messages.
*/
public Locale getLocale ()
{ return locale; }
/**
* Assigns the locale to be used for diagnostic messages.
* Multi-language applications, such as web servers dealing with
* clients from different locales, need the ability to interact
* with clients in languages other than the server's default.
* When an XmlDocument is created, its locale is the default
* locale for the virtual machine.
*
* @see #chooseLocale
*/
public void setLocale (Locale locale)
{
if (locale == null)
locale = Locale.getDefault ();
this.locale = locale;
}
/**
* Chooses a client locale to use for diagnostics, using the first
* language specified in the list that is supported by this DOM
* implementation. That locale is then automatically assigned using <a
* href="#setLocale(java.util.Locale)">setLocale()</a>. Such a list
* could be provided by a variety of user preference mechanisms,
* including the HTTP <em>Accept-Language</em> header field.
*
* @see org.apache.xerces.tree.MessageCatalog
*
* @param languages Array of language specifiers, ordered with the most
* preferable one at the front. For example, "en-ca" then "fr-ca",
* followed by "zh_CN". Both RFC 1766 and Java styles are supported.
* @return The chosen locale, or null.
*/
public Locale chooseLocale (String languages [])
{
Locale l = catalog.chooseLocale (languages);
if (l != null)
setLocale (l);
return l;
}
/**
* Writes the document in UTF-8 character encoding, as a well formed
* XML construct.
*
* @param out stream on which the document will be written
*/
public void write (OutputStream out) throws IOException
{
Writer writer = new OutputStreamWriter (out, "UTF8");
write (writer, "UTF-8");
}
/**
* Writes the document as a well formed XML construct. If the
* encoding can be determined from the writer, that is used in
* the document's XML declaration. The encoding name may first
* be transformed from a Java-internal form to a standard one;
* for example, Java's "UTF8" is the standard "UTF-8".
*
* <P> <em>Use of UTF-8 (or UTF-16) OutputStreamWriters is strongly
* encouraged. </em> All other encodings may lose critical data,
* since the standard Java output writers substitute characters
* such as the question mark for data which they can't encode in
* the current output encoding. The IETF and other organizations
* strongly encourage the use of UTF-8; also, all XML processors
* are guaranteed to support it.
*
* @see #write(java.io.Writer,java.lang.String)
*
* @param out stream on which the document will be written
*/
public void write (Writer out) throws IOException
{
String encoding = null;
if (out instanceof OutputStreamWriter)
encoding = java2std (((OutputStreamWriter)out).getEncoding ());
write (out, encoding);
}
//
// Try some of the common conversions from Java's internal names
// (which must fit in class names) to standard ones understood by
// most other code. We use the IETF's preferred names; case is
// supposed to be ignored, note.
//
// package private
static String java2std (String encodingName)
{
if (encodingName == null)
return null;
//
// ISO-8859-N is a common family of 8 bit encodings;
// N=1 is the eight bit subset of UNICODE, and there
// seem to be at least drafts for some N >10.
//
if (encodingName.startsWith ("ISO8859_")) // JDK 1.2
return "ISO-8859-" + encodingName.substring (8);
if (encodingName.startsWith ("8859_")) // JDK 1.1
return "ISO-8859-" + encodingName.substring (5);
// XXX seven bit encodings ISO-2022-* ...
// XXX EBCDIC encodings ...
if ("ASCII7".equalsIgnoreCase (encodingName)
|| "ASCII".equalsIgnoreCase (encodingName))
return "US-ASCII";
//
// All XML parsers _must_ support UTF-8 and UTF-16.
// (UTF-16 ~= ISO-10646-UCS-2 plus surrogate pairs)
//
if ("UTF8".equalsIgnoreCase (encodingName))
return "UTF-8";
if (encodingName.startsWith ("Unicode"))
return "UTF-16";
//
// Some common Japanese character sets.
//
if ("SJIS".equalsIgnoreCase (encodingName))
return "Shift_JIS";
if ("JIS".equalsIgnoreCase (encodingName))
return "ISO-2022-JP";
if ("EUCJIS".equalsIgnoreCase (encodingName))
return "EUC-JP";
// else we can't really do anything
return encodingName;
}
/**
* Writes the document in the specified encoding, and listing
* that encoding in the XML declaration. The document will be
* well formed XML; at this time, it will not additionally be
* valid XML or standalone, since it includes no document type
* declaration.
*
* <P> Note that the document will by default be "pretty printed".
* Extra whitespace is added to indent children of elements according
* to their level of nesting, unless those elements have (or inherit)
* the <em>xml:space='preserve'</em> attribute value. This space
* will be removed if, when the document is read back with DOM, a
* call to <em>ElementNode.normalize</em> is made. To avoid this
* pretty printing, use a write context configured to disable it,
* or explicitly assign an <em>xml:space='preserve'</em> attribute to
* the root node of your document.
*
* <P> Also, if a SAX parser was used to construct this tree, data
* will have been discarded. Most of that will be insignificant in
* terms of a "logical" view of document data: comments, whitespace
* outside of the top level element, the exact content of the XML
* directive, and entity references were expanded. However, <em>if a
* DOCTYPE declaration was provided, it was also discarded</em>.
* Such declarations will often be logically significant, due to the
* attribute value defaulting and normalization they can provide.
*
* <P> In general, DOM does not support "round tripping" data from
* XML to DOM and back without losing data about physical structures
* and DTD information. "Logical structure" will be preserved.
*
* @see #setDoctype
* @see #writeXml
*
* @param out the writer to use when writing the document
* @param encoding the encoding name to use; this should be a
* standard encoding name registered with the IANA (like "UTF-8")
* not a Java-internal name (like "UTF8").
*/
public void write (Writer out, String encoding)
throws IOException
{
//
// We put a pretty minimal declaration here, which is the
// best we can do given SAX input and DOM. For the moment
// this precludes our generating "standalone" annotations.
//
out.write ("<?xml version=\"1.0\"");
if (encoding != null) {
out.write (" encoding=\"");
out.write (encoding);
out.write ('\"');
}
out.write ("?>");
out.write (eol);
out.write (eol);
writeChildrenXml (createWriteContext (out, 0));
out.write (eol);
out.flush ();
}
/**
* Returns an XML write context set up not to pretty-print,
* and which knows about the entities defined for this document.
*
* @param out stream on which the document will be written
*/
public XmlWriteContext createWriteContext (Writer out)
{
return new ExtWriteContext (out);
}
/**
* Returns an XML write context which pretty-prints output starting
* at a specified indent level, and which knows about the entities
* defined for this document.
*
* @param out stream on which the document will be written
* @param level initial indent level for pretty-printing
*/
public XmlWriteContext createWriteContext (Writer out, int level)
{
return new ExtWriteContext (out, level);
}
/**
* Writes the document out using the specified context, using
* an encoding name derived from the stream in the context where
* that is possible.
*
* @see #createWriteContext(java.io.Writer)
* @see #createWriteContext(java.io.Writer,int)
*
* @param context describes how to write the document
*/
public void writeXml (XmlWriteContext context) throws IOException
{
Writer out = context.getWriter ();
String encoding = null;
//
// XXX as above, it should be possible to be "told" this name
// in order to use more standard names. We can pretty print,
// or we can use the right encoding name; not both!!
//
if (out instanceof OutputStreamWriter)
encoding = java2std (((OutputStreamWriter)out).getEncoding ());
//
// We put a pretty minimal declaration here, which is the
// best we can do given SAX input and DOM. For the moment
// this precludes our generating "standalone" annotations.
//
out.write ("<?xml version=\"1.0\"");
if (encoding != null) {
out.write (" encoding=\"");
out.write (encoding);
out.write ('\"');
}
out.write ("?>");
out.write (eol);
out.write (eol);
writeChildrenXml (context);
}
/**
* Writes all the child nodes of the document, following each one
* with the end-of-line string in use in this environment.
*/
public void writeChildrenXml (XmlWriteContext context) throws IOException
{
int length = getLength ();
Writer out = context.getWriter ();
if (length == 0)
return;
for (int i = 0; i < length; i++) {
((NodeBase)item (i)).writeXml (context);
out.write (eol);
}
}
// package private -- overrides base class method
void checkChildType (int type)
throws DOMException
{
switch (type) {
case ELEMENT_NODE:
case PROCESSING_INSTRUCTION_NODE:
case COMMENT_NODE:
case DOCUMENT_TYPE_NODE:
return;
default:
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
}
}
/**
* Assigns the URI associated with the document, which is its
* system ID.
*
* @param uri The document's system ID, as used when storing
* the document.
*/
final public void setSystemId (String uri)
{
systemId = uri;
}
/**
* Returns system ID associated with the document, or null if
* this is unknown.
*
* <P> This URI should not be used when interpreting relative URIs,
* since the document may be partially stored in external parsed
* entities with different base URIs. Instead, use methods in the
* <em>XmlReadable</em> interface as the document is being parsed,
* so that the correct base URI is available.
*/
final public String getSystemId ()
{
return systemId;
}
// DOM support
/**
* DOM: Appends the specified child node to the document. Only one
* element or document type node may be a child of a document.
*
* @param node the node to be appended.
*/
public Node appendChild (Node n)
throws DOMException
{
if (n instanceof Element && getDocument () != null)
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
if (n instanceof DocumentType && getDoctype () != null)
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
return super.appendChild (n);
}
/**
* DOM: Inserts the specified child node into the document. Only one
* element or document type node may be a child of a document.
*
* @param n the node to be inserted.
* @param refNode the node before which this is to be inserted
*/
public Node insertBefore (Node n, Node refNode)
throws DOMException
{
if (!replaceRootElement && n instanceof Element &&
getDocument () != null)
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
if (!replaceRootElement && n instanceof DocumentType
&& getDoctype () != null)
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
return super.insertBefore (n, refNode);
}
/**
* <b>DOM:</b> Replaces the specified child with the new node,
* returning the original child or throwing an exception.
* The new child must belong to this particular document.
*
* @param newChild the new child to be inserted
* @param refChild node which is to be replaced
*/
public Node replaceChild (Node newChild, Node refChild)
throws DOMException
{
if (newChild instanceof DocumentFragment ) {
int elemCount = 0;
int docCount = 0;
replaceRootElement = false;
ParentNode frag = (ParentNode) newChild;
Node temp;
int i = 0;
while ((temp = frag.item (i)) != null) {
if (temp instanceof Element)
elemCount++;
else if (temp instanceof DocumentType)
docCount++;
i++;
}
if (elemCount > 1 || docCount > 1)
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
else
replaceRootElement = true;
}
return super.replaceChild (newChild, refChild);
}
/** DOM: Returns the DOCUMENT_NODE node type constant. */
final public short getNodeType () { return DOCUMENT_NODE; }
/** DOM: returns the document type (DTD) */
final public DocumentType getDoctype ()
{
// We ignore comments, PIs, whitespace, etc
// and return the first (only!) doctype.
for (int i = 0; true; i++) {
Node n = item (i);
if (n == null)
return null;
if (n instanceof DocumentType)
return (DocumentType) n;
}
}
// package private!!
Doctype createDoctype (String name)
{
Doctype retval = new Doctype (name);
retval.setOwnerDocument (this);
return retval;
}
/**
* Establishes how the document prints its document type. If a system
* ID (URI) is provided, that is used in a SYSTEM (or PUBLIC, if a public
* ID is also provided) declaration. If an internal subset is provided,
* that will be printed. The root element in the DTD will be what the
* document itself provides.
*
* @param dtdPublicId Holds a "public identifier" used to identify the
* last part of the external DTD subset that is read into the DTD.
* This may be omitted, and in any case is ignored unless a system
* ID is provided.
* @param dtdSystemId Holds a "system identifier" (a URI) used to
* identify the last part of the external DTD subset that is read
* into the DTD. This may be omitted, in which case the document
* type will contain at most an internal subset. This URI should
* not be a relative URI unless the document will be accessed in a
* context from which that relative URI makes sense.
* @param internalSubset Optional; this holds XML text which will
* be put into the internal subset. This must be legal syntax,
* and it is not tested by this document.
*/
public DocumentType setDoctype (
String dtdPublicId,
String dtdSystemId,
String internalSubset
) {
Doctype retval = (Doctype) getDoctype ();
if (retval != null)
retval.setPrintInfo (dtdPublicId, dtdSystemId,
internalSubset);
else {
retval = new Doctype (dtdPublicId, dtdSystemId,
internalSubset);
retval.setOwnerDocument (this);
insertBefore (retval, getFirstChild ());
}
return retval;
}
/** DOM: Returns the content root element. */
public Element getDocumentElement ()
{ return getDocument (); }
/**
* Assigns the element factory to be used by this document.
*
* @param factory the element factory to be used; if this is null,
* all elements will be implemented by <em>ElementNode</em>.
*/
final public void setElementFactory (ElementFactory factory)
{
this.factory = factory;
}
/**
* Returns the element factory to be used by this document.
*/
final public ElementFactory getElementFactory ()
{
return factory;
}
/**
* DOM: Create a new element, associated with this document, with
* no children, attributes, or parent, by calling createElementEx.
*
* @param tagName the tag of the element, used to determine what
* type element to create as well as what tag to assign the new node.
* @exception IllegalArgumentException if a mapping is defined,
* but is invalid because the element can't be instantiated or
* does not subclass <em>ElementNode</em>.
*/
final public Element createElement (String tagName)
throws DOMException
{
return createElementEx (tagName);
}
/**
* <b>DOM2:</b>
* @since DOM Level 2
* XXX Does not work with ElementFactory
*/
public Element createElementNS(String namespaceURI, String qualifiedName)
throws DOMException
{
ElementNode retval = new ElementNode(namespaceURI, qualifiedName);
retval.setOwnerDocument(this);
return retval;
}
/**
* Create a new element, associated with this document, with no
* children, attributes, or parent. This uses the element factory,
* or else directly constructs an ElementNode.
*
* @param tagName the tag of the element, used to determine what
* type element to create as well as what tag to assign the new node.
* @exception IllegalArgumentException if a mapping is defined,
* but is invalid because the element can't be instantiated or
* does not subclass <em>ElementNode</em>.
*/
final public ElementEx createElementEx (String tagName)
throws DOMException
{
ElementNode retval;
if (!XmlNames.isName (tagName))
throw new DomEx (DomEx.INVALID_CHARACTER_ERR);
if (factory != null)
retval = (ElementNode) factory.createElementEx (tagName);
else
retval = new ElementNode ();
retval.setTag (tagName);
retval.setOwnerDocument (this);
return retval;
}
/**
* Create a new element, associated with this document, with no
* children, attributes, or parent. This uses the element factory,
* or else directly constructs an ElementNode.
*
* @param uri The namespace used to determine what type of element to
* create. This is not stored with the element; the element must be
* inserted into a DOM tree in a location where namespace declarations
* cause its tag to be interpreted correctly.
* @param tagName The tag of the element, which should not contain
* any namespace prefix.
* @exception IllegalArgumentException When a mapping is defined,
* but is invalid because the element can't be instantiated or
* does not subclass <em>ElementNode</em>.
*/
final public ElementEx createElementEx (String uri, String tagName)
throws DOMException
{
ElementNode retval;
if (!XmlNames.isName (tagName))
throw new DomEx (DomEx.INVALID_CHARACTER_ERR);
if (factory != null)
retval = (ElementNode) factory.createElementEx (uri, tagName);
else
retval = new ElementNode ();
retval.setTag (tagName);
retval.setOwnerDocument (this);
return retval;
}
/**
* DOM: returns a Text node initialized with the given text.
*
* @param text The contents of the text node being created, which
* should never contain "<em>]]&gt;</em>".
*/
public Text createTextNode (String text)
{
TextNode retval;
retval = new TextNode ();
retval.setOwnerDocument (this);
if (text != null)
retval.setText (text.toCharArray ());
return retval;
}
/**
* DOM: Returns a CDATA section initialized with the given text.
*
* @param text the text which the CDATA section will hold, which
* should never contain "<em>]]&gt;</em>".
*/
public CDATASection createCDATASection (String text)
{
CDataNode retval = new CDataNode ();
if (text != null)
retval.setText (text.toCharArray ());
retval.setOwnerDocument (this);
return retval;
}
// package private ... convenience rtn, reduced mallocation
TextNode newText (char buf [], int offset, int len)
throws SAXException
{
TextNode retval = (TextNode) createTextNode (null);
char data [] = new char [len];
System.arraycopy (buf, offset, data, 0, len);
retval.setText (data);
return retval;
}
/**
* DOM: Returns a Processing Instruction node for the specified
* processing target, with the given instructions.
*
* @param target the target of the processing instruction
* @param instructions the processing instruction, which should
* never contain "<em>?&gt;</em>".
*/
public ProcessingInstruction createProcessingInstruction (
String target,
String instructions
) throws DOMException
{
if (!XmlNames.isName (target))
throw new DomEx (DomEx.INVALID_CHARACTER_ERR);
PINode retval = new PINode (target, instructions);
retval.setOwnerDocument (this);
return retval;
}
/**
* DOM: Returns a valueless attribute node with no default value.
*
* @param name the name of the attribute.
*/
public Attr createAttribute (String name)
throws DOMException
{
if (!XmlNames.isName (name))
throw new DomEx (DomEx.INVALID_CHARACTER_ERR);
AttributeNode retval = new AttributeNode (name, null, true, null);
retval.setOwnerDocument (this);
return retval;
}
/**
* <b>DOM2:</b>
* @since DOM Level 2
*/
public Attr createAttributeNS(String namespaceURI,
String qualifiedName)
throws DOMException
{
// XXX do some checking here
AttributeNode retval = new AttributeNode(qualifiedName, null, true,
null);
retval.setOwnerDocument(this);
return retval;
}
/**
* DOM: creates a comment node.
*
* @param data The characters which will be in the comment.
* This should not include the "<em>--</em>" characters.
*/
public Comment createComment (String data)
{
CommentNode retval = new CommentNode (data);
retval.setOwnerDocument (this);
return retval;
}
/** DOM: returns null. */
public Document getOwnerDoc ()
{
return null;
}
/**
* DOM: Returns an object describing the features of the DOM implementation
* used by this document: it supports the XML 1.0 feature set.
*/
public DOMImplementation getImplementation ()
{ return this; }
/**
* DOM: Creates a new document fragment.
*/
public DocumentFragment createDocumentFragment ()
{
DocFragNode retval = new DocFragNode ();
retval.setOwnerDocument (this);
return retval;
}
/**
* DOM: Creates an entity reference to the named entity.
* Note that the entity must already be defined in the document
* type, and that the name must be a legal entity name.
*
* @param name the name of the the parsed entity
*/
public EntityReference createEntityReference (String name)
throws DOMException
{
if (!XmlNames.isName (name))
throw new DomEx (DomEx.INVALID_CHARACTER_ERR);
EntityRefNode retval = new EntityRefNode (name);
retval.setOwnerDocument (this);
return retval;
}
/** DOM: Returns the string "#document". */
final public String getNodeName () { return "#document"; }
/**
* DOM: Returns a copy of this document.
*
* <P> <em>Note:</em> At this time, any element factory or document
* type associated with this document will not be cloned.
*
* @param deep if true, child nodes are also cloned.
*/
public Node cloneNode (boolean deep)
{
XmlDocument retval = new XmlDocument ();
retval.systemId = systemId;
// XXX clone the element factory ...
if (deep) {
Node node;
for (int i = 0; (node = item (i)) != null; i++) {
if (node instanceof DocumentType) {
// XXX recreate
continue;
}
node = node.cloneNode (true);
retval.changeNodeOwner (node);
retval.appendChild (node);
}
}
return retval;
}
/**
* Changes the "owner document" of the given node, and all child
* and associated attribute nodes, to be this document. If the
* node has a parent, it is first removed from that parent.
*
* @param node
* @exception DOMException WRONG_DOCUMENT_ERROR when attempting
* to change the owner for some other DOM implementation<P>
* HIERARCHY_REQUEST_ERROR when the node is a document, document
* type, entity, or notation; or when it is an attribute associated
* with an element whose owner is not being (recursively) changed.
*/
final public void changeNodeOwner (Node node)
throws DOMException
{
TreeWalker walker;
NodeBase n;
if (node.getOwnerDocument () == this)
return;
if (!(node instanceof NodeBase))
throw new DomEx (DomEx.WRONG_DOCUMENT_ERR);
switch (node.getNodeType ()) {
// Documents _are_ owners; can't switch identities
case Node.DOCUMENT_NODE:
// Entities, Notations only live in doctypes ... we
// don't support changing their ownership at this time
case Node.ENTITY_NODE:
case Node.NOTATION_NODE:
case Node.DOCUMENT_TYPE_NODE:
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
}
//
// If node is an attribute, its "scoped" by one element...
// and if that scope hasn't been changed (i.e. if this isn't
// a recursive call) we can't really fix anything!
//
if (node instanceof AttributeNode) {
AttributeNode attr = (AttributeNode) node;
ElementNode scope = attr.getNameScope ();
if (scope != null && scope.getOwnerDocument () != this)
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
}
// unparent node if needed
n = (NodeBase) node.getParentNode ();
if (n != null)
n.removeChild (node);
// change any children (including self)
for (walker = new TreeWalker (node),
n = (NodeBase) walker.getCurrent ();
n != null;
n = (NodeBase) walker.getNext ()) {
n.setOwnerDocument (this);
// Elements have associated attributes, which must
// also have owners changed.
if (n instanceof ElementNode) {
NamedNodeMap list = n.getAttributes ();
int length = list.getLength ();
for (int i = 0; i < length; i++)
changeNodeOwner (list.item (i));
}
}
}
/**
* Reports on features that this implementation supports. Allows code to
* be shared with NodeBase.supports().
*/
static boolean hasFeature0(String feature, String version) {
if (!"XML".equalsIgnoreCase(feature)) {
return false;
}
if (version.equalsIgnoreCase("2.0") || version.equalsIgnoreCase("1.0")
|| version == null) {
return true;
}
return false;
}
/**
* DOM: Reports on features that this document supports
*
* @param feature identifies a feature of this DOM implementation;
* case is ignored
* @param version optionally identifies the version of the feature
*/
public boolean hasFeature(String feature, String version) {
return hasFeature0(feature, version);
}
/**
* <b>DOM2:</b>
* @since DOM Level 2
*/
public DocumentType createDocumentType(String qualifiedName,
String publicId,
String systemId,
String internalSubset)
{
return new Doctype(qualifiedName, publicId, systemId, internalSubset);
}
public DocumentType createDocumentType(String qualifiedName,
String publicId,
String systemId)
{
return createDocumentType (qualifiedName, publicId, systemId, null);
}
/**
* <b>DOM2:</b>
* @since DOM Level 2
*/
public Document createDocument(String namespaceURI,
String qualifiedName,
DocumentType doctype)
throws DOMException
{
// Create document and append DocumentType to it
// Note: WRONG_DOCUMENT_ERR is checked by appendChild()
Document doc = new XmlDocument();
doc.appendChild(doctype);
// Create document element and append it
// Note: name exceptions are checked by createElementNS()
Element docElement = doc.createElementNS(namespaceURI, qualifiedName);
doc.appendChild(docElement);
return doc;
}
/**
* <b>DOM2:</b>
* @since DOM Level 2
*/
public Element getElementById(String elementId) {
return getElementExById(elementId);
}
/**
* Returns the element whose ID is given by the parameter; or null
* if no such element exists. This relies on elements to know the
* name of their ID attribute, as will be currently be true only if
* the document has been parsed from XML text with a DTD using the
* <em>XmlDocumentBuilder</em> class, or if it has been constructed
* using specialized DOM implementation classes which know the name
* of their ID attribute. (XML allows only one ID attribute per
* element, and different elements may use different names for their
* ID attributes.)
*
* <P> This may be used to implement internal IDREF linkage, as well
* as some kinds of <em>XPointer</em> linkage as used in current
* drafts of <em>XLink</em>.
*
* @param id The value of the ID attribute which will be matched
* by any element which is returned.
*/
// Note: HTML DOM has getElementById() with "Element" return type
public ElementEx getElementExById (String id)
{
if (id == null)
throw new IllegalArgumentException (getMessage ("XD-000"));
TreeWalker w = new TreeWalker (this);
ElementEx element;
while ((element = (ElementEx) w.getNextElement (null)) != null) {
String idAttr = element.getIdAttributeName ();
String value;
if (idAttr == null)
continue;
value = element.getAttribute (idAttr);
if (value.equals (id))
return element;
}
return null;
}
/**
* @since DOM Level 2
* XXX Fix to better conform to spec. cloneNode() also needs to be fixed.
*/
public Node importNode(Node importedNode, boolean deep)
throws DOMException
{
Node retval = null;
switch (importedNode.getNodeType()) {
case DOCUMENT_NODE:
case DOCUMENT_TYPE_NODE:
throw new DomEx(DomEx.NOT_SUPPORTED_ERR);
default:
retval = importedNode.cloneNode(deep);
}
((NodeBase)retval).setOwnerDocument(this);
return retval;
}
//
// Represent document fragments other than the document itself.
// (This class is primarily motivated for use with editors.)
//
static final class DocFragNode extends ParentNode
implements DocumentFragment
{
// package private -- overrides base class method
void checkChildType (int type)
throws DOMException
{
switch (type) {
case ELEMENT_NODE:
case PROCESSING_INSTRUCTION_NODE:
case COMMENT_NODE:
case TEXT_NODE:
case CDATA_SECTION_NODE:
case ENTITY_REFERENCE_NODE:
return;
default:
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
}
}
public void writeXml (XmlWriteContext context) throws IOException
{
this.writeChildrenXml (context);
}
public Node getParentNode ()
{ return null; }
public void setParentNode (Node p)
{ if (p != null) throw new IllegalArgumentException (); }
public short getNodeType ()
{ return DOCUMENT_FRAGMENT_NODE; }
public String getNodeName () {
return ("#document-fragment");
}
public Node cloneNode (boolean deep)
{
DocFragNode retval = new DocFragNode ();
((NodeBase)retval).setOwnerDocument
((XmlDocument)this.getOwnerDocument ());
if (deep) {
Node node;
for (int i = 0; (node = item (i)) != null; i++) {
node = node.cloneNode (true);
retval.appendChild (node);
}
}
return retval;
}
}
//
// Represent entity references.
//
final static class EntityRefNode extends ParentNode
implements EntityReference
{
private String entity;
EntityRefNode (String name)
{
if (name == null)
throw new IllegalArgumentException (getMessage ("XD-002"));
entity = name;
}
// package private -- overrides base class method
void checkChildType (int type)
throws DOMException
{
switch (type) {
case ELEMENT_NODE:
case PROCESSING_INSTRUCTION_NODE:
case COMMENT_NODE:
case TEXT_NODE:
case CDATA_SECTION_NODE:
case ENTITY_REFERENCE_NODE:
return;
default:
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
}
}
public void writeXml (XmlWriteContext context)
throws IOException
{
if (!context.isEntityDeclared (entity))
throw new IOException (getMessage ("XD-003", new Object[]
{ entity }));
Writer out = context.getWriter ();
out.write ('&');
out.write (entity);
out.write (';');
}
public short getNodeType ()
{ return ENTITY_REFERENCE_NODE; }
public String getNodeName ()
{ return entity; }
public Node cloneNode (boolean deep) {
EntityRefNode retval = new EntityRefNode (entity);
((NodeBase)retval).setOwnerDocument((
XmlDocument)this.getOwnerDocument ());
if (deep) {
Node node;
for (int i = 0; (node = item (i)) != null; i++) {
node = node.cloneNode (true);
retval.appendChild (node);
}
// XXX
//throw new RuntimeException (getMessage ("XD-001"));
}
return retval;
}
}
class ExtWriteContext extends XmlWriteContext
{
ExtWriteContext (Writer out) { super (out); }
ExtWriteContext (Writer out, int level) { super (out, level); }
public boolean isEntityDeclared (String name)
{
if (super.isEntityDeclared (name))
return true;
DocumentType doctype = getDoctype ();
if (doctype == null)
return false;
else
return doctype.getEntities ().getNamedItem (name) != null;
}
}
static class Catalog extends MessageCatalog
{
Catalog () { super (Catalog.class); }
}
}