/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "txXPathTreeWalker.h" #include "nsAtom.h" #include "nsINode.h" #include "nsPrintfCString.h" #include "nsReadableUtils.h" #include "nsString.h" #include "nsTextFragment.h" #include "txXMLUtils.h" #include "txLog.h" #include "nsUnicharUtils.h" #include "nsAttrName.h" #include "nsNameSpaceManager.h" #include "nsTArray.h" #include "mozilla/Maybe.h" #include "mozilla/dom/Attr.h" #include "mozilla/dom/CharacterData.h" #include "mozilla/dom/Element.h" #include #include using namespace mozilla::dom; using mozilla::Maybe; txXPathTreeWalker::txXPathTreeWalker(const txXPathTreeWalker& aOther) = default; txXPathTreeWalker::txXPathTreeWalker(const txXPathNode& aNode) : mPosition(aNode) {} void txXPathTreeWalker::moveToRoot() { if (mPosition.isDocument()) { return; } Document* root = mPosition.mNode->GetUncomposedDoc(); if (root) { mPosition.mIndex = txXPathNode::eDocument; mPosition.mNode = root; } else { nsINode* rootNode = mPosition.Root(); NS_ASSERTION(rootNode->IsContent(), "root of subtree wasn't an nsIContent"); mPosition.mIndex = txXPathNode::eContent; mPosition.mNode = rootNode; } } bool txXPathTreeWalker::moveToElementById(const nsAString& aID) { if (aID.IsEmpty()) { return false; } Document* doc = mPosition.mNode->GetUncomposedDoc(); nsCOMPtr content; if (doc) { content = doc->GetElementById(aID); } else { // We're in a disconnected subtree, search only that subtree. nsINode* rootNode = mPosition.Root(); NS_ASSERTION(rootNode->IsContent(), "root of subtree wasn't an nsIContent"); content = nsContentUtils::MatchElementId(static_cast(rootNode), aID); } if (!content) { return false; } mPosition.mIndex = txXPathNode::eContent; mPosition.mNode = content; return true; } bool txXPathTreeWalker::moveToFirstAttribute() { if (!mPosition.isContent()) { return false; } return moveToValidAttribute(0); } bool txXPathTreeWalker::moveToNextAttribute() { // XXX an assertion should be enough here with the current code if (!mPosition.isAttribute()) { return false; } return moveToValidAttribute(mPosition.mIndex + 1); } bool txXPathTreeWalker::moveToValidAttribute(uint32_t aStartIndex) { NS_ASSERTION(!mPosition.isDocument(), "documents doesn't have attrs"); if (!mPosition.Content()->IsElement()) { return false; } Element* element = mPosition.Content()->AsElement(); uint32_t total = element->GetAttrCount(); if (aStartIndex >= total) { return false; } uint32_t index; for (index = aStartIndex; index < total; ++index) { const nsAttrName* name = element->GetAttrNameAt(index); // We need to ignore XMLNS attributes. if (name->NamespaceID() != kNameSpaceID_XMLNS) { mPosition.mIndex = index; return true; } } return false; } bool txXPathTreeWalker::moveToNamedAttribute(nsAtom* aLocalName, int32_t aNSID) { if (!mPosition.isContent() || !mPosition.Content()->IsElement()) { return false; } Element* element = mPosition.Content()->AsElement(); const nsAttrName* name; uint32_t i; for (i = 0; (name = element->GetAttrNameAt(i)); ++i) { if (name->Equals(aLocalName, aNSID)) { mPosition.mIndex = i; return true; } } return false; } bool txXPathTreeWalker::moveToFirstChild() { if (mPosition.isAttribute()) { return false; } nsIContent* child = mPosition.mNode->GetFirstChild(); if (!child) { return false; } mPosition.mIndex = txXPathNode::eContent; mPosition.mNode = child; return true; } bool txXPathTreeWalker::moveToLastChild() { if (mPosition.isAttribute()) { return false; } nsIContent* child = mPosition.mNode->GetLastChild(); if (!child) { return false; } mPosition.mIndex = txXPathNode::eContent; mPosition.mNode = child; return true; } bool txXPathTreeWalker::moveToNextSibling() { if (!mPosition.isContent()) { return false; } nsINode* sibling = mPosition.mNode->GetNextSibling(); if (!sibling) { return false; } mPosition.mNode = sibling; return true; } bool txXPathTreeWalker::moveToPreviousSibling() { if (!mPosition.isContent()) { return false; } nsINode* sibling = mPosition.mNode->GetPreviousSibling(); if (!sibling) { return false; } mPosition.mNode = sibling; return true; } bool txXPathTreeWalker::moveToParent() { if (mPosition.isDocument()) { return false; } if (mPosition.isAttribute()) { mPosition.mIndex = txXPathNode::eContent; return true; } nsINode* parent = mPosition.mNode->GetParentNode(); if (!parent) { return false; } mPosition.mIndex = mPosition.mNode->GetParent() ? txXPathNode::eContent : txXPathNode::eDocument; mPosition.mNode = parent; return true; } txXPathNode::txXPathNode(const txXPathNode& aNode) : mNode(aNode.mNode), mRefCountRoot(aNode.mRefCountRoot), mIndex(aNode.mIndex) { MOZ_COUNT_CTOR(txXPathNode); if (mRefCountRoot) { NS_ADDREF(Root()); } } txXPathNode::~txXPathNode() { MOZ_COUNT_DTOR(txXPathNode); if (mRefCountRoot) { nsINode* root = Root(); NS_RELEASE(root); } } /* static */ bool txXPathNodeUtils::getAttr(const txXPathNode& aNode, nsAtom* aLocalName, int32_t aNSID, nsAString& aValue) { if (aNode.isDocument() || aNode.isAttribute() || !aNode.Content()->IsElement()) { return false; } return aNode.Content()->AsElement()->GetAttr(aNSID, aLocalName, aValue); } /* static */ already_AddRefed txXPathNodeUtils::getLocalName( const txXPathNode& aNode) { if (aNode.isDocument()) { return nullptr; } if (aNode.isContent()) { if (aNode.mNode->IsElement()) { RefPtr localName = aNode.Content()->NodeInfo()->NameAtom(); return localName.forget(); } if (aNode.mNode->IsProcessingInstruction()) { return NS_Atomize(aNode.mNode->NodeName()); } return nullptr; } // This is an attribute node, so we necessarily come from an element. RefPtr localName = aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex)->LocalName(); return localName.forget(); } nsAtom* txXPathNodeUtils::getPrefix(const txXPathNode& aNode) { if (aNode.isDocument()) { return nullptr; } if (aNode.isContent()) { // All other nsIContent node types but elements have a null prefix // which is what we want here. return aNode.Content()->NodeInfo()->GetPrefixAtom(); } return aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex)->GetPrefix(); } /* static */ void txXPathNodeUtils::getLocalName(const txXPathNode& aNode, nsAString& aLocalName) { if (aNode.isDocument()) { aLocalName.Truncate(); return; } if (aNode.isContent()) { if (aNode.mNode->IsElement()) { mozilla::dom::NodeInfo* nodeInfo = aNode.Content()->NodeInfo(); nodeInfo->GetName(aLocalName); return; } if (aNode.mNode->IsProcessingInstruction()) { // PIs don't have a nodeinfo but do have a name // XXXbz Not actually true, but this function looks like it wants // different things from elements and PIs for "local name"... aLocalName = aNode.mNode->NodeName(); return; } aLocalName.Truncate(); return; } aNode.Content() ->AsElement() ->GetAttrNameAt(aNode.mIndex) ->LocalName() ->ToString(aLocalName); // Check for html if (aNode.Content()->NodeInfo()->NamespaceEquals(kNameSpaceID_None) && aNode.Content()->IsHTMLElement()) { nsContentUtils::ASCIIToUpper(aLocalName); } } /* static */ void txXPathNodeUtils::getNodeName(const txXPathNode& aNode, nsAString& aName) { if (aNode.isDocument()) { aName.Truncate(); return; } if (aNode.isContent()) { // Elements and PIs have a name if (aNode.mNode->IsElement() || aNode.mNode->NodeType() == nsINode::PROCESSING_INSTRUCTION_NODE) { aName = aNode.Content()->NodeName(); return; } aName.Truncate(); return; } aNode.Content() ->AsElement() ->GetAttrNameAt(aNode.mIndex) ->GetQualifiedName(aName); } /* static */ int32_t txXPathNodeUtils::getNamespaceID(const txXPathNode& aNode) { if (aNode.isDocument()) { return kNameSpaceID_None; } if (aNode.isContent()) { return aNode.Content()->GetNameSpaceID(); } return aNode.Content() ->AsElement() ->GetAttrNameAt(aNode.mIndex) ->NamespaceID(); } /* static */ void txXPathNodeUtils::getNamespaceURI(const txXPathNode& aNode, nsAString& aURI) { nsNameSpaceManager::GetInstance()->GetNameSpaceURI(getNamespaceID(aNode), aURI); } /* static */ uint16_t txXPathNodeUtils::getNodeType(const txXPathNode& aNode) { if (aNode.isDocument()) { return txXPathNodeType::DOCUMENT_NODE; } if (aNode.isContent()) { return aNode.mNode->NodeType(); } return txXPathNodeType::ATTRIBUTE_NODE; } /* static */ void txXPathNodeUtils::appendNodeValue(const txXPathNode& aNode, nsAString& aResult) { if (aNode.isAttribute()) { const nsAttrName* name = aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex); if (aResult.IsEmpty()) { aNode.Content()->AsElement()->GetAttr(name->NamespaceID(), name->LocalName(), aResult); } else { nsAutoString result; aNode.Content()->AsElement()->GetAttr(name->NamespaceID(), name->LocalName(), result); aResult.Append(result); } return; } if (aNode.isDocument() || aNode.mNode->IsElement() || aNode.mNode->IsDocumentFragment()) { nsContentUtils::AppendNodeTextContent(aNode.mNode, true, aResult, mozilla::fallible); return; } MOZ_ASSERT(aNode.mNode->IsCharacterData()); static_cast(aNode.Content())->AppendTextTo(aResult); } /* static */ bool txXPathNodeUtils::isWhitespace(const txXPathNode& aNode) { NS_ASSERTION(aNode.isContent() && isText(aNode), "Wrong type!"); return aNode.Content()->TextIsOnlyWhitespace(); } /* static */ txXPathNode* txXPathNodeUtils::getOwnerDocument(const txXPathNode& aNode) { return new txXPathNode(aNode.mNode->OwnerDoc()); } const char gPrintfFmt[] = "id0x%" PRIxPTR; const char gPrintfFmtAttr[] = "id0x%" PRIxPTR "-%010i"; /* static */ nsresult txXPathNodeUtils::getXSLTId(const txXPathNode& aNode, const txXPathNode& aBase, nsAString& aResult) { uintptr_t nodeid = ((uintptr_t)aNode.mNode) - ((uintptr_t)aBase.mNode); if (!aNode.isAttribute()) { CopyASCIItoUTF16(nsPrintfCString(gPrintfFmt, nodeid), aResult); } else { CopyASCIItoUTF16(nsPrintfCString(gPrintfFmtAttr, nodeid, aNode.mIndex), aResult); } return NS_OK; } /* static */ nsresult txXPathNodeUtils::getBaseURI(const txXPathNode& aNode, nsAString& aURI) { return aNode.mNode->GetBaseURI(aURI); } /* static */ int txXPathNodeUtils::comparePosition(const txXPathNode& aNode, const txXPathNode& aOtherNode) { // First check for equal nodes or attribute-nodes on the same element. if (aNode.mNode == aOtherNode.mNode) { if (aNode.mIndex == aOtherNode.mIndex) { return 0; } NS_ASSERTION(!aNode.isDocument() && !aOtherNode.isDocument(), "documents should always have a set index"); if (aNode.isContent() || (!aOtherNode.isContent() && aNode.mIndex < aOtherNode.mIndex)) { return -1; } return 1; } // Get document for both nodes. Document* document = aNode.mNode->GetUncomposedDoc(); Document* otherDocument = aOtherNode.mNode->GetUncomposedDoc(); // If the nodes have different current documents, compare the document // pointers. if (document != otherDocument) { return document < otherDocument ? -1 : 1; } // Now either both nodes are in orphan trees, or they are both in the // same tree. // Get parents up the tree. AutoTArray parents, otherParents; nsINode* node = aNode.mNode; nsINode* otherNode = aOtherNode.mNode; nsINode* parent; nsINode* otherParent; while (node && otherNode) { parent = node->GetParentNode(); otherParent = otherNode->GetParentNode(); // Hopefully this is a common case. if (parent == otherParent) { if (!parent) { // Both node and otherNode are root nodes in respective orphan // tree. return node < otherNode ? -1 : 1; } const Maybe indexOfNode = parent->ComputeIndexOf(node); const Maybe indexOfOtherNode = parent->ComputeIndexOf(otherNode); if (MOZ_LIKELY(indexOfNode.isSome() && indexOfOtherNode.isSome())) { return *indexOfNode < *indexOfOtherNode ? -1 : 1; } // XXX Keep the odd traditional behavior for now. return indexOfNode.isNothing() && indexOfOtherNode.isSome() ? -1 : 1; } parents.AppendElement(node); otherParents.AppendElement(otherNode); node = parent; otherNode = otherParent; } while (node) { parents.AppendElement(node); node = node->GetParentNode(); } while (otherNode) { otherParents.AppendElement(otherNode); otherNode = otherNode->GetParentNode(); } // Walk back down along the parent-chains until we find where they split. int32_t total = parents.Length() - 1; int32_t otherTotal = otherParents.Length() - 1; NS_ASSERTION(total != otherTotal, "Can't have same number of parents"); int32_t lastIndex = std::min(total, otherTotal); int32_t i; parent = nullptr; for (i = 0; i <= lastIndex; ++i) { node = parents.ElementAt(total - i); otherNode = otherParents.ElementAt(otherTotal - i); if (node != otherNode) { if (!parent) { // The two nodes are in different orphan subtrees. NS_ASSERTION(i == 0, "this shouldn't happen"); return node < otherNode ? -1 : 1; } const Maybe index = parent->ComputeIndexOf(node); const Maybe otherIndex = parent->ComputeIndexOf(otherNode); if (MOZ_LIKELY(index.isSome() && otherIndex.isSome())) { NS_ASSERTION(*index != *otherIndex, "invalid index in comparePosition"); return *index < *otherIndex ? -1 : 1; } NS_ASSERTION(false, "invalid index in comparePosition"); // XXX Keep the odd traditional behavior for now. return index.isNothing() && otherIndex.isSome() ? -1 : 1; } parent = node; } // One node is a descendant of the other. The one with the shortest // parent-chain is first in the document. return total < otherTotal ? -1 : 1; } /* static */ txXPathNode* txXPathNativeNode::createXPathNode(nsIContent* aContent, bool aKeepRootAlive) { nsINode* root = aKeepRootAlive ? txXPathNode::RootOf(aContent) : nullptr; return new txXPathNode(aContent, txXPathNode::eContent, root); } /* static */ txXPathNode* txXPathNativeNode::createXPathNode(nsINode* aNode, bool aKeepRootAlive) { uint16_t nodeType = aNode->NodeType(); if (nodeType == nsINode::ATTRIBUTE_NODE) { auto* attr = static_cast(aNode); NodeInfo* nodeInfo = attr->NodeInfo(); Element* parent = attr->GetElement(); if (!parent) { return nullptr; } nsINode* root = aKeepRootAlive ? txXPathNode::RootOf(parent) : nullptr; uint32_t i, total = parent->GetAttrCount(); for (i = 0; i < total; ++i) { const nsAttrName* name = parent->GetAttrNameAt(i); if (nodeInfo->Equals(name->LocalName(), name->NamespaceID())) { return new txXPathNode(parent, i, root); } } NS_ERROR("Couldn't find the attribute in its parent!"); return nullptr; } uint32_t index; nsINode* root = aKeepRootAlive ? aNode : nullptr; if (nodeType == nsINode::DOCUMENT_NODE) { index = txXPathNode::eDocument; } else { index = txXPathNode::eContent; if (root) { root = txXPathNode::RootOf(root); } } return new txXPathNode(aNode, index, root); } /* static */ txXPathNode* txXPathNativeNode::createXPathNode(Document* aDocument) { return new txXPathNode(aDocument); } /* static */ nsINode* txXPathNativeNode::getNode(const txXPathNode& aNode) { if (!aNode.isAttribute()) { return aNode.mNode; } const nsAttrName* name = aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex); nsAutoString namespaceURI; nsNameSpaceManager::GetInstance()->GetNameSpaceURI(name->NamespaceID(), namespaceURI); nsCOMPtr element = do_QueryInterface(aNode.mNode); nsDOMAttributeMap* map = element->Attributes(); return map->GetNamedItemNS(namespaceURI, nsDependentAtomString(name->LocalName())); } /* static */ nsIContent* txXPathNativeNode::getContent(const txXPathNode& aNode) { NS_ASSERTION(aNode.isContent(), "Only call getContent on nsIContent wrappers!"); return aNode.Content(); } /* static */ Document* txXPathNativeNode::getDocument(const txXPathNode& aNode) { NS_ASSERTION(aNode.isDocument(), "Only call getDocument on Document wrappers!"); return aNode.Document(); }