/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Mozilla Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is TransforMiiX XSLT processor. * * The Initial Developer of the Original Code is The MITRE Corporation. * Portions created by MITRE are Copyright (C) 1999 The MITRE Corporation. * * Portions created by Keith Visco as a Non MITRE employee, * (C) 1999 Keith Visco. All Rights Reserved. * * Contributor(s): * Keith Visco, kvisco@ziplink.net * -- original author. * * Larry Fitzpatrick, OpenText, lef@opentext.com * -- moved initialization of DEFAULT_SIZE from NodeSet.h to here * * Olivier Gerardin, ogerardin@vo.lu * -- fixed numberValue() * */ #include "NodeSet.h" #include "XMLDOMUtils.h" #include "string.h" #include "stdio.h" static const int kTxNodeSetMinSize = 4; static const int kTxNodeSetGrowFactor = 2; /* * Implementation of an XPath NodeSet */ /* * Creates a new empty NodeSet */ NodeSet::NodeSet() : mElements(0), mBufferSize(0), mElementCount(0) { } /* * Creates a new NodeSet containing the supplied Node */ NodeSet::NodeSet(Node* aNode) : mBufferSize(1), mElementCount(1) { NS_ASSERTION(aNode, "missing node to NodeSet::add"); mElements = new Node*[1]; if (!mElements) { NS_ASSERTION(0, "out of memory"); mBufferSize = 0; mElementCount = 0; } else { mElements[0] = aNode; } } /* * Creates a new NodeSet, copying the Node references from the source * NodeSet */ NodeSet::NodeSet(const NodeSet& aSource) : mElements(0), mBufferSize(0), mElementCount(0) { append(&aSource); } /* * Adds the specified Node to this NodeSet if it is not already in this * NodeSet. The node is inserted according to document order. * @param aNode the Node to add to the NodeSet * @return errorcode. */ nsresult NodeSet::add(Node* aNode) { NS_ASSERTION(aNode, "missing node to NodeSet::add"); if (!aNode) return NS_ERROR_NULL_POINTER; MBool nonDup; int pos = findPosition(aNode, 0, mElementCount - 1, nonDup); if (nonDup) { if (!ensureSize(mElementCount + 1)) return NS_ERROR_OUT_OF_MEMORY; memmove(mElements + pos + 1, mElements + pos, (mElementCount - pos) * sizeof(Node*)); mElements[pos] = aNode; ++mElementCount; } return NS_OK; } /* * Adds the nodes in specified NodeSet to this NodeSet. The resulting NodeSet * is sorted in document order and does not contain any duplicate nodes. * @param aNodes the NodeSet to add, must be in document order. * @return true on success. false on failure. */ /* * The code is optimized to make a minimum number of calls to * Node::compareDocumentPosition. The idea is this: * We have the two nodesets (number indicate "document position") * * 1 3 7 <- source 1 * 2 3 6 8 9 <- source 2 * _ _ _ _ _ _ _ _ <- result * * * We select the last node in the smallest nodeset and find where in the other * nodeset it would be inserted. In this case we would take the 7 from the * first nodeset and find the position between the 6 and 8 in the second. * We then take the nodes after the insert-position and move it to the end of * the resulting nodeset, and then do the same for the node from the smaller * nodeset. Which in this case means that we'd first move the 8 and 9 nodes, * and then the 7 node, giving us the following: * * 1 3 <- source 1 * 2 3 6 <- source 2 * _ _ _ _ _ 7 8 9 <- result * * Repeat until one of the nodesets are empty. If we find a duplicate node * when searching for where insertposition we skip the step where we move the * node from the smaller nodeset to the resulting nodeset. So in this next * step in the example we would only move the 3 and 6 nodes from the second * nodeset and then just remove the 3 node from the first nodeset. Giving: * * 1 <- source 1 * 2 <- source 2 * _ _ _ 3 6 7 8 9 <- result * * We might therefor end up with some blanks in the bigining of the resulting * nodeset, which we simply fix by moving all the nodes one step down. */ nsresult NodeSet::add(const NodeSet* aNodes) { NS_ASSERTION(aNodes, "missing nodeset to NodeSet::add"); if (!aNodes) return NS_ERROR_NULL_POINTER; if (aNodes->mElementCount == 0) return NS_OK; // This is probably a rather common case, so lets try to shortcut if (mElementCount == 0 || mElements[mElementCount-1]->compareDocumentPosition(aNodes->mElements[0]) < 0) return append(aNodes); if (!ensureSize(mElementCount + aNodes->mElementCount)) return NS_ERROR_OUT_OF_MEMORY; // Index of last node in this nodeset int thisPos = mElementCount - 1; // Index of last node in other nodeset int otherPos = aNodes->mElementCount - 1; // Index in result where last insert was done. int lastInsertPos = mElementCount + aNodes->mElementCount; while (thisPos >= 0 && otherPos >= 0) { if (thisPos > otherPos) { int pos; MBool nonDup; // Find where in the remaining nodes in this nodeset a node from // the other nodeset should be inserted pos = findPosition(aNodes->mElements[otherPos], 0, thisPos, nonDup); // Move nodes in this nodeset lastInsertPos -= thisPos - pos + 1; memmove(mElements + lastInsertPos, mElements + pos, (thisPos - pos + 1) * sizeof(Node*)); // Copy node from the other nodeset unless it's a dup if (nonDup) mElements[--lastInsertPos] = aNodes->mElements[otherPos]; // Adjust positions in both nodesets thisPos = pos - 1; --otherPos; } else { int pos; MBool nonDup; // Find where in the remaining nodes in the other nodeset a node // from this nodeset should be inserted pos = aNodes->findPosition(mElements[thisPos], 0, otherPos, nonDup); // Copy nodes from other nodeset to this lastInsertPos -= otherPos - pos + 1; memcpy(mElements + lastInsertPos, aNodes->mElements + pos, (otherPos - pos + 1) * sizeof(Node*)); // Move node in this nodeset unless it's a dup if (nonDup) mElements[--lastInsertPos] = mElements[thisPos]; // Adjust positions in both nodesets otherPos = pos - 1; --thisPos; } } if (thisPos >= 0) { // There were some elements still left in this nodeset that need to // be moved lastInsertPos -= thisPos + 1; memmove(mElements + lastInsertPos, mElements, (thisPos + 1) * sizeof(Node*)); } else if (otherPos >= 0) { // There were some elements still left in the other nodeset that need // to be copied lastInsertPos -= otherPos + 1; memcpy(mElements + lastInsertPos, aNodes->mElements, (otherPos + 1) * sizeof(Node*)); } // if lastInsertPos != 0 then we have found some duplicates causing the // first element to not be placed at mElements[0] mElementCount += aNodes->mElementCount - lastInsertPos; if (lastInsertPos) { memmove(mElements, mElements + lastInsertPos, mElementCount * sizeof(Node*)); } return NS_OK; } /* * Append API * These functions should be used with care. * They are intended to be used when the caller assures that the resulting * NodeSet remains in document order. * Abuse will break document order, and cause errors in the result. * These functions are significantly faster than the add API, as no * Node::OrderInfo structs will be generated. */ /* * Appends the specified Node to the end of this NodeSet * @param aNode the Node to append to the NodeSet * @return true on success. false on failure. */ nsresult NodeSet::append(Node* aNode) { NS_ASSERTION(aNode, "missing node to NodeSet::append"); if (!aNode) return NS_ERROR_NULL_POINTER; if (!ensureSize(mElementCount + 1)) return NS_ERROR_OUT_OF_MEMORY; mElements[mElementCount++] = aNode; return NS_OK; } /* * Appends the nodes in the specified NodeSet to the end of this NodeSet * @param aNodes the NodeSet to append to the NodeSet * @return true on success. false on failure. */ nsresult NodeSet::append(const NodeSet* aNodes) { NS_ASSERTION(aNodes, "missing nodeset to NodeSet::append"); if (!aNodes) return NS_ERROR_NULL_POINTER; if (!ensureSize(mElementCount + aNodes->mElementCount)) return NS_ERROR_OUT_OF_MEMORY; memcpy(mElements + mElementCount, aNodes->mElements, aNodes->mElementCount * sizeof(Node*)); mElementCount += aNodes->mElementCount; return NS_OK; } /* * Reverse the order of the nodes. */ void NodeSet::reverse() { int i; for (i = 0; i < mElementCount / 2; ++i) { Node* tmp; tmp = mElements[i]; mElements[i] = mElements[mElementCount - 1 - i]; mElements[mElementCount - 1 - i] = tmp; } } /* * Returns the index of the specified Node, * or -1 if the Node is not contained in the NodeSet * @param aNode the Node to get the index for * @return index of specified node or -1 if the node does not exist */ MBool NodeSet::indexOf(Node* aNode) const { // XXX evaluate cost of this // Workaround to fix the fact that attributes can't be // pointer-compared MBool nonDup; int pos = findPosition(aNode, 0, mElementCount - 1, nonDup); return nonDup ? -1 : pos; } /* * Returns the Node at the specified position in this NodeSet. * @param aIndex the position of the Node to return * @return Node at specified position */ Node* NodeSet::get(int aIndex) const { NS_ASSERTION(aIndex >= 0 && aIndex < mElementCount, "invalid index in NodeSet::get"); if (aIndex < 0 || aIndex >= mElementCount) return 0; return mElements[aIndex]; } /* * Returns the type of ExprResult represented * @return the type of ExprResult represented */ short NodeSet::getResultType() { return ExprResult::NODESET; } /* * Converts this ExprResult to a Boolean (MBool) value * @return the Boolean value */ MBool NodeSet::booleanValue() { return mElementCount > 0; } /* * Converts this ExprResult to a Number (double) value * @return the Number value */ double NodeSet::numberValue() { String str; stringValue(str); return Double::toDouble(str); } /* * Creates a String representation of this ExprResult * @param aStr the destination string to append the String representation to. */ void NodeSet::stringValue(String& aStr) { if (mElementCount > 0) XMLDOMUtils::getNodeValue(get(0), aStr); } /* * Makes sure that the mElements buffer contains at least aSize elements. * If a new allocation is required the elements are copied over to the new * buffer * @param aSize requested number of elements * @return true if allocation succeded, false on out of memory */ MBool NodeSet::ensureSize(int aSize) { if (aSize <= mBufferSize) return MB_TRUE; // This isn't 100% safe. But until someone manages to make a 1gig nodeset // it should be ok. int newSize = mBufferSize ? mBufferSize : kTxNodeSetMinSize; while (newSize < aSize) newSize *= kTxNodeSetGrowFactor; Node** newArr = new Node*[newSize]; if (!newArr) return MB_FALSE; if (mElementCount) memcpy(newArr, mElements, mElementCount * sizeof(Node*)); delete [] mElements; mElements = newArr; mBufferSize = newSize; return MB_TRUE; } /* * Finds position in the mElements buffer where a node should be inserted * to keep the nodeset in document order. Searches the positions * aFirst-aLast, including both aFirst and aLast. * @param aNode Node to find insert position for * @param aFirst First index to search, this index will be searched * @param aLast Last index to search, this index will be searched * @param aNonDup Out-param. Set to true if the node should be inserted, * false if it already exists in the NodeSet. * @return The index where to insert the node. The node should be * inserted before the node at this index. This value is * always >= aFirst and <= aLast + 1. This value is always * set, even if aNode already exists in the NodeSet */ int NodeSet::findPosition(Node* aNode, int aFirst, int aLast, MBool& aNonDup) const { NS_ASSERTION(aNode, "missing node in NodeSet::findPosition"); NS_ASSERTION(aFirst <= aLast+1 && aLast < mElementCount, "bad position in NodeSet::findPosition"); if (aLast - aFirst <= 1) { // If we search 2 nodes or less there is no point in further divides int pos; for (pos = aFirst; pos <= aLast; ++pos) { int cmp = aNode->compareDocumentPosition(mElements[pos]); if (cmp < 0) { aNonDup = MB_TRUE; return pos; } if (cmp == 0) { aNonDup = MB_FALSE; return pos; } } aNonDup = MB_TRUE; return pos; } int midpos = (aFirst + aLast) / 2; int cmp = aNode->compareDocumentPosition(mElements[midpos]); if (cmp == 0) { aNonDup = MB_FALSE; return midpos; } if (cmp > 0) return findPosition(aNode, midpos + 1, aLast, aNonDup); return findPosition(aNode, aFirst, midpos - 1, aNonDup); }