1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "txXPathTreeWalker.h"
9 #include "nsPrintfCString.h"
10 #include "nsReadableUtils.h"
12 #include "nsTextFragment.h"
13 #include "txXMLUtils.h"
15 #include "nsUnicharUtils.h"
16 #include "nsAttrName.h"
17 #include "nsNameSpaceManager.h"
19 #include "mozilla/Maybe.h"
20 #include "mozilla/dom/Attr.h"
21 #include "mozilla/dom/CharacterData.h"
22 #include "mozilla/dom/Element.h"
26 using namespace mozilla::dom
;
29 txXPathTreeWalker::txXPathTreeWalker(const txXPathTreeWalker
& aOther
) = default;
31 txXPathTreeWalker::txXPathTreeWalker(const txXPathNode
& aNode
)
34 void txXPathTreeWalker::moveToRoot() {
35 if (mPosition
.isDocument()) {
39 Document
* root
= mPosition
.mNode
->GetUncomposedDoc();
41 mPosition
.mIndex
= txXPathNode::eDocument
;
42 mPosition
.mNode
= root
;
44 nsINode
* rootNode
= mPosition
.Root();
46 NS_ASSERTION(rootNode
->IsContent(), "root of subtree wasn't an nsIContent");
48 mPosition
.mIndex
= txXPathNode::eContent
;
49 mPosition
.mNode
= rootNode
;
53 bool txXPathTreeWalker::moveToElementById(const nsAString
& aID
) {
58 Document
* doc
= mPosition
.mNode
->GetUncomposedDoc();
60 nsCOMPtr
<nsIContent
> content
;
62 content
= doc
->GetElementById(aID
);
64 // We're in a disconnected subtree, search only that subtree.
65 nsINode
* rootNode
= mPosition
.Root();
67 NS_ASSERTION(rootNode
->IsContent(), "root of subtree wasn't an nsIContent");
70 nsContentUtils::MatchElementId(static_cast<nsIContent
*>(rootNode
), aID
);
77 mPosition
.mIndex
= txXPathNode::eContent
;
78 mPosition
.mNode
= content
;
83 bool txXPathTreeWalker::moveToFirstAttribute() {
84 if (!mPosition
.isContent()) {
88 return moveToValidAttribute(0);
91 bool txXPathTreeWalker::moveToNextAttribute() {
92 // XXX an assertion should be enough here with the current code
93 if (!mPosition
.isAttribute()) {
97 return moveToValidAttribute(mPosition
.mIndex
+ 1);
100 bool txXPathTreeWalker::moveToValidAttribute(uint32_t aStartIndex
) {
101 NS_ASSERTION(!mPosition
.isDocument(), "documents doesn't have attrs");
103 if (!mPosition
.Content()->IsElement()) {
107 Element
* element
= mPosition
.Content()->AsElement();
108 uint32_t total
= element
->GetAttrCount();
109 if (aStartIndex
>= total
) {
114 for (index
= aStartIndex
; index
< total
; ++index
) {
115 const nsAttrName
* name
= element
->GetAttrNameAt(index
);
117 // We need to ignore XMLNS attributes.
118 if (name
->NamespaceID() != kNameSpaceID_XMLNS
) {
119 mPosition
.mIndex
= index
;
127 bool txXPathTreeWalker::moveToNamedAttribute(nsAtom
* aLocalName
,
129 if (!mPosition
.isContent() || !mPosition
.Content()->IsElement()) {
133 Element
* element
= mPosition
.Content()->AsElement();
135 const nsAttrName
* name
;
137 for (i
= 0; (name
= element
->GetAttrNameAt(i
)); ++i
) {
138 if (name
->Equals(aLocalName
, aNSID
)) {
139 mPosition
.mIndex
= i
;
147 bool txXPathTreeWalker::moveToFirstChild() {
148 if (mPosition
.isAttribute()) {
152 nsIContent
* child
= mPosition
.mNode
->GetFirstChild();
156 mPosition
.mIndex
= txXPathNode::eContent
;
157 mPosition
.mNode
= child
;
162 bool txXPathTreeWalker::moveToLastChild() {
163 if (mPosition
.isAttribute()) {
167 nsIContent
* child
= mPosition
.mNode
->GetLastChild();
172 mPosition
.mIndex
= txXPathNode::eContent
;
173 mPosition
.mNode
= child
;
178 bool txXPathTreeWalker::moveToNextSibling() {
179 if (!mPosition
.isContent()) {
183 nsINode
* sibling
= mPosition
.mNode
->GetNextSibling();
188 mPosition
.mNode
= sibling
;
193 bool txXPathTreeWalker::moveToPreviousSibling() {
194 if (!mPosition
.isContent()) {
198 nsINode
* sibling
= mPosition
.mNode
->GetPreviousSibling();
203 mPosition
.mNode
= sibling
;
208 bool txXPathTreeWalker::moveToParent() {
209 if (mPosition
.isDocument()) {
213 if (mPosition
.isAttribute()) {
214 mPosition
.mIndex
= txXPathNode::eContent
;
219 nsINode
* parent
= mPosition
.mNode
->GetParentNode();
224 mPosition
.mIndex
= mPosition
.mNode
->GetParent() ? txXPathNode::eContent
225 : txXPathNode::eDocument
;
226 mPosition
.mNode
= parent
;
231 txXPathNode::txXPathNode(const txXPathNode
& aNode
)
232 : mNode(aNode
.mNode
),
233 mRefCountRoot(aNode
.mRefCountRoot
),
234 mIndex(aNode
.mIndex
) {
235 MOZ_COUNT_CTOR(txXPathNode
);
241 txXPathNode::~txXPathNode() {
242 MOZ_COUNT_DTOR(txXPathNode
);
244 nsINode
* root
= Root();
250 bool txXPathNodeUtils::getAttr(const txXPathNode
& aNode
, nsAtom
* aLocalName
,
251 int32_t aNSID
, nsAString
& aValue
) {
252 if (aNode
.isDocument() || aNode
.isAttribute() ||
253 !aNode
.Content()->IsElement()) {
257 return aNode
.Content()->AsElement()->GetAttr(aNSID
, aLocalName
, aValue
);
261 already_AddRefed
<nsAtom
> txXPathNodeUtils::getLocalName(
262 const txXPathNode
& aNode
) {
263 if (aNode
.isDocument()) {
267 if (aNode
.isContent()) {
268 if (aNode
.mNode
->IsElement()) {
269 RefPtr
<nsAtom
> localName
= aNode
.Content()->NodeInfo()->NameAtom();
270 return localName
.forget();
273 if (aNode
.mNode
->IsProcessingInstruction()) {
274 return NS_Atomize(aNode
.mNode
->NodeName());
280 // This is an attribute node, so we necessarily come from an element.
281 RefPtr
<nsAtom
> localName
=
282 aNode
.Content()->AsElement()->GetAttrNameAt(aNode
.mIndex
)->LocalName();
284 return localName
.forget();
287 nsAtom
* txXPathNodeUtils::getPrefix(const txXPathNode
& aNode
) {
288 if (aNode
.isDocument()) {
292 if (aNode
.isContent()) {
293 // All other nsIContent node types but elements have a null prefix
294 // which is what we want here.
295 return aNode
.Content()->NodeInfo()->GetPrefixAtom();
298 return aNode
.Content()->AsElement()->GetAttrNameAt(aNode
.mIndex
)->GetPrefix();
302 void txXPathNodeUtils::getLocalName(const txXPathNode
& aNode
,
303 nsAString
& aLocalName
) {
304 if (aNode
.isDocument()) {
305 aLocalName
.Truncate();
310 if (aNode
.isContent()) {
311 if (aNode
.mNode
->IsElement()) {
312 mozilla::dom::NodeInfo
* nodeInfo
= aNode
.Content()->NodeInfo();
313 nodeInfo
->GetName(aLocalName
);
317 if (aNode
.mNode
->IsProcessingInstruction()) {
318 // PIs don't have a nodeinfo but do have a name
319 // XXXbz Not actually true, but this function looks like it wants
320 // different things from elements and PIs for "local name"...
321 aLocalName
= aNode
.mNode
->NodeName();
325 aLocalName
.Truncate();
332 ->GetAttrNameAt(aNode
.mIndex
)
334 ->ToString(aLocalName
);
337 if (aNode
.Content()->NodeInfo()->NamespaceEquals(kNameSpaceID_None
) &&
338 aNode
.Content()->IsHTMLElement()) {
339 nsContentUtils::ASCIIToUpper(aLocalName
);
344 void txXPathNodeUtils::getNodeName(const txXPathNode
& aNode
, nsAString
& aName
) {
345 if (aNode
.isDocument()) {
351 if (aNode
.isContent()) {
352 // Elements and PIs have a name
353 if (aNode
.mNode
->IsElement() ||
354 aNode
.mNode
->NodeType() == nsINode::PROCESSING_INSTRUCTION_NODE
) {
355 aName
= aNode
.Content()->NodeName();
366 ->GetAttrNameAt(aNode
.mIndex
)
367 ->GetQualifiedName(aName
);
371 int32_t txXPathNodeUtils::getNamespaceID(const txXPathNode
& aNode
) {
372 if (aNode
.isDocument()) {
373 return kNameSpaceID_None
;
376 if (aNode
.isContent()) {
377 return aNode
.Content()->GetNameSpaceID();
380 return aNode
.Content()
382 ->GetAttrNameAt(aNode
.mIndex
)
387 void txXPathNodeUtils::getNamespaceURI(const txXPathNode
& aNode
,
389 nsNameSpaceManager::GetInstance()->GetNameSpaceURI(getNamespaceID(aNode
),
394 uint16_t txXPathNodeUtils::getNodeType(const txXPathNode
& aNode
) {
395 if (aNode
.isDocument()) {
396 return txXPathNodeType::DOCUMENT_NODE
;
399 if (aNode
.isContent()) {
400 return aNode
.mNode
->NodeType();
403 return txXPathNodeType::ATTRIBUTE_NODE
;
407 void txXPathNodeUtils::appendNodeValue(const txXPathNode
& aNode
,
408 nsAString
& aResult
) {
409 if (aNode
.isAttribute()) {
410 const nsAttrName
* name
=
411 aNode
.Content()->AsElement()->GetAttrNameAt(aNode
.mIndex
);
413 if (aResult
.IsEmpty()) {
414 aNode
.Content()->AsElement()->GetAttr(name
->NamespaceID(),
415 name
->LocalName(), aResult
);
418 aNode
.Content()->AsElement()->GetAttr(name
->NamespaceID(),
419 name
->LocalName(), result
);
420 aResult
.Append(result
);
426 if (aNode
.isDocument() || aNode
.mNode
->IsElement() ||
427 aNode
.mNode
->IsDocumentFragment()) {
428 nsContentUtils::AppendNodeTextContent(aNode
.mNode
, true, aResult
,
434 MOZ_ASSERT(aNode
.mNode
->IsCharacterData());
435 static_cast<CharacterData
*>(aNode
.Content())->AppendTextTo(aResult
);
439 bool txXPathNodeUtils::isWhitespace(const txXPathNode
& aNode
) {
440 NS_ASSERTION(aNode
.isContent() && isText(aNode
), "Wrong type!");
442 return aNode
.Content()->TextIsOnlyWhitespace();
446 txXPathNode
* txXPathNodeUtils::getOwnerDocument(const txXPathNode
& aNode
) {
447 return new txXPathNode(aNode
.mNode
->OwnerDoc());
450 const char gPrintfFmt
[] = "id0x%" PRIxPTR
;
451 const char gPrintfFmtAttr
[] = "id0x%" PRIxPTR
"-%010i";
454 nsresult
txXPathNodeUtils::getXSLTId(const txXPathNode
& aNode
,
455 const txXPathNode
& aBase
,
456 nsAString
& aResult
) {
457 uintptr_t nodeid
= ((uintptr_t)aNode
.mNode
) - ((uintptr_t)aBase
.mNode
);
458 if (!aNode
.isAttribute()) {
459 CopyASCIItoUTF16(nsPrintfCString(gPrintfFmt
, nodeid
), aResult
);
461 CopyASCIItoUTF16(nsPrintfCString(gPrintfFmtAttr
, nodeid
, aNode
.mIndex
),
469 nsresult
txXPathNodeUtils::getBaseURI(const txXPathNode
& aNode
,
471 return aNode
.mNode
->GetBaseURI(aURI
);
475 int txXPathNodeUtils::comparePosition(const txXPathNode
& aNode
,
476 const txXPathNode
& aOtherNode
) {
477 // First check for equal nodes or attribute-nodes on the same element.
478 if (aNode
.mNode
== aOtherNode
.mNode
) {
479 if (aNode
.mIndex
== aOtherNode
.mIndex
) {
483 NS_ASSERTION(!aNode
.isDocument() && !aOtherNode
.isDocument(),
484 "documents should always have a set index");
486 if (aNode
.isContent() ||
487 (!aOtherNode
.isContent() && aNode
.mIndex
< aOtherNode
.mIndex
)) {
494 // Get document for both nodes.
495 Document
* document
= aNode
.mNode
->GetUncomposedDoc();
496 Document
* otherDocument
= aOtherNode
.mNode
->GetUncomposedDoc();
498 // If the nodes have different current documents, compare the document
500 if (document
!= otherDocument
) {
501 return document
< otherDocument
? -1 : 1;
504 // Now either both nodes are in orphan trees, or they are both in the
507 // Get parents up the tree.
508 AutoTArray
<nsINode
*, 8> parents
, otherParents
;
509 nsINode
* node
= aNode
.mNode
;
510 nsINode
* otherNode
= aOtherNode
.mNode
;
512 nsINode
* otherParent
;
513 while (node
&& otherNode
) {
514 parent
= node
->GetParentNode();
515 otherParent
= otherNode
->GetParentNode();
517 // Hopefully this is a common case.
518 if (parent
== otherParent
) {
520 // Both node and otherNode are root nodes in respective orphan
522 return node
< otherNode
? -1 : 1;
525 const Maybe
<uint32_t> indexOfNode
= parent
->ComputeIndexOf(node
);
526 const Maybe
<uint32_t> indexOfOtherNode
=
527 parent
->ComputeIndexOf(otherNode
);
528 if (MOZ_LIKELY(indexOfNode
.isSome() && indexOfOtherNode
.isSome())) {
529 return *indexOfNode
< *indexOfOtherNode
? -1 : 1;
531 // XXX Keep the odd traditional behavior for now.
532 return indexOfNode
.isNothing() && indexOfOtherNode
.isSome() ? -1 : 1;
535 parents
.AppendElement(node
);
536 otherParents
.AppendElement(otherNode
);
538 otherNode
= otherParent
;
542 parents
.AppendElement(node
);
543 node
= node
->GetParentNode();
546 otherParents
.AppendElement(otherNode
);
547 otherNode
= otherNode
->GetParentNode();
550 // Walk back down along the parent-chains until we find where they split.
551 int32_t total
= parents
.Length() - 1;
552 int32_t otherTotal
= otherParents
.Length() - 1;
553 NS_ASSERTION(total
!= otherTotal
, "Can't have same number of parents");
555 int32_t lastIndex
= std::min(total
, otherTotal
);
558 for (i
= 0; i
<= lastIndex
; ++i
) {
559 node
= parents
.ElementAt(total
- i
);
560 otherNode
= otherParents
.ElementAt(otherTotal
- i
);
561 if (node
!= otherNode
) {
563 // The two nodes are in different orphan subtrees.
564 NS_ASSERTION(i
== 0, "this shouldn't happen");
565 return node
< otherNode
? -1 : 1;
568 const Maybe
<uint32_t> index
= parent
->ComputeIndexOf(node
);
569 const Maybe
<uint32_t> otherIndex
= parent
->ComputeIndexOf(otherNode
);
570 if (MOZ_LIKELY(index
.isSome() && otherIndex
.isSome())) {
571 NS_ASSERTION(*index
!= *otherIndex
, "invalid index in comparePosition");
572 return *index
< *otherIndex
? -1 : 1;
574 NS_ASSERTION(false, "invalid index in comparePosition");
575 // XXX Keep the odd traditional behavior for now.
576 return index
.isNothing() && otherIndex
.isSome() ? -1 : 1;
582 // One node is a descendant of the other. The one with the shortest
583 // parent-chain is first in the document.
584 return total
< otherTotal
? -1 : 1;
588 txXPathNode
* txXPathNativeNode::createXPathNode(nsIContent
* aContent
,
589 bool aKeepRootAlive
) {
590 nsINode
* root
= aKeepRootAlive
? txXPathNode::RootOf(aContent
) : nullptr;
592 return new txXPathNode(aContent
, txXPathNode::eContent
, root
);
596 txXPathNode
* txXPathNativeNode::createXPathNode(nsINode
* aNode
,
597 bool aKeepRootAlive
) {
598 uint16_t nodeType
= aNode
->NodeType();
599 if (nodeType
== nsINode::ATTRIBUTE_NODE
) {
600 auto* attr
= static_cast<Attr
*>(aNode
);
602 NodeInfo
* nodeInfo
= attr
->NodeInfo();
603 Element
* parent
= attr
->GetElement();
608 nsINode
* root
= aKeepRootAlive
? txXPathNode::RootOf(parent
) : nullptr;
610 uint32_t i
, total
= parent
->GetAttrCount();
611 for (i
= 0; i
< total
; ++i
) {
612 const nsAttrName
* name
= parent
->GetAttrNameAt(i
);
613 if (nodeInfo
->Equals(name
->LocalName(), name
->NamespaceID())) {
614 return new txXPathNode(parent
, i
, root
);
618 NS_ERROR("Couldn't find the attribute in its parent!");
624 nsINode
* root
= aKeepRootAlive
? aNode
: nullptr;
626 if (nodeType
== nsINode::DOCUMENT_NODE
) {
627 index
= txXPathNode::eDocument
;
629 index
= txXPathNode::eContent
;
631 root
= txXPathNode::RootOf(root
);
635 return new txXPathNode(aNode
, index
, root
);
639 txXPathNode
* txXPathNativeNode::createXPathNode(Document
* aDocument
) {
640 return new txXPathNode(aDocument
);
644 nsINode
* txXPathNativeNode::getNode(const txXPathNode
& aNode
) {
645 if (!aNode
.isAttribute()) {
649 const nsAttrName
* name
=
650 aNode
.Content()->AsElement()->GetAttrNameAt(aNode
.mIndex
);
652 nsAutoString namespaceURI
;
653 nsNameSpaceManager::GetInstance()->GetNameSpaceURI(name
->NamespaceID(),
656 nsCOMPtr
<Element
> element
= do_QueryInterface(aNode
.mNode
);
657 nsDOMAttributeMap
* map
= element
->Attributes();
658 return map
->GetNamedItemNS(namespaceURI
,
659 nsDependentAtomString(name
->LocalName()));
663 nsIContent
* txXPathNativeNode::getContent(const txXPathNode
& aNode
) {
664 NS_ASSERTION(aNode
.isContent(),
665 "Only call getContent on nsIContent wrappers!");
666 return aNode
.Content();
670 Document
* txXPathNativeNode::getDocument(const txXPathNode
& aNode
) {
671 NS_ASSERTION(aNode
.isDocument(),
672 "Only call getDocument on Document wrappers!");
673 return aNode
.Document();