txMozillaXPathTreeWalker.cpp (16977B)
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/. */ 5 6 #include <stdint.h> 7 8 #include <algorithm> 9 10 #include "mozilla/Maybe.h" 11 #include "mozilla/dom/Attr.h" 12 #include "mozilla/dom/CharacterData.h" 13 #include "mozilla/dom/Element.h" 14 #include "nsAtom.h" 15 #include "nsAttrName.h" 16 #include "nsINode.h" 17 #include "nsNameSpaceManager.h" 18 #include "nsPrintfCString.h" 19 #include "nsReadableUtils.h" 20 #include "nsString.h" 21 #include "nsTArray.h" 22 #include "nsUnicharUtils.h" 23 #include "txLog.h" 24 #include "txXMLUtils.h" 25 #include "txXPathTreeWalker.h" 26 27 using namespace mozilla; 28 using namespace mozilla::dom; 29 30 txXPathTreeWalker::txXPathTreeWalker(const txXPathTreeWalker& aOther) = default; 31 32 txXPathTreeWalker::txXPathTreeWalker(const txXPathNode& aNode) 33 : mPosition(aNode) {} 34 35 void txXPathTreeWalker::moveToRoot() { 36 if (mPosition.isDocument()) { 37 return; 38 } 39 40 Document* root = mPosition.mNode->GetUncomposedDoc(); 41 if (root) { 42 mPosition.mIndex = txXPathNode::eDocument; 43 mPosition.mNode = root; 44 } else { 45 nsINode* rootNode = mPosition.Root(); 46 47 NS_ASSERTION(rootNode->IsContent(), "root of subtree wasn't an nsIContent"); 48 49 mPosition.mIndex = txXPathNode::eContent; 50 mPosition.mNode = rootNode; 51 } 52 } 53 54 bool txXPathTreeWalker::moveToElementById(const nsAString& aID) { 55 if (aID.IsEmpty()) { 56 return false; 57 } 58 59 Document* doc = mPosition.mNode->GetUncomposedDoc(); 60 61 nsCOMPtr<nsIContent> content; 62 if (doc) { 63 content = doc->GetElementById(aID); 64 } else { 65 // We're in a disconnected subtree, search only that subtree. 66 nsINode* rootNode = mPosition.Root(); 67 68 NS_ASSERTION(rootNode->IsContent(), "root of subtree wasn't an nsIContent"); 69 70 content = 71 nsContentUtils::MatchElementId(static_cast<nsIContent*>(rootNode), aID); 72 } 73 74 if (!content) { 75 return false; 76 } 77 78 mPosition.mIndex = txXPathNode::eContent; 79 mPosition.mNode = content; 80 81 return true; 82 } 83 84 bool txXPathTreeWalker::moveToFirstAttribute() { 85 if (!mPosition.isContent()) { 86 return false; 87 } 88 89 return moveToValidAttribute(0); 90 } 91 92 bool txXPathTreeWalker::moveToNextAttribute() { 93 // XXX an assertion should be enough here with the current code 94 if (!mPosition.isAttribute()) { 95 return false; 96 } 97 98 return moveToValidAttribute(mPosition.mIndex + 1); 99 } 100 101 bool txXPathTreeWalker::moveToValidAttribute(uint32_t aStartIndex) { 102 NS_ASSERTION(!mPosition.isDocument(), "documents doesn't have attrs"); 103 104 if (!mPosition.Content()->IsElement()) { 105 return false; 106 } 107 108 Element* element = mPosition.Content()->AsElement(); 109 uint32_t total = element->GetAttrCount(); 110 if (aStartIndex >= total) { 111 return false; 112 } 113 114 uint32_t index; 115 for (index = aStartIndex; index < total; ++index) { 116 const nsAttrName* name = element->GetAttrNameAt(index); 117 118 // We need to ignore XMLNS attributes. 119 if (name->NamespaceID() != kNameSpaceID_XMLNS) { 120 mPosition.mIndex = index; 121 122 return true; 123 } 124 } 125 return false; 126 } 127 128 bool txXPathTreeWalker::moveToNamedAttribute(nsAtom* aLocalName, 129 int32_t aNSID) { 130 if (!mPosition.isContent() || !mPosition.Content()->IsElement()) { 131 return false; 132 } 133 134 Element* element = mPosition.Content()->AsElement(); 135 136 const nsAttrName* name; 137 uint32_t i; 138 for (i = 0; element->GetAttrNameAt(i, &name); ++i) { 139 if (name->Equals(aLocalName, aNSID)) { 140 mPosition.mIndex = i; 141 142 return true; 143 } 144 } 145 return false; 146 } 147 148 bool txXPathTreeWalker::moveToFirstChild() { 149 if (mPosition.isAttribute()) { 150 return false; 151 } 152 153 nsIContent* child = mPosition.mNode->GetFirstChild(); 154 if (!child) { 155 return false; 156 } 157 mPosition.mIndex = txXPathNode::eContent; 158 mPosition.mNode = child; 159 160 return true; 161 } 162 163 bool txXPathTreeWalker::moveToLastChild() { 164 if (mPosition.isAttribute()) { 165 return false; 166 } 167 168 nsIContent* child = mPosition.mNode->GetLastChild(); 169 if (!child) { 170 return false; 171 } 172 173 mPosition.mIndex = txXPathNode::eContent; 174 mPosition.mNode = child; 175 176 return true; 177 } 178 179 bool txXPathTreeWalker::moveToNextSibling() { 180 if (!mPosition.isContent()) { 181 return false; 182 } 183 184 nsINode* sibling = mPosition.mNode->GetNextSibling(); 185 if (!sibling) { 186 return false; 187 } 188 189 mPosition.mNode = sibling; 190 191 return true; 192 } 193 194 bool txXPathTreeWalker::moveToPreviousSibling() { 195 if (!mPosition.isContent()) { 196 return false; 197 } 198 199 nsINode* sibling = mPosition.mNode->GetPreviousSibling(); 200 if (!sibling) { 201 return false; 202 } 203 204 mPosition.mNode = sibling; 205 206 return true; 207 } 208 209 bool txXPathTreeWalker::moveToParent() { 210 if (mPosition.isDocument()) { 211 return false; 212 } 213 214 if (mPosition.isAttribute()) { 215 mPosition.mIndex = txXPathNode::eContent; 216 217 return true; 218 } 219 220 nsINode* parent = mPosition.mNode->GetParentNode(); 221 if (!parent) { 222 return false; 223 } 224 225 mPosition.mIndex = mPosition.mNode->GetParent() ? txXPathNode::eContent 226 : txXPathNode::eDocument; 227 mPosition.mNode = parent; 228 229 return true; 230 } 231 232 /* static */ 233 bool txXPathNodeUtils::getAttr(const txXPathNode& aNode, nsAtom* aLocalName, 234 int32_t aNSID, nsAString& aValue) { 235 if (aNode.isDocument() || aNode.isAttribute() || 236 !aNode.Content()->IsElement()) { 237 return false; 238 } 239 240 return aNode.Content()->AsElement()->GetAttr(aNSID, aLocalName, aValue); 241 } 242 243 /* static */ 244 already_AddRefed<nsAtom> txXPathNodeUtils::getLocalName( 245 const txXPathNode& aNode) { 246 if (aNode.isDocument()) { 247 return nullptr; 248 } 249 250 if (aNode.isContent()) { 251 if (aNode.mNode->IsElement()) { 252 RefPtr<nsAtom> localName = aNode.Content()->NodeInfo()->NameAtom(); 253 return localName.forget(); 254 } 255 256 if (aNode.mNode->IsProcessingInstruction()) { 257 return NS_Atomize(aNode.mNode->NodeName()); 258 } 259 260 return nullptr; 261 } 262 263 // This is an attribute node, so we necessarily come from an element. 264 RefPtr<nsAtom> localName = 265 aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex)->LocalName(); 266 267 return localName.forget(); 268 } 269 270 nsAtom* txXPathNodeUtils::getPrefix(const txXPathNode& aNode) { 271 if (aNode.isDocument()) { 272 return nullptr; 273 } 274 275 if (aNode.isContent()) { 276 // All other nsIContent node types but elements have a null prefix 277 // which is what we want here. 278 return aNode.Content()->NodeInfo()->GetPrefixAtom(); 279 } 280 281 return aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex)->GetPrefix(); 282 } 283 284 /* static */ 285 void txXPathNodeUtils::getLocalName(const txXPathNode& aNode, 286 nsAString& aLocalName) { 287 if (aNode.isDocument()) { 288 aLocalName.Truncate(); 289 290 return; 291 } 292 293 if (aNode.isContent()) { 294 if (aNode.mNode->IsElement()) { 295 mozilla::dom::NodeInfo* nodeInfo = aNode.Content()->NodeInfo(); 296 nodeInfo->GetName(aLocalName); 297 return; 298 } 299 300 if (aNode.mNode->IsProcessingInstruction()) { 301 // PIs don't have a nodeinfo but do have a name 302 // XXXbz Not actually true, but this function looks like it wants 303 // different things from elements and PIs for "local name"... 304 aLocalName = aNode.mNode->NodeName(); 305 return; 306 } 307 308 aLocalName.Truncate(); 309 310 return; 311 } 312 313 aNode.Content() 314 ->AsElement() 315 ->GetAttrNameAt(aNode.mIndex) 316 ->LocalName() 317 ->ToString(aLocalName); 318 319 // Check for html 320 if (aNode.Content()->NodeInfo()->NamespaceEquals(kNameSpaceID_None) && 321 aNode.Content()->IsHTMLElement()) { 322 nsContentUtils::ASCIIToUpper(aLocalName); 323 } 324 } 325 326 /* static */ 327 void txXPathNodeUtils::getNodeName(const txXPathNode& aNode, nsAString& aName) { 328 if (aNode.isDocument()) { 329 aName.Truncate(); 330 331 return; 332 } 333 334 if (aNode.isContent()) { 335 // Elements and PIs have a name 336 if (aNode.mNode->IsElement() || 337 aNode.mNode->NodeType() == nsINode::PROCESSING_INSTRUCTION_NODE) { 338 aName = aNode.Content()->NodeName(); 339 return; 340 } 341 342 aName.Truncate(); 343 344 return; 345 } 346 347 aNode.Content() 348 ->AsElement() 349 ->GetAttrNameAt(aNode.mIndex) 350 ->GetQualifiedName(aName); 351 } 352 353 /* static */ 354 int32_t txXPathNodeUtils::getNamespaceID(const txXPathNode& aNode) { 355 if (aNode.isDocument()) { 356 return kNameSpaceID_None; 357 } 358 359 if (aNode.isContent()) { 360 return aNode.Content()->GetNameSpaceID(); 361 } 362 363 return aNode.Content() 364 ->AsElement() 365 ->GetAttrNameAt(aNode.mIndex) 366 ->NamespaceID(); 367 } 368 369 /* static */ 370 void txXPathNodeUtils::getNamespaceURI(const txXPathNode& aNode, 371 nsAString& aURI) { 372 nsNameSpaceManager::GetInstance()->GetNameSpaceURI(getNamespaceID(aNode), 373 aURI); 374 } 375 376 /* static */ 377 uint16_t txXPathNodeUtils::getNodeType(const txXPathNode& aNode) { 378 if (aNode.isDocument()) { 379 return txXPathNodeType::DOCUMENT_NODE; 380 } 381 382 if (aNode.isContent()) { 383 return aNode.mNode->NodeType(); 384 } 385 386 return txXPathNodeType::ATTRIBUTE_NODE; 387 } 388 389 /* static */ 390 void txXPathNodeUtils::appendNodeValue(const txXPathNode& aNode, 391 nsAString& aResult) { 392 if (aNode.isAttribute()) { 393 const nsAttrName* name = 394 aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex); 395 396 if (aResult.IsEmpty()) { 397 aNode.Content()->AsElement()->GetAttr(name->NamespaceID(), 398 name->LocalName(), aResult); 399 } else { 400 nsAutoString result; 401 aNode.Content()->AsElement()->GetAttr(name->NamespaceID(), 402 name->LocalName(), result); 403 aResult.Append(result); 404 } 405 406 return; 407 } 408 409 if (aNode.isDocument() || aNode.mNode->IsElement() || 410 aNode.mNode->IsDocumentFragment()) { 411 nsContentUtils::AppendNodeTextContent(aNode.mNode, true, aResult, 412 mozilla::fallible); 413 414 return; 415 } 416 417 MOZ_ASSERT(aNode.mNode->IsCharacterData()); 418 static_cast<CharacterData*>(aNode.Content())->AppendTextTo(aResult); 419 } 420 421 /* static */ 422 bool txXPathNodeUtils::isWhitespace(const txXPathNode& aNode) { 423 NS_ASSERTION(aNode.isContent() && isText(aNode), "Wrong type!"); 424 425 return aNode.Content()->TextIsOnlyWhitespace(); 426 } 427 428 /* static */ 429 txXPathNode txXPathNodeUtils::getOwnerDocument(const txXPathNode& aNode) { 430 return txXPathNode(aNode.mNode->OwnerDoc()); 431 } 432 433 const char gPrintfFmt[] = "id0x%" PRIxPTR; 434 const char gPrintfFmtAttr[] = "id0x%" PRIxPTR "-%010i"; 435 436 /* static */ 437 nsresult txXPathNodeUtils::getXSLTId(const txXPathNode& aNode, 438 const txXPathNode& aBase, 439 nsAString& aResult) { 440 uintptr_t nodeid = 441 ((uintptr_t)aNode.mNode.get()) - ((uintptr_t)aBase.mNode.get()); 442 if (!aNode.isAttribute()) { 443 CopyASCIItoUTF16(nsPrintfCString(gPrintfFmt, nodeid), aResult); 444 } else { 445 CopyASCIItoUTF16(nsPrintfCString(gPrintfFmtAttr, nodeid, aNode.mIndex), 446 aResult); 447 } 448 449 return NS_OK; 450 } 451 452 /* static */ 453 nsresult txXPathNodeUtils::getBaseURI(const txXPathNode& aNode, 454 nsAString& aURI) { 455 return aNode.mNode->GetBaseURI(aURI); 456 } 457 458 /* static */ 459 int txXPathNodeUtils::comparePosition(const txXPathNode& aNode, 460 const txXPathNode& aOtherNode) { 461 // First check for equal nodes or attribute-nodes on the same element. 462 if (aNode.mNode == aOtherNode.mNode) { 463 if (aNode.mIndex == aOtherNode.mIndex) { 464 return 0; 465 } 466 467 NS_ASSERTION(!aNode.isDocument() && !aOtherNode.isDocument(), 468 "documents should always have a set index"); 469 470 if (aNode.isContent() || 471 (!aOtherNode.isContent() && aNode.mIndex < aOtherNode.mIndex)) { 472 return -1; 473 } 474 475 return 1; 476 } 477 478 // Get document for both nodes. 479 Document* document = aNode.mNode->GetUncomposedDoc(); 480 Document* otherDocument = aOtherNode.mNode->GetUncomposedDoc(); 481 482 // If the nodes have different current documents, compare the document 483 // pointers. 484 if (document != otherDocument) { 485 return document < otherDocument ? -1 : 1; 486 } 487 488 // Now either both nodes are in orphan trees, or they are both in the 489 // same tree. 490 491 // Get parents up the tree. 492 AutoTArray<nsINode*, 8> parents, otherParents; 493 nsINode* node = aNode.mNode; 494 nsINode* otherNode = aOtherNode.mNode; 495 nsINode* parent; 496 nsINode* otherParent; 497 while (node && otherNode) { 498 parent = node->GetParentNode(); 499 otherParent = otherNode->GetParentNode(); 500 501 // Hopefully this is a common case. 502 if (parent == otherParent) { 503 if (!parent) { 504 // Both node and otherNode are root nodes in respective orphan 505 // tree. 506 return node < otherNode ? -1 : 1; 507 } 508 509 const Maybe<uint32_t> indexOfNode = parent->ComputeIndexOf(node); 510 const Maybe<uint32_t> indexOfOtherNode = 511 parent->ComputeIndexOf(otherNode); 512 if (MOZ_LIKELY(indexOfNode.isSome() && indexOfOtherNode.isSome())) { 513 return *indexOfNode < *indexOfOtherNode ? -1 : 1; 514 } 515 // XXX Keep the odd traditional behavior for now. 516 return indexOfNode.isNothing() && indexOfOtherNode.isSome() ? -1 : 1; 517 } 518 519 parents.AppendElement(node); 520 otherParents.AppendElement(otherNode); 521 node = parent; 522 otherNode = otherParent; 523 } 524 525 while (node) { 526 parents.AppendElement(node); 527 node = node->GetParentNode(); 528 } 529 while (otherNode) { 530 otherParents.AppendElement(otherNode); 531 otherNode = otherNode->GetParentNode(); 532 } 533 534 // Walk back down along the parent-chains until we find where they split. 535 int32_t total = parents.Length() - 1; 536 int32_t otherTotal = otherParents.Length() - 1; 537 NS_ASSERTION(total != otherTotal, "Can't have same number of parents"); 538 539 int32_t lastIndex = std::min(total, otherTotal); 540 int32_t i; 541 parent = nullptr; 542 for (i = 0; i <= lastIndex; ++i) { 543 node = parents.ElementAt(total - i); 544 otherNode = otherParents.ElementAt(otherTotal - i); 545 if (node != otherNode) { 546 if (!parent) { 547 // The two nodes are in different orphan subtrees. 548 NS_ASSERTION(i == 0, "this shouldn't happen"); 549 return node < otherNode ? -1 : 1; 550 } 551 552 const Maybe<uint32_t> index = parent->ComputeIndexOf(node); 553 const Maybe<uint32_t> otherIndex = parent->ComputeIndexOf(otherNode); 554 if (MOZ_LIKELY(index.isSome() && otherIndex.isSome())) { 555 NS_ASSERTION(*index != *otherIndex, "invalid index in comparePosition"); 556 return *index < *otherIndex ? -1 : 1; 557 } 558 NS_ASSERTION(false, "invalid index in comparePosition"); 559 // XXX Keep the odd traditional behavior for now. 560 return index.isNothing() && otherIndex.isSome() ? -1 : 1; 561 } 562 563 parent = node; 564 } 565 566 // One node is a descendant of the other. The one with the shortest 567 // parent-chain is first in the document. 568 return total < otherTotal ? -1 : 1; 569 } 570 571 /* static */ 572 Maybe<txXPathNode> txXPathNativeNode::createXPathNode(nsINode* aNode) { 573 uint16_t nodeType = aNode->NodeType(); 574 if (nodeType == nsINode::ATTRIBUTE_NODE) { 575 auto* attr = static_cast<Attr*>(aNode); 576 577 NodeInfo* nodeInfo = attr->NodeInfo(); 578 Element* parent = attr->GetElement(); 579 if (!parent) { 580 return Nothing(); 581 } 582 583 uint32_t i, total = parent->GetAttrCount(); 584 for (i = 0; i < total; ++i) { 585 const nsAttrName* name = parent->GetAttrNameAt(i); 586 if (nodeInfo->Equals(name->LocalName(), name->NamespaceID())) { 587 return Some(txXPathNode(parent, i)); 588 } 589 } 590 591 NS_ERROR("Couldn't find the attribute in its parent!"); 592 593 return Nothing(); 594 } 595 596 uint32_t index; 597 if (nodeType == nsINode::DOCUMENT_NODE) { 598 index = txXPathNode::eDocument; 599 } else { 600 index = txXPathNode::eContent; 601 } 602 603 return Some(txXPathNode(aNode, index)); 604 } 605 606 /* static */ 607 nsINode* txXPathNativeNode::getNode(const txXPathNode& aNode) { 608 if (!aNode.isAttribute()) { 609 return aNode.mNode; 610 } 611 612 const nsAttrName* name = 613 aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex); 614 615 nsAutoString namespaceURI; 616 nsNameSpaceManager::GetInstance()->GetNameSpaceURI(name->NamespaceID(), 617 namespaceURI); 618 619 nsCOMPtr<Element> element = do_QueryInterface(aNode.mNode); 620 nsDOMAttributeMap* map = element->Attributes(); 621 return map->GetNamedItemNS(namespaceURI, 622 nsDependentAtomString(name->LocalName())); 623 } 624 625 /* static */ 626 nsIContent* txXPathNativeNode::getContent(const txXPathNode& aNode) { 627 NS_ASSERTION(aNode.isContent(), 628 "Only call getContent on nsIContent wrappers!"); 629 return aNode.Content(); 630 } 631 632 /* static */ 633 Document* txXPathNativeNode::getDocument(const txXPathNode& aNode) { 634 NS_ASSERTION(aNode.isDocument(), 635 "Only call getDocument on Document wrappers!"); 636 return aNode.Document(); 637 }