/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * 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 code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2003 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Peter Van der Beken * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "txXPathTreeWalker.h" #include "nsIAtom.h" #include "nsIAttribute.h" #include "nsIDOM3Node.h" #include "nsIDOMAttr.h" #include "nsIDOMDocument.h" #include "nsIDOMNode.h" #include "nsIDOMElement.h" #include "nsIDOMProcessingInstruction.h" #include "nsINodeInfo.h" #include "nsITextContent.h" #include "nsPrintfCString.h" #include "nsReadableUtils.h" #include "nsString.h" #include "nsTextFragment.h" #include "XMLUtils.h" #include "TxLog.h" #include "nsUnicharUtils.h" const PRUint32 kUnknownIndex = PRUint32(-1); txXPathTreeWalker::txXPathTreeWalker(const txXPathTreeWalker& aOther) : mPosition(aOther.mPosition), mCurrentIndex(aOther.mCurrentIndex) { } txXPathTreeWalker::txXPathTreeWalker(const txXPathNode& aNode) : mPosition(aNode), mCurrentIndex(kUnknownIndex) { } txXPathTreeWalker::~txXPathTreeWalker() { } PRBool txXPathTreeWalker::moveToElementById(const nsAString& aID) { nsCOMPtr document; if (mPosition.isDocument()) { document = do_QueryInterface(mPosition.mDocument); } else { document = do_QueryInterface(mPosition.mContent->GetDocument()); } if (!document) { return PR_FALSE; } nsCOMPtr element; document->GetElementById(aID, getter_AddRefs(element)); if (!element) { return PR_FALSE; } nsCOMPtr content = do_QueryInterface(element); NS_ENSURE_TRUE(content, PR_FALSE); mPosition.mIndex = txXPathNode::eContent; mPosition.mContent = content; mCurrentIndex = kUnknownIndex; mDescendants.Clear(); return PR_TRUE; } PRBool txXPathTreeWalker::moveToFirstAttribute() { if (!mPosition.isContent()) { return PR_FALSE; } return moveToValidAttribute(0); } PRBool txXPathTreeWalker::moveToNextAttribute() { // XXX an assertion should be enough here with the current code if (!mPosition.isAttribute()) { return PR_FALSE; } return moveToValidAttribute(mPosition.mIndex + 1); } PRBool txXPathTreeWalker::moveToValidAttribute(PRUint32 aStartIndex) { NS_ASSERTION(!mPosition.isDocument(), "documents doesn't have attrs"); PRUint32 total = mPosition.mContent->GetAttrCount(); if (aStartIndex >= total) { return PR_FALSE; } PRInt32 namespaceID; nsCOMPtr name, prefix; PRUint32 index; for (index = aStartIndex; index < total; ++index) { mPosition.mContent->GetAttrNameAt(index, &namespaceID, getter_AddRefs(name), getter_AddRefs(prefix)); // We need to ignore XMLNS attributes. if (namespaceID != kNameSpaceID_XMLNS) { mPosition.mIndex = index; return PR_TRUE; } } return PR_FALSE; } PRBool txXPathTreeWalker::moveToFirstChild() { if (mPosition.isAttribute()) { return PR_FALSE; } if (mPosition.isDocument()) { nsIContent* child = mPosition.mDocument->GetChildAt(0); if (!child) { return PR_FALSE; } mPosition.mIndex = txXPathNode::eContent; mPosition.mContent = child; NS_ASSERTION(mCurrentIndex == kUnknownIndex && !mDescendants.Count(), "we shouldn't have any position info at the document"); mCurrentIndex = 0; return PR_TRUE; } nsIContent* child = mPosition.mContent->GetChildAt(0); if (!child) { return PR_FALSE; } mPosition.mContent = child; NS_ASSERTION(mCurrentIndex != kUnknownIndex || !mDescendants.Count(), "Index should be known if parents index are"); if (mCurrentIndex != kUnknownIndex && !mDescendants.AppendValue(mCurrentIndex)) { mDescendants.Clear(); } mCurrentIndex = 0; return PR_TRUE; } PRBool txXPathTreeWalker::moveToLastChild() { if (mPosition.isAttribute()) { return PR_FALSE; } if (mPosition.isDocument()) { PRUint32 total = mPosition.mDocument->GetChildCount(); if (!total) { return PR_FALSE; } mPosition.mIndex = txXPathNode::eContent; mPosition.mContent = mPosition.mDocument->GetChildAt(total - 1); NS_ASSERTION(mCurrentIndex == kUnknownIndex && !mDescendants.Count(), "we shouldn't have any position info at the document"); mCurrentIndex = total - 1; return PR_TRUE; } PRUint32 total = mPosition.mContent->GetChildCount(); if (!total) { return PR_FALSE; } mPosition.mContent = mPosition.mContent->GetChildAt(total - 1); NS_ASSERTION(mCurrentIndex != kUnknownIndex || !mDescendants.Count(), "Index should be known if parents index are"); if (mCurrentIndex != kUnknownIndex && !mDescendants.AppendValue(mCurrentIndex)) { mDescendants.Clear(); } mCurrentIndex = total - 1; return PR_TRUE; } PRBool txXPathTreeWalker::moveToNextSibling() { if (!mPosition.isContent()) { return PR_FALSE; } return moveToSibling(1); } PRBool txXPathTreeWalker::moveToPreviousSibling() { if (!mPosition.isContent()) { return PR_FALSE; } return moveToSibling(-1); } PRBool txXPathTreeWalker::moveToParent() { if (mPosition.isDocument()) { return PR_FALSE; } if (mPosition.isAttribute()) { mPosition.mIndex = txXPathNode::eContent; return PR_TRUE; } nsIContent *parent = mPosition.mContent->GetParent(); if (parent) { mPosition.mContent = parent; PRInt32 count = mDescendants.Count(); if (count) { mCurrentIndex = mDescendants.ValueAt(--count); mDescendants.RemoveValueAt(count); } else { mCurrentIndex = kUnknownIndex; } return PR_TRUE; } nsIDocument* document = mPosition.mContent->GetDocument(); if (!document) { return PR_FALSE; } mPosition.mIndex = txXPathNode::eDocument; mPosition.mDocument = document; return PR_TRUE; } PRBool txXPathTreeWalker::moveToSibling(PRInt32 aDir) { NS_ASSERTION(mPosition.isContent(), "moveToSibling should only be called for content"); nsIDocument* document; nsIContent* parent = mPosition.mContent->GetParent(); if (!parent) { document = mPosition.mContent->GetDocument(); if (!document) { return PR_FALSE; } } if (mCurrentIndex == kUnknownIndex) { mCurrentIndex = parent ? parent->IndexOf(mPosition.mContent) : document->IndexOf(mPosition.mContent); } // if mCurrentIndex is 0 we rely on GetChildAt returning null for an // index of PRUint32(-1). PRUint32 newIndex = mCurrentIndex + aDir; nsIContent* newChild = parent ? parent->GetChildAt(newIndex) : document->GetChildAt(newIndex); if (!newChild) { return PR_FALSE; } mPosition.mContent = newChild; mCurrentIndex = newIndex; return PR_TRUE; } txXPathNode::txXPathNode(const txXPathNode& aNode) : mIndex(aNode.mIndex) { // Hopefully it's ok to access mContent through mDocument. mDocument = aNode.mDocument; } PRBool txXPathNode::operator==(const txXPathNode& aNode) const { if (mIndex != aNode.mIndex) { return PR_FALSE; } // Hopefully it's ok to access mContent through mDocument. return (mDocument == aNode.mDocument); } /* static */ PRBool txXPathNodeUtils::getAttr(const txXPathNode& aNode, nsIAtom* aLocalName, PRInt32 aNSID, nsAString& aValue) { if (aNode.isDocument() || aNode.isAttribute()) { return PR_FALSE; } nsresult rv = aNode.mContent->GetAttr(aNSID, aLocalName, aValue); return NS_SUCCEEDED(rv) && rv != NS_CONTENT_ATTR_NOT_THERE; } /* static */ already_AddRefed txXPathNodeUtils::getLocalName(const txXPathNode& aNode) { if (aNode.isDocument()) { return nsnull; } if (aNode.isContent()) { if (aNode.mContent->IsContentOfType(nsIContent::eELEMENT)) { nsIAtom* localName = aNode.mContent->Tag(); NS_ADDREF(localName); return localName; } if (aNode.mContent->IsContentOfType(nsIContent::ePROCESSING_INSTRUCTION)) { nsCOMPtr node = do_QueryInterface(aNode.mContent); nsAutoString target; node->GetNodeName(target); return NS_NewAtom(target); } return nsnull; } nsIAtom* localName; nsCOMPtr prefix; PRInt32 namespaceID; aNode.mContent->GetAttrNameAt(aNode.mIndex, &namespaceID, &localName, getter_AddRefs(prefix)); return localName; } /* static */ void txXPathNodeUtils::getLocalName(const txXPathNode& aNode, nsAString& aLocalName) { if (aNode.isDocument()) { aLocalName.Truncate(); return; } if (aNode.isContent()) { nsINodeInfo* nodeInfo = aNode.mContent->GetNodeInfo(); if (nodeInfo) { nodeInfo->GetLocalName(aLocalName); // Check for html if (nodeInfo->NamespaceEquals(kNameSpaceID_None) && aNode.mContent->IsContentOfType(nsIContent::eHTML)) { ToUpperCase(aLocalName); } return; } if (aNode.mContent->IsContentOfType(nsIContent::ePROCESSING_INSTRUCTION)) { // PIs don't have a nodeinfo but do have a name nsCOMPtr node = do_QueryInterface(aNode.mContent); node->GetNodeName(aLocalName); return; } aLocalName.Truncate(); return; } nsCOMPtr prefix, localName; PRInt32 namespaceID; aNode.mContent->GetAttrNameAt(aNode.mIndex, &namespaceID, getter_AddRefs(localName), getter_AddRefs(prefix)); localName->ToString(aLocalName); // Check for html if (aNode.mContent->GetNodeInfo()->NamespaceEquals(kNameSpaceID_None) && aNode.mContent->IsContentOfType(nsIContent::eHTML)) { ToUpperCase(aLocalName); } } /* static */ void txXPathNodeUtils::getNodeName(const txXPathNode& aNode, nsAString& aName) { if (aNode.isDocument()) { aName.Truncate(); return; } if (aNode.isContent()) { nsINodeInfo* nodeInfo = aNode.mContent->GetNodeInfo(); if (nodeInfo) { nodeInfo->GetQualifiedName(aName); // Check for html if (nodeInfo->NamespaceEquals(kNameSpaceID_None) && aNode.mContent->IsContentOfType(nsIContent::eHTML)) { ToUpperCase(aName); } return; } if (aNode.mContent->IsContentOfType(nsIContent::ePROCESSING_INSTRUCTION)) { // PIs don't have a nodeinfo but do have a name nsCOMPtr node = do_QueryInterface(aNode.mContent); node->GetNodeName(aName); return; } aName.Truncate(); return; } nsCOMPtr name, prefix; PRInt32 namespaceID; aNode.mContent->GetAttrNameAt(aNode.mIndex, &namespaceID, getter_AddRefs(name), getter_AddRefs(prefix)); if (prefix) { prefix->ToString(aName); aName.Append(PRUnichar(':')); } else { aName.Truncate(); } const char* attrName; name->GetUTF8String(&attrName); AppendUTF8toUTF16(attrName, aName); // Check for html if (aNode.mContent->GetNodeInfo()->NamespaceEquals(kNameSpaceID_None) && aNode.mContent->IsContentOfType(nsIContent::eHTML)) { ToUpperCase(aName); } } /* static */ PRInt32 txXPathNodeUtils::getNamespaceID(const txXPathNode& aNode) { if (aNode.isDocument()) { return kNameSpaceID_None; } if (aNode.isContent()) { nsINodeInfo* ni = aNode.mContent->GetNodeInfo(); return ni ? ni->NamespaceID() : kNameSpaceID_None; } nsCOMPtr name, prefix; PRInt32 namespaceID; aNode.mContent->GetAttrNameAt(aNode.mIndex, &namespaceID, getter_AddRefs(name), getter_AddRefs(prefix)); return namespaceID; } /* static */ void txXPathNodeUtils::getNamespaceURI(const txXPathNode& aNode, nsAString& aURI) { gTxNameSpaceManager->GetNameSpaceURI(getNamespaceID(aNode), aURI); } /* static */ PRUint16 txXPathNodeUtils::getNodeType(const txXPathNode& aNode) { if (aNode.isDocument()) { return txXPathNodeType::DOCUMENT_NODE; } if (aNode.isContent()) { PRUint16 type; nsCOMPtr node = do_QueryInterface(aNode.mContent); node->GetNodeType(&type); return type; } return txXPathNodeType::ATTRIBUTE_NODE; } static void appendTextContent(nsIContent* aElement, nsAString& aResult) { nsIContent* content = aElement->GetChildAt(0); PRUint32 i = 0; while (content) { if (content->IsContentOfType(nsIContent::eELEMENT)) { appendTextContent(content, aResult); } else if (content->IsContentOfType(nsIContent::eTEXT)) { nsCOMPtr textContent = do_QueryInterface(content); textContent->AppendTextTo(aResult); } content = aElement->GetChildAt(++i); } } /* static */ void txXPathNodeUtils::appendNodeValue(const txXPathNode& aNode, nsAString& aResult) { if (aNode.isAttribute()) { nsCOMPtr name, prefix; PRInt32 namespaceID; aNode.mContent->GetAttrNameAt(aNode.mIndex, &namespaceID, getter_AddRefs(name), getter_AddRefs(prefix)); nsAutoString result; aNode.mContent->GetAttr(namespaceID, name, result); aResult.Append(result); return; } if (aNode.isDocument()) { nsIContent* content = aNode.mDocument->GetRootContent(); if (content) { appendTextContent(content, aResult); } return; } if (aNode.mContent->IsContentOfType(nsIContent::eELEMENT)) { appendTextContent(aNode.mContent, aResult); return; } if (aNode.mContent->IsContentOfType(nsIContent::ePROCESSING_INSTRUCTION)) { nsCOMPtr node = do_QueryInterface(aNode.mContent); nsAutoString result; node->GetNodeValue(result); aResult.Append(result); return; } nsCOMPtr textContent = do_QueryInterface(aNode.mContent); if (!textContent) { NS_ERROR("Unexpected nodetype."); return; } textContent->AppendTextTo(aResult); } /* static */ PRBool txXPathNodeUtils::isWhitespace(const txXPathNode& aNode) { NS_ASSERTION(aNode.isContent(), "Wrong type!"); nsCOMPtr textCont = do_QueryInterface(aNode.mContent); if (!textCont) { return PR_TRUE; } return textCont->IsOnlyWhitespace(); } /* static */ txXPathNode* txXPathNodeUtils::getDocument(const txXPathNode& aNode) { if (aNode.isDocument()) { return new txXPathNode(aNode); } nsIDocument* document = aNode.mContent->GetDocument(); return document ? new txXPathNode(document) : nsnull; } /* static */ txXPathNode* txXPathNodeUtils::getOwnerDocument(const txXPathNode& aNode) { if (aNode.isDocument()) { return new txXPathNode(aNode); } nsIDocument* document = aNode.mContent->GetDocument(); if (!document) { nsINodeInfo* ni = aNode.mContent->GetNodeInfo(); if (ni) { document = ni->GetDocument(); } } return document ? new txXPathNode(document) : nsnull; } #ifndef HAVE_64BIT_OS #define kFmtSize 13 #define kFmtSizeAttr 24 const char gPrintfFmt[] = "id0x%08p"; const char gPrintfFmtAttr[] = "id0x%08p-%010i"; #else #define kFmtSize 21 #define kFmtSizeAttr 32 const char gPrintfFmt[] = "id0x%016p"; const char gPrintfFmtAttr[] = "id0x%016p-%010i"; #endif /* static */ nsresult txXPathNodeUtils::getXSLTId(const txXPathNode& aNode, nsAString& aResult) { if (aNode.isDocument()) { CopyASCIItoUTF16(nsPrintfCString(kFmtSize, gPrintfFmt, aNode.mDocument), aResult); return NS_OK; } if (aNode.isContent()) { CopyASCIItoUTF16(nsPrintfCString(kFmtSize, gPrintfFmt, aNode.mContent), aResult); return NS_OK; } CopyASCIItoUTF16(nsPrintfCString(kFmtSizeAttr, gPrintfFmtAttr, aNode.mContent, aNode.mIndex), aResult); return NS_OK; } /* static */ void txXPathNodeUtils::getBaseURI(const txXPathNode& aNode, nsAString& aURI) { nsCOMPtr node; if (aNode.isDocument()) { node = do_QueryInterface(aNode.mDocument); } else { node = do_QueryInterface(aNode.mContent); } if (node) { node->GetBaseURI(aURI); } else { aURI.Truncate(); } } /* static */ PRIntn txXPathNodeUtils::comparePosition(const txXPathNode& aNode, const txXPathNode& aOtherNode) { // First check for equal nodes or attribute-nodes on the same element. if (aNode.mContent == aOtherNode.mContent) { if (aNode.mIndex == aOtherNode.mIndex) { return 0; } if (aNode.isContent() || (!aOtherNode.isContent() && aNode.mIndex < aOtherNode.mIndex)) { return -1; } return 1; } // Get document for both nodes. nsIDocument* document = aNode.isDocument() ? aNode.mDocument : aNode.mContent->GetDocument(); nsIDocument* otherDocument = aOtherNode.isDocument() ? aOtherNode.mDocument : aOtherNode.mContent->GetDocument(); // If the nodes have different ownerdocuments, compare the document // pointers. if (document && otherDocument && document != otherDocument) { return document > otherDocument ? 1 : -1; } // Every node comes after its ownerdocument. if (aNode.isDocument()) { return -1; } if (aOtherNode.isDocument()) { return 1; } // Get parents up the tree. nsAutoVoidArray parents, otherParents; nsIContent* content = aNode.mContent; nsIContent* otherContent = aOtherNode.mContent; nsIContent* parent, *otherParent; PRInt32 index, otherIndex; while (content && otherContent) { parent = content->GetParent(); otherParent = otherContent->GetParent(); // Hopefully this is a common case. if (parent == otherParent) { if (parent) { index = (PRUint32)parent->IndexOf(content); otherIndex = (PRUint32)parent->IndexOf(otherContent); } else { if (!document) { if (!otherDocument) { // Neither aNode nor aOtherNode are not in a document, // compare their top ancestors. return content > otherContent ? 1 : -1; } // aNode is not in the tree, compare its top ancestor with // aOtherNode's document. return (void*)content > (void*)otherDocument ? 1 : -1; } else if (!otherDocument) { // aOtherNode is not in a document, compare its top // ancestor with aNode's document. return (void*)document > (void*)otherContent ? 1 : -1; } // Both nodes are in the same document. index = (PRUint32)document->IndexOf(content); otherIndex = (PRUint32)document->IndexOf(otherContent); } return index < otherIndex ? -1 : 1; } parents.AppendElement(content); otherParents.AppendElement(otherContent); content = parent; otherContent = otherParent; } while (content) { parents.AppendElement(content); content = content->GetParent(); } while (otherContent) { otherParents.AppendElement(otherContent); otherContent = otherContent->GetParent(); } if (!document) { if (!otherDocument) { // Neither aNode nor aOtherNode are not in a document, compare // their top ancestors. return parents.ElementAt(parents.Count() - 1) > otherParents.ElementAt(otherParents.Count() - 1) ? 1 : -1; } // aNode is not in the tree, compare its top ancestor with aOtherNode's // document. return parents.ElementAt(parents.Count() - 1) > otherDocument ? 1 : -1; } else if (!otherDocument) { // aOtherNode is not in a document, compare its top // ancestor with aNode's document. return document > otherParents.ElementAt(otherParents.Count() - 1) ? 1 : -1; } // Walk back down along the parent-chains until we find where they split. PRInt32 total = parents.Count() - 1; PRInt32 otherTotal = otherParents.Count() - 1; NS_ASSERTION(total != otherTotal, "Can't have same number of parents"); PRInt32 lastIndex = PR_MIN(total, otherTotal); PRInt32 i; parent = nsnull; for (i = 0; i <= lastIndex; ++i) { content = NS_STATIC_CAST(nsIContent*, parents.ElementAt(total - i)); otherContent = NS_STATIC_CAST(nsIContent*, otherParents.ElementAt(otherTotal - i)); if (content != otherContent) { if (parent) { index = (PRUint32)parent->IndexOf(content); otherIndex = (PRUint32)parent->IndexOf(otherContent); } else { index = (PRUint32)document->IndexOf(content); otherIndex = (PRUint32)document->IndexOf(otherContent); } NS_ASSERTION(index != otherIndex && index >= 0 && otherIndex >= 0, "invalid index in compareTreePosition"); return index < otherIndex ? -1 : 1; } parent = content; } // 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(nsIDOMNode* aNode) { PRUint16 nodeType; aNode->GetNodeType(&nodeType); if (nodeType == nsIDOMNode::ATTRIBUTE_NODE) { nsCOMPtr attr = do_QueryInterface(aNode); NS_ASSERTION(attr, "doesn't implement nsIAttribute"); nsINodeInfo *nodeInfo = attr->NodeInfo(); nsIContent *parent = attr->GetContent(); if (!parent) { return nsnull; } nsCOMPtr attName, attPrefix; PRInt32 attNS; PRUint32 i, total = parent->GetAttrCount(); for (i = 0; i < total; ++i) { parent->GetAttrNameAt(i, &attNS, getter_AddRefs(attName), getter_AddRefs(attPrefix)); if (nodeInfo->Equals(attName, attNS)) { return new txXPathNode(parent, i); } } NS_ERROR("Couldn't find the attribute in its parent!"); return nsnull; } if (nodeType == nsIDOMNode::DOCUMENT_NODE) { nsCOMPtr document = do_QueryInterface(aNode); return new txXPathNode(document); } nsCOMPtr content = do_QueryInterface(aNode); return new txXPathNode(content); } /* static */ txXPathNode* txXPathNativeNode::createXPathNode(nsIDOMDocument* aDocument) { nsCOMPtr document = do_QueryInterface(aDocument); return new txXPathNode(document); } /* static */ nsresult txXPathNativeNode::getNode(const txXPathNode& aNode, nsIDOMNode** aResult) { if (aNode.isDocument()) { return CallQueryInterface(aNode.mDocument, aResult); } if (aNode.isContent()) { return CallQueryInterface(aNode.mContent, aResult); } PRInt32 namespaceID; nsCOMPtr name, prefix; aNode.mContent->GetAttrNameAt(aNode.mIndex, &namespaceID, getter_AddRefs(name), getter_AddRefs(prefix)); nsAutoString namespaceURI, localname; gTxNameSpaceManager->GetNameSpaceURI(namespaceID, namespaceURI); name->ToString(localname); nsCOMPtr element = do_QueryInterface(aNode.mContent); nsCOMPtr attr; element->GetAttributeNodeNS(namespaceURI, localname, getter_AddRefs(attr)); return CallQueryInterface(attr, aResult); } /* static */ nsIContent* txXPathNativeNode::getContent(const txXPathNode& aNode) { NS_ASSERTION(aNode.isContent(), "Only call getContent on nsIContent wrappers!"); return aNode.mContent; } /* static */ nsIDocument* txXPathNativeNode::getDocument(const txXPathNode& aNode) { NS_ASSERTION(aNode.isDocument(), "Only call getDocument on nsIDocument wrappers!"); return aNode.mDocument; }