All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.htmlunit.cyberneko.xerces.dom.NamedNodeMapImpl 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 java.util.List;

import org.htmlunit.cyberneko.util.SimpleArrayList;
import org.w3c.dom.DOMException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/**
 * NamedNodeMaps represent collections of Nodes that can be accessed by name.
 * Entity and Notation nodes are stored in NamedNodeMaps attached to the
 * DocumentType. Attributes are placed in a NamedNodeMap attached to the elem
 * they're related too. However, because attributes require more work, such as
 * firing mutation events, they are stored in a subclass of NamedNodeMapImpl.
 * 

* Only one Node may be stored per name; attempting to store another will * replace the previous value. *

* NOTE: The "primary" storage key is taken from the NodeName attribute of the * node. The "secondary" storage key is the namespaceURI and localName, when * accessed by DOM level 2 nodes. All nodes, even DOM Level 2 nodes are stored * in a single ArrayList sorted by the primary "nodename" key. *

* NOTE: item()'s integer index does _not_ imply that the named nodes must be * stored in an array; that's only an access method. Note too that these indices * are "live"; if someone changes the map's contents, the indices associated * with nodes may change. */ public class NamedNodeMapImpl implements NamedNodeMap { protected short flags; protected static final short READONLY = 0x1 << 0; protected static final short CHANGED = 0x1 << 1; protected static final short HASDEFAULTS = 0x1 << 2; /** Nodes. */ protected List nodes; protected final NodeImpl ownerNode; // the node this map belongs to /** * Constructs a named node map. * * @param ownerNode the owner node */ protected NamedNodeMapImpl(final NodeImpl ownerNode) { this.ownerNode = ownerNode; } /** * {@inheritDoc} * * Report how many nodes are currently stored in this NamedNodeMap. Caveat: This * is a count rather than an index, so the highest-numbered node at any time can * be accessed via item(getLength()-1). */ @Override public int getLength() { return (nodes != null) ? nodes.size() : 0; } /** * {@inheritDoc} * * Retrieve an item from the map by 0-based index. * * @param index Which item to retrieve. Note that indices are just an * enumeration of the current contents; they aren't guaranteed to * be stable, nor do they imply any promises about the order of the * NamedNodeMap's contents. In other words, DO NOT assume either * that index(i) will always refer to the same entry, or that there * is any stable ordering of entries... and be prepared for * double-reporting or skips as insertion and deletion occur. * * @return the node which currenly has the specified index, or null if index is * greater than or equal to getLength(). */ @Override public Node item(final int index) { return (nodes != null && index < nodes.size()) ? nodes.get(index) : null; } /** * {@inheritDoc} * * Retrieve a node by name. * * @param name Name of a node to look up. * @return the Node (of unspecified sub-class) stored with that name, or null if * no value has been assigned to that name. */ @Override public Node getNamedItem(final String name) { final int i = findNamePoint(name); return (i < 0) ? null : nodes.get(i); } /** * {@inheritDoc} * * Introduced in DOM Level 2. *

* Retrieves a node specified by local name and namespace URI. * * @param namespaceURI The namespace URI of the node to retrieve. When it is * null or an empty string, this method behaves like * getNamedItem. * @param localName The local name of the node to retrieve. * @return Node A Node (of any type) with the specified name, or null if the * specified name did not identify any node in the map. */ @Override public Node getNamedItemNS(final String namespaceURI, final String localName) { final int i = findNamePoint(namespaceURI, localName); return (i < 0) ? null : nodes.get(i); } /** * {@inheritDoc} * * Adds a node using its nodeName attribute. As the nodeName attribute is used * to derive the name which the node must be stored under, multiple nodes of * certain types (those that have a "special" string value) cannot be stored as * the names would clash. This is seen as preferable to allowing nodes to be * aliased. * * @see org.w3c.dom.NamedNodeMap#setNamedItem * @return If the new Node replaces an existing node the replaced Node is * returned, otherwise null is returned. * @param arg A node to store in a named node map. The node will later be * accessible using the value of the namespaceURI and localName * attribute of the node. If a node with those namespace URI and * local name is already present in the map, it is replaced by the * new one. * @exception org.w3c.dom.DOMException The exception description. */ @Override public Node setNamedItem(final Node arg) throws DOMException { final CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); if (ownerDocument.errorChecking) { if (arg.getOwnerDocument() != ownerDocument) { final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); } } int i = findNamePoint(arg.getNodeName()); NodeImpl previous = null; if (i >= 0) { previous = (NodeImpl) nodes.get(i); nodes.set(i, arg); } else { i = -1 - i; // Insert point (may be end of list) if (null == nodes) { nodes = new SimpleArrayList<>(1); nodes.add(arg); } else { nodes.add(i, arg); } } return previous; } /** * {@inheritDoc} */ @Override public Node setNamedItemNS(final Node arg) throws DOMException { final CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); if (ownerDocument.errorChecking) { if (arg.getOwnerDocument() != ownerDocument) { final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); } } int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName()); NodeImpl previous = null; if (i >= 0) { previous = (NodeImpl) nodes.get(i); nodes.set(i, arg); } else { // If we can't find by namespaceURI, localName, then we find by // nodeName so we know where to insert. i = findNamePoint(arg.getNodeName()); if (i >= 0) { previous = (NodeImpl) nodes.get(i); nodes.add(i, arg); } else { i = -1 - i; // Insert point (may be end of list) if (null == nodes) { nodes = new SimpleArrayList<>(1); nodes.add(arg); } else { nodes.add(i, arg); } } } return previous; } /** * {@inheritDoc} * * Removes a node specified by name. * * @param name The name of a node to remove. * @return The node removed from the map if a node with such a name exists. */ @Override public Node removeNamedItem(final String name) throws DOMException { final int i = findNamePoint(name); if (i < 0) { final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); throw new DOMException(DOMException.NOT_FOUND_ERR, msg); } final NodeImpl n = (NodeImpl) nodes.get(i); nodes.remove(i); return n; } /** * {@inheritDoc} */ @Override public Node removeNamedItemNS(final String namespaceURI, final String name) throws DOMException { final int i = findNamePoint(namespaceURI, name); if (i < 0) { final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); throw new DOMException(DOMException.NOT_FOUND_ERR, msg); } final NodeImpl n = (NodeImpl) nodes.get(i); nodes.remove(i); return n; } /** * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones all the nodes * contained in the map. * * @param ownerNode the owner node * @return the cloned map */ public NamedNodeMapImpl cloneMap(final NodeImpl ownerNode) { final NamedNodeMapImpl newmap = new NamedNodeMapImpl(ownerNode); newmap.cloneContent(this); return newmap; } protected void cloneContent(final NamedNodeMapImpl srcmap) { final List srcnodes = srcmap.nodes; if (srcnodes != null) { final int size = srcnodes.size(); if (size != 0) { if (nodes == null) { nodes = new SimpleArrayList<>(size); } else { nodes.clear(); } for (int i = 0; i < size; ++i) { final NodeImpl n = (NodeImpl) srcmap.nodes.get(i); final NodeImpl clone = (NodeImpl) n.cloneNode(true); clone.isSpecified(n.isSpecified()); nodes.add(clone); } } } } /** * NON-DOM set the ownerDocument of this node, and the attributes it contains * * @param doc the doc */ protected void setOwnerDocument(final CoreDocumentImpl doc) { if (nodes != null) { final int size = nodes.size(); for (int i = 0; i < size; ++i) { ((NodeImpl) item(i)).setOwnerDocument(doc); } } } final boolean changed() { return (flags & CHANGED) != 0; } final void changed(final boolean value) { flags = (short) (value ? flags | CHANGED : flags & ~CHANGED); } final boolean hasDefaults() { return (flags & HASDEFAULTS) != 0; } final void hasDefaults(final boolean value) { flags = (short) (value ? flags | HASDEFAULTS : flags & ~HASDEFAULTS); } /** * Subroutine: Locate the named item, or the point at which said item should be * added. * * @param name Name of a node to look up. * * @return If positive or zero, the index of the found item. If negative, index * of the appropriate point at which to insert the item, encoded as * -1-index and hence reconvertable by subtracting it from -1. (Encoding * because I don't want to recompare the strings but don't want to burn * bytes on a datatype to hold a flagged value.) */ protected int findNamePoint(final String name) { // Binary search int i = 0; if (nodes != null) { int first = 0; int last = nodes.size() - 1; while (first <= last) { i = (first + last) / 2; final int test = name.compareTo((nodes.get(i)).getNodeName()); if (test == 0) { return i; // Name found } else if (test < 0) { last = i - 1; } else { first = i + 1; } } if (first > i) { i = first; } } return -1 - i; // not-found has to be encoded. } protected int findNamePoint(final String namespaceURI, final String name) { // This findNamePoint is for DOM Level 2 Namespaces. if ((nodes == null) || (name == null)) { return -1; } // This is a linear search through the same nodes ArrayList. // The ArrayList is sorted on the DOM Level 1 nodename. // The DOM Level 2 NS keys are namespaceURI and Localname, // so we must linear search thru it. // In addition, to get this to work with nodes without any namespace // (namespaceURI and localNames are both null) we then use the nodeName // as a secondary key. final int size = nodes.size(); for (int i = 0; i < size; ++i) { final NodeImpl a = (NodeImpl) nodes.get(i); final String aNamespaceURI = a.getNamespaceURI(); final String aLocalName = a.getLocalName(); if (namespaceURI == null) { if (aNamespaceURI == null && (name.equals(aLocalName) || (aLocalName == null && name.equals(a.getNodeName())))) { return i; } } else { if (namespaceURI.equals(aNamespaceURI) && name.equals(aLocalName)) { return i; } } } return -1; } // compare 2 nodes in the map. If a precedes b, return true, otherwise // return false protected boolean precedes(final Node a, final Node b) { if (nodes != null) { for (final Node node : nodes) { if (node == a) { return true; } if (node == b) { return false; } } } return false; } /** * NON-DOM: Remove attribute at specified index * * @param index the index to be removed */ protected void removeItem(final int index) { if (nodes != null && index < nodes.size()) { nodes.remove(index); } } protected Node getItem(final int index) { if (nodes != null) { return nodes.get(index); } return null; } protected int addItem(final Node arg) { int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName()); if (i >= 0) { nodes.set(i, arg); } else { // If we can't find by namespaceURI, localName, then we find by // nodeName so we know where to insert. i = findNamePoint(arg.getNodeName()); if (i >= 0) { nodes.add(i, arg); } else { i = -1 - i; // Insert point (may be end of list) if (null == nodes) { nodes = new SimpleArrayList<>(1); nodes.add(arg); } else { nodes.add(i, arg); } } } return i; } protected int getNamedItemIndex(final String namespaceURI, final String localName) { return findNamePoint(namespaceURI, localName); } /** * NON-DOM remove all elements from this map */ public void removeAll() { if (nodes != null) { nodes.clear(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy