diff --git a/src/dom/AttrImpl.cpp b/src/dom/AttrImpl.cpp index 7e78cc4a691155ed113c68299cc2229cdcfbed76..0066c35718f9300591f9e2561a4206be24245d73 100644 --- a/src/dom/AttrImpl.cpp +++ b/src/dom/AttrImpl.cpp @@ -64,6 +64,7 @@ #include "TextImpl.hpp" #include "ElementImpl.hpp" #include "DStringPool.hpp" +#include "NodeIDMap.hpp" AttrImpl::AttrImpl(DocumentImpl *ownerDoc, const DOMString &aName) @@ -78,12 +79,15 @@ AttrImpl::AttrImpl(const AttrImpl &other, bool deep) { name = other.name.clone(); specified(false); + idAttr(false); if (deep) cloneChildren(other); }; AttrImpl::~AttrImpl() { + if (this->idAttr()) + this->getOwnerDocument()->getNodeIDMap()->remove(this); }; @@ -177,6 +181,13 @@ void AttrImpl::setValue(const DOMString &val) ); } + // If this attribute was of type ID and in the map, take it out, + // then put it back in with the new name. For now, we don't worry + // about what happens if the new name conflicts + // + if (idAttr()) + this->getOwnerDocument()->getNodeIDMap()->remove(this); + NodeImpl *kid; while ((kid = firstChild) != null) // Remove existing kids { @@ -189,6 +200,10 @@ void AttrImpl::setValue(const DOMString &val) appendChild(ownerDocument->createTextNode(val)); specified(true); changed(); + + if (idAttr()) + this->getOwnerDocument()->getNodeIDMap()->add(this); + }; diff --git a/src/dom/DocumentImpl.cpp b/src/dom/DocumentImpl.cpp index f0e5047c40d6670111cf1fb6d3d6ebbd74dfb7b5..efca2a4a8c6bc86fc16550e808847b242595c9de 100644 --- a/src/dom/DocumentImpl.cpp +++ b/src/dom/DocumentImpl.cpp @@ -86,6 +86,7 @@ #include <internal/XMLReader.hpp> #include "TreeWalkerImpl.hpp" #include "NodeIteratorImpl.hpp" +#include "NodeIDMap.hpp" #include "DOM_Document.hpp" @@ -97,6 +98,8 @@ DocumentImpl::DocumentImpl() namePool = new DStringPool(257); iterators = 0L; treeWalkers = 0L; + fNodeIDMap = 0; + }; @@ -113,8 +116,9 @@ DocumentImpl::DocumentImpl(const DOMString &fNamespaceURI, docElement=null; appendChild(createElementNS(fNamespaceURI, qualifiedName)); //root element namePool = new DStringPool(257); - iterators = 0L; - treeWalkers = 0L; + iterators = 0; + treeWalkers = 0; + fNodeIDMap = 0; } void DocumentImpl::setDocumentType(DocumentTypeImpl *doctype) @@ -158,6 +162,8 @@ DocumentImpl::~DocumentImpl() // Do not delete docType and docElement pointers here. // These are also normal child nodes of the document, // and refcounting will take them out in the usual way. + + delete fNodeIDMap; }; @@ -651,7 +657,14 @@ DeepNodeListImpl *DocumentImpl::getElementsByTagNameNS(const DOMString &fNamespa ElementImpl *DocumentImpl::getElementById(const DOMString &elementId) { - return null; + if (fNodeIDMap == 0) + return null; + + AttrImpl *theAttr = fNodeIDMap->find(elementId); + if (theAttr == null) + return null; + + return theAttr->getOwnerElement(); } diff --git a/src/dom/DocumentImpl.hpp b/src/dom/DocumentImpl.hpp index 816a8dd60e77772cccdcc67bbbfd0be1ce90693a..e2f05684182363a43a53158d942eb57abb804cb9 100644 --- a/src/dom/DocumentImpl.hpp +++ b/src/dom/DocumentImpl.hpp @@ -98,6 +98,7 @@ class DOM_NodeFilter; class NodeFilterImpl; class DOM_DOMImplementation; class DOMString; +class NodeIDMap; typedef RefVectorOf<NodeIteratorImpl> NodeIterators; typedef RefVectorOf<TreeWalkerImpl> TreeWalkers; @@ -114,6 +115,7 @@ private: DocumentTypeImpl *docType; ElementImpl *docElement; DStringPool *namePool; + NodeIDMap *fNodeIDMap; // for use by GetElementsById(). NodeIterators *iterators; TreeWalkers *treeWalkers; @@ -164,17 +166,19 @@ public: //Introduced in DOM Level 2 virtual NodeImpl *importNode(NodeImpl *source, bool deep); virtual ElementImpl *createElementNS(const DOMString &namespaceURI, - const DOMString &qualifiedName); + const DOMString &qualifiedName); virtual AttrImpl *createAttributeNS(const DOMString &namespaceURI, - const DOMString &qualifiedName); + const DOMString &qualifiedName); virtual DeepNodeListImpl *getElementsByTagNameNS(const DOMString &namespaceURI, - const DOMString &localName); + const DOMString &localName); virtual ElementImpl *getElementById(const DOMString &elementId); //Return the index > 0 of ':' in the given qualified name qName="prefix:localName". //Return 0 if there is no ':', or -1 if qName is malformed such as ":abcd". static int indexofQualifiedName(const DOMString & qName); - static bool isKidOK(NodeImpl *parent, NodeImpl *child); + static bool isKidOK(NodeImpl *parent, NodeImpl *child); + + inline NodeIDMap * getNodeIDMap() {return fNodeIDMap;}; }; #endif diff --git a/src/dom/NodeIDMap.cpp b/src/dom/NodeIDMap.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4b6f2eb6369517a4288db540c9394466ad2e25b1 --- /dev/null +++ b/src/dom/NodeIDMap.cpp @@ -0,0 +1,273 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-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 "Xerces" 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, International + * Business Machines, Inc., http://www.ibm.com . For more information + * on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + +#include "DOMString.hpp" +#include "AttrImpl.hpp" +#include "NodeIDMap.hpp" +#include <util/XMLString.hpp> + +static const int gPrimes[] = {997, 9973, 99991, 999983, 0 }; // To do - add a few more. + +static const float gMaxFill = 0.8f; // The maximum fraction of the total + // table entries to consume before exanding. + +NodeIDMap::NodeIDMap(int initialSize) +{ + for (fSizeIndex = 0; gPrimes[fSizeIndex] < initialSize; fSizeIndex++); + { + if (gPrimes[fSizeIndex] == 0) + { + // We need a bigger size than the largest available one. + // Big trouble. + fSizeIndex--; + throw "NodeIDMap::NodeIDMap - big trouble."; + } + } + + fSize = gPrimes[fSizeIndex]; + fNumEntries = 0; + fMaxEntries = unsigned long(float(fSize) * gMaxFill); + + fTable = new AttrImpl *[fSize]; + unsigned int i; + for (i=0; i<fSize; i++) + fTable[i] = 0; +}; + + +NodeIDMap::~NodeIDMap() +{ + delete[] fTable; + fTable = 0; +}; + + + +void NodeIDMap::add(AttrImpl *attr) +{ + // + // If the table is getting too full, grow it. We arbitrarily limit + // the table to 80 full, which should limit the average number of + // rehashes to a reasonable value. + // + if (fNumEntries >= fMaxEntries) + growTable(); + fNumEntries++; + + // + // Hash the value string from the ID attribute being added to the table + // 0 < Initial hash value < table size. + // An initial hash of zero would cause the rehash to fail. + // + DOMString id=attr->getValue(); + unsigned int initalHash = XMLString::hashN(id.rawBuffer(), id.length(), fSize-1); + initalHash++; + unsigned int currentHash = initalHash; + + // + // Loop looking for an empty slot for this ID. + // Don't even bother checking to see if the ID is already there - + // the table is only filled by the parser from valid documents, which + // can not have duplicates. Behavior of invalid docs is not defined. + // + while (true) + { + AttrImpl *tableSlot = fTable[currentHash]; + if (tableSlot == 0 || + tableSlot == (AttrImpl *)-1) + break; + currentHash += initalHash; // rehash + } + + // + // We've found our slot. Stick the pointer to the attr into it. + // + fTable[currentHash] = attr; + +}; + + +void NodeIDMap::remove(AttrImpl *attr) +{ + // + // Hash the value string from the ID attribute being added to the table + // 0 < Initial hash value < table size. + // An initial hash of zero would cause the rehash to fail. + // + DOMString id=attr->getValue(); + unsigned int initalHash = XMLString::hashN(id.rawBuffer(), id.length(), fSize-1); + initalHash++; + unsigned int currentHash = initalHash; + + // + // Loop looking for a slot pointing to an attr with this id. + // + while (true) + { + AttrImpl *tableSlot = fTable[currentHash]; + if (tableSlot == 0) + { + // There is no matching entry in the table + return; + } + + if (tableSlot == (AttrImpl *)-1) + { + // This slot contains an entry for a removed attribute. + // We need to keep looking - the one we are after could + // still show up later on. + continue; + } + + if (tableSlot == attr) + { + // Found the attribute. Set the slot to -1 to indicate + // that it was once used, meaning that lookups, while never + // matching here, can not stop either, but must rehash again + // and continue searching. + fTable[currentHash] = (AttrImpl *)-1; + return; + } + + currentHash += initalHash; // rehash. + } + +}; + + +AttrImpl *NodeIDMap::find(const DOMString &id) +{ + // + // Get the hashcode for the supplied string. + // + unsigned int initalHash = XMLString::hashN(id.rawBuffer(), id.length(), fSize-1); + initalHash++; + unsigned int currentHash = initalHash; + + // + // Loop looking for a slot pointing to an attr with this id. + // + while (true) + { + AttrImpl *tableSlot = fTable[currentHash]; + if (tableSlot == 0) + { + // There is no matching entry in the table + return 0; + } + + if (tableSlot == (AttrImpl *)-1) + { + // This slot contains an entry for a removed attribute. + // We need to keep looking - the one we are after could + // still show up later on. + continue; + } + + if (tableSlot->getValue().equals(id)) + return tableSlot; + + currentHash += initalHash; // rehash + } + return 0; // Never gets here, but keeps some compilers happy. +}; + + +// +// Grow the table to the next larger size. +// It has gotten too full for efficient operation. +// (We never fill it all the way) +// +void NodeIDMap::growTable() +{ + AttrImpl **oldTable = fTable; + unsigned int oldSize = fSize; + + // + // Figure the new table size. + // + fSizeIndex++; + unsigned int fSize = gPrimes[fSizeIndex]; + if (fSize == 0) + { + // We need to grow bigger than the largest available size. + // Big trouble. + fSizeIndex--; + throw "NodeIDMap::growTable - big trouble."; + } + + // + // Allocate the new table. + // + fTable = new AttrImpl *[fSize]; + unsigned int i; + for (i=0; i<fSize; i++) + fTable[i] = 0; + + fMaxEntries = unsigned long(float(fSize) * gMaxFill); + + // + // Move entries over from the old table to the new one. + // + for (i=0; i<oldSize; i++) + add(oldTable[i]); + + delete [] oldTable; + +}; + + + diff --git a/src/dom/NodeIDMap.hpp b/src/dom/NodeIDMap.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b79fcda4ea73fa7ccf6d27223457995d57987811 --- /dev/null +++ b/src/dom/NodeIDMap.hpp @@ -0,0 +1,120 @@ +#ifndef NodeIDMap_HEADER_GUARD_ +#define NodeIDMap_HEADER_GUARD_ + +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-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 "Xerces" 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, International + * Business Machines, Inc., http://www.ibm.com . For more information + * on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +// +// This file is part of the internal implementation of the C++ XML DOM. +// It should NOT be included or used directly by application programs. +// +// Applications should include the file <dom/DOM.hpp> for the entire +// DOM API, or DOM_*.hpp for individual DOM classes, where the class +// name is substituded for the *. +// + + + + +// +// Class NodeIDMap is a hash table that is used in the implementation of +// of DOM_Document::getElementsByID(). +// +// Why Yet Another HashTable implementation? Becuase it can be significantly +// smaller when tuned for this exact usage, and the generic RefHashTableOf +// from the xerces utils project is not a paricularly good fit. +// +class AttrImpl; +class DOMString; + + +class NodeIDMap { +public: + + NodeIDMap(int initialSize); // Create a new hash table, sized to hold "initialSize" + // Entries. It will automatically grow if need be. + + virtual ~NodeIDMap(); + +private: + NodeIDMap(const NodeIDMap &other); // No copy, assignement, comparison. + NodeIDMap &operator = (const NodeIDMap &other); + bool operator == (const NodeIDMap &other); + +public: + void add(AttrImpl *attr); // Add the specified attribute to the table. + void remove(AttrImpl *other); // Remove the specified attribute. + // Does nothing if the node is not in the table. + AttrImpl *find(const DOMString &ID); // Find the attribute node in the table with this ID + +private: + void growTable(); + +private: + AttrImpl **fTable; + unsigned int fSizeIndex; // Index of the current table size in the + // array of possible table sizes. + unsigned int fSize; // The current size of the table array + // (number of slots, not bytes.) + unsigned int fNumEntries; // The number of entries used. + unsigned int fMaxEntries; // The max number of entries to use before + // growing the table. + + +}; + +#endif diff --git a/src/dom/NodeImpl.cpp b/src/dom/NodeImpl.cpp index e6df6e483cc1a3ba3412fe5ccd08f12c3eaa9353..c356252d9b90e54736cc9b49d036d5aacd1f7b89 100644 --- a/src/dom/NodeImpl.cpp +++ b/src/dom/NodeImpl.cpp @@ -85,6 +85,8 @@ const unsigned short NodeImpl::FIRSTCHILD = 0x1<<4; const unsigned short NodeImpl::SPECIFIED = 0x1<<5; const unsigned short NodeImpl::IGNORABLEWS = 0x1<<6; const unsigned short NodeImpl::SETVALUE = 0x1<<7; +const unsigned short NodeImpl::ID_ATTR = 0x1<<8; + NodeImpl::NodeImpl(DocumentImpl *ownerDoc) { diff --git a/src/dom/NodeImpl.hpp b/src/dom/NodeImpl.hpp index 4f047b63ae9886fe1d84a2032e3e382d71f2fd7f..70157bb030fbe2fafee2e65023fb3ab4cb09c358 100644 --- a/src/dom/NodeImpl.hpp +++ b/src/dom/NodeImpl.hpp @@ -125,6 +125,7 @@ public: static const unsigned short SPECIFIED; static const unsigned short IGNORABLEWS; static const unsigned short SETVALUE; + static const unsigned short ID_ATTR; void *userData; @@ -269,6 +270,15 @@ public: // should really be protected - ALH inline void setValue(bool value) { flags = (value ? flags | SETVALUE : flags & ~SETVALUE); } + + inline bool idAttr() { + return (flags & ID_ATTR) != 0; + } + + inline void idAttr(bool value) { + flags = (value ? flags | ID_ATTR : flags & ~ID_ATTR); + } + }; diff --git a/src/parsers/DOMParser.cpp b/src/parsers/DOMParser.cpp index 6e907b77f8cfa6f819906609334042cead9f3de7..891b997a5985b812a26fe1ee6ca4cc5e3c24e2f0 100644 --- a/src/parsers/DOMParser.cpp +++ b/src/parsers/DOMParser.cpp @@ -84,6 +84,7 @@ #include <dom/EntityImpl.hpp> #include <dom/NotationImpl.hpp> #include <dom/NamedNodeMapImpl.hpp> +#include <dom/NodeIDMap.hpp> #include <validators/DTD/ContentSpecNode.hpp> @@ -572,7 +573,9 @@ void DOMParser::startElement(const XMLElementDecl& elemDecl , const bool isEmpty , const bool isRoot) { - DOM_Element elem; + DOM_Element elem; + DocumentImpl *docImpl = (DocumentImpl *)fDocument.fImpl; + if (fScanner -> getDoNamespaces()) { //DOM Level 2, doNamespaces on unsigned int globalNSid = fValidator -> getGlobalNamespaceId(); XMLBuffer buf; @@ -598,11 +601,18 @@ void DOMParser::startElement(const XMLElementDecl& elemDecl } AttrImpl *attr = elemImpl->setAttributeNS(namespaceURI, oneAttrib -> getQName(), oneAttrib -> getValue()); - // Register identifiers + + // Attributes of type ID. If this is one, add it to the hashtable of IDs + // that is constructed for use by GetElementByID(). + // if (oneAttrib->getType()==XMLAttDef::ID) { - // When we record ID attributes, here is the place to do it. + if (docImpl->fNodeIDMap == 0) + docImpl->fNodeIDMap = new NodeIDMap(500); + docImpl->fNodeIDMap->add(attr); + attr->idAttr(true); } + attr->setSpecified(oneAttrib->getSpecified()); } } else { //DOM Level 1 @@ -611,12 +621,18 @@ void DOMParser::startElement(const XMLElementDecl& elemDecl for (unsigned int index = 0; index < attrCount; ++index) { const XMLAttr* oneAttrib = attrList.elementAt(index); AttrImpl *attr = elemImpl->setAttribute(oneAttrib->getName(), oneAttrib->getValue()); - // Register identifiers + + // Attributes of type ID. If this is one, add it to the hashtable of IDs + // that is constructed for use by GetElementByID(). + // if (oneAttrib->getType()==XMLAttDef::ID) { - // When we record ID attributes, here is the place to do it. + if (docImpl->fNodeIDMap == 0) + docImpl->fNodeIDMap = new NodeIDMap(500); + docImpl->fNodeIDMap->add(attr); + attr->idAttr(true); } - attr->setSpecified(oneAttrib->getSpecified()); + } }