org.htmlunit.cyberneko.xerces.dom.ElementImpl Maven / Gradle / Ivy
/*
* Copyright (c) 2017-2024 Ronald Brill
*
* 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
* https://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.
*/
package org.htmlunit.cyberneko.xerces.dom;
import org.htmlunit.cyberneko.xerces.util.URI;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.TypeInfo;
/**
* Elements represent most of the "markup" and structure of the document. They
* contain both the data for the element itself (element name and attributes),
* and any contained nodes, including document text (as children).
*
* Elements may have Attributes associated with them; the API for this is
* defined in Node, but the function is implemented here. In general, XML
* applications should retrive Attributes as Nodes, since they may contain
* entity references and hence be a fairly complex sub-tree. HTML users will be
* dealing with simple string values, and convenience methods are provided to
* work in terms of Strings.
*
* ElementImpl does not support Namespaces. ElementNSImpl, which inherits from
* it, does.
*
*
* @see ElementNSImpl
*
* @author Arnaud Le Hors, IBM
* @author Joe Kesselman, IBM
* @author Andy Clark, IBM
* @author Ralf Pfeiffer, IBM
* @author Michael Glavassevich, IBM
*/
public class ElementImpl extends ParentNode implements Element, TypeInfo {
/** Element name. */
protected String name_;
/** Attributes. */
protected AttributeMap attributes_;
// Factory constructor.
public ElementImpl(final CoreDocumentImpl ownerDoc, final String name) {
super(ownerDoc);
name_ = name;
}
// Support for DOM Level 3 renameNode method.
// Note: This only deals with part of the pb. CoreDocumentImpl
// does all the work.
void rename(final String name) {
if (ownerDocument.errorChecking) {
final int colon1 = name.indexOf(':');
if (colon1 != -1) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NAMESPACE_ERR", null);
throw new DOMException(DOMException.NAMESPACE_ERR, msg);
}
if (!CoreDocumentImpl.isXMLName(name, ownerDocument.isXML11Version())) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null);
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg);
}
}
name_ = name;
}
/**
* {@inheritDoc}
*
* A short integer indicating what type of node this is. The named constants for
* this value are defined in the org.w3c.dom.Node interface.
*/
@Override
public short getNodeType() {
return Node.ELEMENT_NODE;
}
/**
* {@inheritDoc}
*
* Returns the element name
*/
@Override
public String getNodeName() {
return name_;
}
/**
* {@inheritDoc}
*
* Retrieve all the Attributes as a set. Note that this API is inherited from
* Node rather than specified on Element; in fact only Elements will ever have
* Attributes, but they want to allow folks to "blindly" operate on the tree as
* a set of Nodes.
*/
@Override
public NamedNodeMap getAttributes() {
if (attributes_ == null) {
attributes_ = new AttributeMap(this);
}
return attributes_;
}
/**
* {@inheritDoc}
*
* Return a duplicate copy of this Element. Note that its children will not be
* copied unless the "deep" flag is true, but Attributes are always
* replicated.
*
* @see org.w3c.dom.Node#cloneNode(boolean)
*/
@Override
public Node cloneNode(final boolean deep) {
final ElementImpl newnode = (ElementImpl) super.cloneNode(deep);
// Replicate NamedNodeMap rather than sharing it.
if (attributes_ != null) {
newnode.attributes_ = (AttributeMap) attributes_.cloneMap(newnode);
}
return newnode;
}
/**
* {@inheritDoc}
*
* DOM Level 3 WD - Experimental. Retrieve baseURI
*/
@Override
public String getBaseURI() {
// Absolute base URI is computed according to
// XML Base (http://www.w3.org/TR/xmlbase/#granularity)
// 1. The base URI specified by an xml:base attribute on the element,
// if one exists
if (attributes_ != null) {
final Attr attrNode = getXMLBaseAttribute();
if (attrNode != null) {
final String uri = attrNode.getNodeValue();
if (uri.length() != 0) { // attribute value is always empty string
try {
final URI _uri = new URI(uri, true);
// If the URI is already absolute return it; otherwise it's relative and we need
// to resolve it.
if (_uri.isAbsoluteURI()) {
return _uri.toString();
}
// Make any parentURI into a URI object to use with the URI(URI, String)
// constructor
final String parentBaseURI = (this.ownerNode_ != null) ? this.ownerNode_.getBaseURI() : null;
if (parentBaseURI != null) {
try {
final URI _parentBaseURI = new URI(parentBaseURI);
_uri.absolutize(_parentBaseURI);
return _uri.toString();
}
catch (final org.htmlunit.cyberneko.xerces.util.URI.MalformedURIException ex) {
// This should never happen: parent should have checked the URI and returned
// null if invalid.
return null;
}
}
// REVISIT: what should happen in this case?
return null;
}
catch (final org.htmlunit.cyberneko.xerces.util.URI.MalformedURIException ex) {
return null;
}
}
}
}
// 2.the base URI of the element's parent element within the
// document or external entity, if one exists
// 3. the base URI of the document entity or external entity
// containing the element
// ownerNode serves as a parent or as document
return (this.ownerNode_ != null) ? this.ownerNode_.getBaseURI() : null;
}
// NON-DOM Returns the xml:base attribute.
protected Attr getXMLBaseAttribute() {
return (Attr) attributes_.getNamedItem("xml:base");
}
// NON-DOM set the ownerDocument of this node, its children, and its attributes
@Override
protected void setOwnerDocument(final CoreDocumentImpl doc) {
super.setOwnerDocument(doc);
if (attributes_ != null) {
attributes_.setOwnerDocument(doc);
}
}
/**
* {@inheritDoc}
*
* Look up a single Attribute by name. Returns the Attribute's string value, or
* an empty string (NOT null!) to indicate that the name did not map to a
* currently defined attribute.
*
* Note: Attributes may contain complex node trees. This method returns the
* "flattened" string obtained from Attribute.getValue(). If you need the
* structure information, see getAttributeNode().
*/
@Override
public String getAttribute(final String name) {
if (attributes_ == null) {
return "";
}
final Attr attr = (Attr) (attributes_.getNamedItem(name));
return (attr == null) ? "" : attr.getValue();
}
/**
* {@inheritDoc}
*
* Look up a single Attribute by name. Returns the Attribute Node, so its
* complete child tree is available. This could be important in XML, where the
* string rendering may not be sufficient information.
*
* If no matching attribute is available, returns null.
*/
@Override
public Attr getAttributeNode(final String name) {
if (attributes_ == null) {
return null;
}
return (Attr) attributes_.getNamedItem(name);
}
/**
* {@inheritDoc}
*
* Returns a NodeList of all descendent nodes (children, grandchildren, and so
* on) which are Elements and which have the specified tag name.
*
* Note: NodeList is a "live" view of the DOM. Its contents will change as the
* DOM changes, and alterations made to the NodeList will be reflected in the
* DOM.
*
* @param tagname The type of element to gather. To obtain a list of all
* elements no matter what their names, use the wild-card tag
* name "*".
*
* @see DeepNodeListImpl
*/
@Override
public NodeList getElementsByTagName(final String tagname) {
return new DeepNodeListImpl(this, tagname);
}
/**
* {@inheritDoc}
*
* Returns the name of the Element. Note that Element.nodeName() is defined to
* also return the tag name.
*
* This is case-preserving in XML. HTML should uppercasify it on the way in.
*/
@Override
public String getTagName() {
return name_;
}
/**
* {@inheritDoc}
*
* Remove the named attribute from this Element. If the removed Attribute has a
* default value, it is immediately replaced thereby.
*
* The default logic is actually implemented in NamedNodeMapImpl.
* PR-DOM-Level-1-19980818 doesn't fully address the DTD, so some of this
* behavior is likely to change in future versions. ?????
*
* Note that this call "succeeds" even if no attribute by this name existed --
* unlike removeAttributeNode, which will throw a not-found exception in that
* case.
*/
@Override
public void removeAttribute(final String name) {
if (attributes_ == null) {
return;
}
attributes_.internalRemoveNamedItem(name, false);
}
/**
* {@inheritDoc}
*
* Remove the specified attribute/value pair. If the removed Attribute has a
* default value, it is immediately replaced.
*
* NOTE: Specifically removes THIS NODE -- not the node with this name, nor the
* node with these contents. If the specific Attribute object passed in is not
* stored in this Element, we throw a DOMException. If you really want to remove
* an attribute by name, use removeAttribute().
*
* @return the Attribute object that was removed.
* @throws DOMException NOT_FOUND_ERR if oldattr is not an attribute of this
* Element.
*/
@Override
public Attr removeAttributeNode(final Attr oldAttr) throws DOMException {
if (attributes_ == null) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
}
return (Attr) attributes_.removeItem(oldAttr);
}
/**
* {@inheritDoc}
*
* Add a new name/value pair, or replace the value of the existing attribute
* having that name.
*
* Note: this method supports only the simplest kind of Attribute, one whose
* value is a string contained in a single Text node. If you want to assert a
* more complex value (which XML permits, though HTML doesn't), see
* setAttributeNode().
*
* The attribute is created with specified=true, meaning it's an explicit value
* rather than inherited from the DTD as a default. Again, setAttributeNode can
* be used to achieve other results.
*
* @throws DOMException INVALID_NAME_ERR if the name is not acceptable.
* (Attribute factory will do that test for us.)
*/
@Override
public void setAttribute(final String name, final String value) {
Attr newAttr = getAttributeNode(name);
if (newAttr == null) {
newAttr = getOwnerDocument().createAttribute(name);
if (attributes_ == null) {
attributes_ = new AttributeMap(this);
}
newAttr.setNodeValue(value);
attributes_.setNamedItem(newAttr);
}
else {
newAttr.setNodeValue(value);
}
}
/**
* {@inheritDoc}
*
* Add a new attribute/value pair, or replace the value of the existing
* attribute with that name.
*
* This method allows you to add an Attribute that has already been constructed,
* and hence avoids the limitations of the simple setAttribute() call. It can
* handle attribute values that have arbitrarily complex tree structure -- in
* particular, those which had entity references mixed into their text.
*
* @throws DOMException INUSE_ATTRIBUTE_ERR if the Attribute object has already
* been assigned to another Element.
*/
@Override
public Attr setAttributeNode(final Attr newAttr) throws DOMException {
if (ownerDocument.errorChecking) {
if (newAttr.getOwnerDocument() != ownerDocument) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
}
}
if (attributes_ == null) {
attributes_ = new AttributeMap(this);
}
// This will throw INUSE if necessary
return (Attr) attributes_.setNamedItem(newAttr);
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*
*
* Retrieves an attribute value by local name and namespace URI.
*
* @param namespaceURI The namespace URI of the attribute to retrieve.
* @param localName The local name of the attribute to retrieve.
* @return String The Attr value as a string, or empty string if that attribute
* does not have a specified or default value.
*/
@Override
public String getAttributeNS(final String namespaceURI, final String localName) {
if (attributes_ == null) {
return "";
}
final Attr attr = (Attr) (attributes_.getNamedItemNS(namespaceURI, localName));
return (attr == null) ? "" : attr.getValue();
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*
*
* Adds a new attribute. If the given namespaceURI is null or an empty string
* and the qualifiedName has a prefix that is "xml", the new attribute is bound
* to the predefined namespace "http://www.w3.org/XML/1998/namespace"
* [Namespaces]. If an attribute with the same local name and namespace URI is
* already present on the element, its prefix is changed to be the prefix part
* of the qualifiedName, and its value is changed to be the value parameter.
* This value is a simple string, it is not parsed as it is being set. So any
* markup (such as syntax to be recognized as an entity reference) is treated as
* literal text, and needs to be appropriately escaped by the implementation
* when it is written out. In order to assign an attribute value that contains
* entity references, the user must create an Attr node plus any Text and
* EntityReference nodes, build the appropriate subtree, and use
* setAttributeNodeNS or setAttributeNode to assign it as the value of an
* attribute.
*
* @param namespaceURI The namespace URI of the attribute to create or alter.
* @param qualifiedName The qualified name of the attribute to create or alter.
* @param value The value to set in string form.
*/
@Override
public void setAttributeNS(final String namespaceURI, final String qualifiedName, final String value) {
final int index = qualifiedName.indexOf(':');
final String prefix;
final String localName;
if (index < 0) {
prefix = null;
localName = qualifiedName;
}
else {
prefix = qualifiedName.substring(0, index);
localName = qualifiedName.substring(index + 1);
}
Attr newAttr = getAttributeNodeNS(namespaceURI, localName);
if (newAttr == null) {
// REVISIT: this is not efficient, we are creating twice the same
// strings for prefix and localName.
newAttr = getOwnerDocument().createAttributeNS(namespaceURI, qualifiedName);
if (attributes_ == null) {
attributes_ = new AttributeMap(this);
}
newAttr.setNodeValue(value);
attributes_.setNamedItemNS(newAttr);
}
else {
if (newAttr instanceof AttrNSImpl) {
// change prefix and value
((AttrNSImpl) newAttr).name = (prefix != null) ? (prefix + ":" + localName) : localName;
}
else {
// This case may happen if user calls:
// elem.setAttribute("name", "value");
// elem.setAttributeNS(null, "name", "value");
// This case is not defined by the DOM spec, we choose
// to create a new attribute in this case and remove an old one from the tree
// note this might cause events to be propagated or user data to be lost
newAttr = ((CoreDocumentImpl) getOwnerDocument()).createAttributeNS(namespaceURI, qualifiedName,
localName);
attributes_.setNamedItemNS(newAttr);
}
newAttr.setNodeValue(value);
}
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*
*
* Removes an attribute by local name and namespace URI. If the removed
* attribute has a default value it is immediately replaced. The replacing
* attribute has the same namespace URI and local name, as well as the original
* prefix.
*
*
* @param namespaceURI The namespace URI of the attribute to remove.
*
* @param localName The local name of the attribute to remove.
*/
@Override
public void removeAttributeNS(final String namespaceURI, final String localName) {
if (attributes_ == null) {
return;
}
attributes_.internalRemoveNamedItemNS(namespaceURI, name_, false);
}
/**
* {@inheritDoc}
*
* Retrieves an Attr node by local name and namespace URI.
*
* @param namespaceURI The namespace URI of the attribute to retrieve.
* @param localName The local name of the attribute to retrieve.
* @return Attr The Attr node with the specified attribute local name and
* namespace URI or null if there is no such attribute.
*/
@Override
public Attr getAttributeNodeNS(final String namespaceURI, final String localName) {
if (attributes_ == null) {
return null;
}
return (Attr) attributes_.getNamedItemNS(namespaceURI, localName);
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*
*
* Adds a new attribute. If an attribute with that local name and namespace URI
* is already present in the element, it is replaced by the new one.
*
* @param newAttr The Attr node to add to the attribute list. When the Node has
* no namespaceURI, this method behaves like setAttributeNode.
* @return Attr If the newAttr attribute replaces an existing attribute with the
* same local name and namespace URI, the * previously existing Attr
* node is returned, otherwise null is returned.
* @throws DOMException WRONG_DOCUMENT_ERR: Raised if newAttr was created from a
* different document than the one that created the
* element.
*
* @throws DOMException INUSE_ATTRIBUTE_ERR: Raised if newAttr is already an
* attribute of another Element object. The DOM user must
* explicitly clone Attr nodes to re-use them in other
* elements.
*/
@Override
public Attr setAttributeNodeNS(final Attr newAttr) throws DOMException {
if (ownerDocument.errorChecking) {
if (newAttr.getOwnerDocument() != ownerDocument) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
}
}
if (attributes_ == null) {
attributes_ = new AttributeMap(this);
}
// This will throw INUSE if necessary
return (Attr) attributes_.setNamedItemNS(newAttr);
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*/
@Override
public boolean hasAttributes() {
return attributes_ != null && attributes_.getLength() != 0;
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*/
@Override
public boolean hasAttribute(final String name) {
return getAttributeNode(name) != null;
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*/
@Override
public boolean hasAttributeNS(final String namespaceURI, final String localName) {
return getAttributeNodeNS(namespaceURI, localName) != null;
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*
*
* Returns a NodeList of all the Elements with a given local name and namespace
* URI in the order in which they would be encountered in a preorder traversal
* of the Document tree, starting from this node.
*
* @param namespaceURI The namespace URI of the elements to match on. The
* special value "*" matches all namespaces. When it is null
* or an empty string, this method behaves like
* getElementsByTagName.
* @param localName The local name of the elements to match on. The special
* value "*" matches all local names.
* @return NodeList A new NodeList object containing all the matched Elements.
*/
@Override
public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) {
return new DeepNodeListImpl(this, namespaceURI, localName);
}
/**
* {@inheritDoc}
*
* DOM Level 3 WD- Experimental. Override inherited behavior from NodeImpl and
* ParentNode to check on attributes
*/
@Override
public boolean isEqualNode(final Node arg) {
if (!super.isEqualNode(arg)) {
return false;
}
final boolean hasAttrs = hasAttributes();
if (hasAttrs != arg.hasAttributes()) {
return false;
}
if (hasAttrs) {
final NamedNodeMap map1 = getAttributes();
final NamedNodeMap map2 = arg.getAttributes();
final int len = map1.getLength();
if (len != map2.getLength()) {
return false;
}
for (int i = 0; i < len; i++) {
final Node n1 = map1.item(i);
if (n1.getLocalName() == null) { // DOM Level 1 Node
final Node n2 = map2.getNamedItem(n1.getNodeName());
if (n2 == null || !n1.isEqualNode(n2)) {
return false;
}
}
else {
final Node n2 = map2.getNamedItemNS(n1.getNamespaceURI(), n1.getLocalName());
if (n2 == null || !n1.isEqualNode(n2)) {
return false;
}
}
}
}
return true;
}
/**
* {@inheritDoc}
*
* DOM Level 3: register the given attribute node as an ID attribute
*/
@Override
public void setIdAttributeNode(final Attr at, final boolean makeId) {
if (ownerDocument.errorChecking) {
if (at.getOwnerElement() != this) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
}
}
((AttrImpl) at).isIdAttribute(makeId);
if (!makeId) {
ownerDocument.removeIdentifier(at.getValue());
}
else {
ownerDocument.putIdentifier(at.getValue(), this);
}
}
/**
* {@inheritDoc}
*
* DOM Level 3: register the given attribute node as an ID attribute
*/
@Override
public void setIdAttribute(final String name, final boolean makeId) {
final Attr at = getAttributeNode(name);
if (at == null) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
}
if (ownerDocument.errorChecking) {
if (at.getOwnerElement() != this) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
}
}
((AttrImpl) at).isIdAttribute(makeId);
if (!makeId) {
ownerDocument.removeIdentifier(at.getValue());
}
else {
ownerDocument.putIdentifier(at.getValue(), this);
}
}
/**
* {@inheritDoc}
*
* DOM Level 3: register the given attribute node as an ID attribute
*/
@Override
public void setIdAttributeNS(final String namespaceURI, final String localName, final boolean makeId) {
final Attr at = getAttributeNodeNS(namespaceURI, localName);
if (at == null) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
}
if (ownerDocument.errorChecking) {
if (at.getOwnerElement() != this) {
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
}
}
((AttrImpl) at).isIdAttribute(makeId);
if (!makeId) {
ownerDocument.removeIdentifier(at.getValue());
}
else {
ownerDocument.putIdentifier(at.getValue(), this);
}
}
/**
* {@inheritDoc}
*
* @see org.w3c.dom.TypeInfo#getTypeName()
*/
@Override
public String getTypeName() {
return null;
}
/**
* {@inheritDoc}
*
* @see org.w3c.dom.TypeInfo#getTypeNamespace()
*/
@Override
public String getTypeNamespace() {
return null;
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 3.
*
* Checks if a type is derived from another by restriction. See:
* http://www.w3.org/TR/DOM-Level-3-Core/core.html#TypeInfo-isDerivedFrom
*
* @param typeNamespaceArg The namspace of the ancestor type declaration
* @param typeNameArg The name of the ancestor type declaration
* @param derivationMethod The derivation method
*
* @return boolean True if the type is derived by restriciton for the reference
* type
*/
@Override
public boolean isDerivedFrom(final String typeNamespaceArg, final String typeNameArg, final int derivationMethod) {
return false;
}
/**
* {@inheritDoc}
*
* Method getSchemaTypeInfo.
*
* @return TypeInfo
*/
@Override
public TypeInfo getSchemaTypeInfo() {
return this;
}
// support for DOM Level 3 renameNode method
// @param el The element from which to take the attributes
void moveSpecifiedAttributes(final ElementImpl el) {
if (el.hasAttributes()) {
if (attributes_ == null) {
attributes_ = new AttributeMap(this);
}
attributes_.moveSpecifiedAttributes(el.attributes_);
}
}
}