summaryrefslogtreecommitdiffstats
path: root/dom/xslt/xpath
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/xslt/xpath
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/xslt/xpath')
-rw-r--r--dom/xslt/xpath/XPathEvaluator.cpp187
-rw-r--r--dom/xslt/xpath/XPathEvaluator.h65
-rw-r--r--dom/xslt/xpath/XPathExpression.cpp210
-rw-r--r--dom/xslt/xpath/XPathExpression.h68
-rw-r--r--dom/xslt/xpath/XPathResult.cpp264
-rw-r--r--dom/xslt/xpath/XPathResult.h156
-rw-r--r--dom/xslt/xpath/moz.build58
-rw-r--r--dom/xslt/xpath/txBooleanExpr.cpp78
-rw-r--r--dom/xslt/xpath/txBooleanResult.cpp48
-rw-r--r--dom/xslt/xpath/txCoreFunctionCall.cpp665
-rw-r--r--dom/xslt/xpath/txErrorExpr.cpp35
-rw-r--r--dom/xslt/xpath/txExpr.cpp26
-rw-r--r--dom/xslt/xpath/txExpr.h847
-rw-r--r--dom/xslt/xpath/txExprLexer.cpp333
-rw-r--r--dom/xslt/xpath/txExprLexer.h200
-rw-r--r--dom/xslt/xpath/txExprParser.cpp855
-rw-r--r--dom/xslt/xpath/txExprParser.h95
-rw-r--r--dom/xslt/xpath/txExprResult.h118
-rw-r--r--dom/xslt/xpath/txFilterExpr.cpp88
-rw-r--r--dom/xslt/xpath/txForwardContext.cpp50
-rw-r--r--dom/xslt/xpath/txForwardContext.h28
-rw-r--r--dom/xslt/xpath/txFunctionCall.cpp110
-rw-r--r--dom/xslt/xpath/txIXPathContext.h132
-rw-r--r--dom/xslt/xpath/txLiteralExpr.cpp74
-rw-r--r--dom/xslt/xpath/txLocationStep.cpp308
-rw-r--r--dom/xslt/xpath/txMozillaXPathTreeWalker.cpp674
-rw-r--r--dom/xslt/xpath/txNameTest.cpp92
-rw-r--r--dom/xslt/xpath/txNamedAttributeStep.cpp53
-rw-r--r--dom/xslt/xpath/txNodeSet.cpp567
-rw-r--r--dom/xslt/xpath/txNodeSet.h199
-rw-r--r--dom/xslt/xpath/txNodeSetContext.cpp51
-rw-r--r--dom/xslt/xpath/txNodeSetContext.h36
-rw-r--r--dom/xslt/xpath/txNodeTypeTest.cpp91
-rw-r--r--dom/xslt/xpath/txNumberExpr.cpp108
-rw-r--r--dom/xslt/xpath/txNumberResult.cpp48
-rw-r--r--dom/xslt/xpath/txPathExpr.cpp229
-rw-r--r--dom/xslt/xpath/txPredicateList.cpp78
-rw-r--r--dom/xslt/xpath/txPredicatedNodeTest.cpp50
-rw-r--r--dom/xslt/xpath/txRelationalExpr.cpp184
-rw-r--r--dom/xslt/xpath/txResultRecycler.cpp171
-rw-r--r--dom/xslt/xpath/txResultRecycler.h76
-rw-r--r--dom/xslt/xpath/txRootExpr.cpp35
-rw-r--r--dom/xslt/xpath/txSingleNodeContext.h64
-rw-r--r--dom/xslt/xpath/txStringResult.cpp43
-rw-r--r--dom/xslt/xpath/txUnaryExpr.cpp49
-rw-r--r--dom/xslt/xpath/txUnionExpr.cpp85
-rw-r--r--dom/xslt/xpath/txUnionNodeTest.cpp59
-rw-r--r--dom/xslt/xpath/txVariableRefExpr.cpp62
-rw-r--r--dom/xslt/xpath/txXPathNode.h91
-rw-r--r--dom/xslt/xpath/txXPathOptimizer.cpp246
-rw-r--r--dom/xslt/xpath/txXPathOptimizer.h30
-rw-r--r--dom/xslt/xpath/txXPathTreeWalker.h201
52 files changed, 8770 insertions, 0 deletions
diff --git a/dom/xslt/xpath/XPathEvaluator.cpp b/dom/xslt/xpath/XPathEvaluator.cpp
new file mode 100644
index 0000000000..269ea98ff6
--- /dev/null
+++ b/dom/xslt/xpath/XPathEvaluator.cpp
@@ -0,0 +1,187 @@
+/* -*- 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 "mozilla/dom/XPathEvaluator.h"
+
+#include <utility>
+
+#include "XPathResult.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/XPathEvaluatorBinding.h"
+#include "mozilla/dom/XPathExpression.h"
+#include "mozilla/dom/XPathNSResolverBinding.h"
+#include "nsAtom.h"
+#include "nsCOMPtr.h"
+#include "nsContentCID.h"
+#include "nsContentUtils.h"
+#include "nsDOMString.h"
+#include "nsError.h"
+#include "nsNameSpaceManager.h"
+#include "txExpr.h"
+#include "txExprParser.h"
+#include "txIXPathContext.h"
+#include "txURIUtils.h"
+
+namespace mozilla::dom {
+
+// txIParseContext implementation
+class XPathEvaluatorParseContext : public txIParseContext {
+ public:
+ XPathEvaluatorParseContext(XPathNSResolver* aResolver, bool aIsCaseSensitive)
+ : mResolver(aResolver),
+ mResolverNode(nullptr),
+ mLastError(NS_OK),
+ mIsCaseSensitive(aIsCaseSensitive) {}
+ XPathEvaluatorParseContext(nsINode* aResolver, bool aIsCaseSensitive)
+ : mResolver(nullptr),
+ mResolverNode(aResolver),
+ mLastError(NS_OK),
+ mIsCaseSensitive(aIsCaseSensitive) {}
+
+ nsresult getError() { return mLastError; }
+
+ nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override;
+ nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
+ FunctionCall** aFunction) override;
+ bool caseInsensitiveNameTests() override;
+ void SetErrorOffset(uint32_t aOffset) override;
+
+ private:
+ XPathNSResolver* mResolver;
+ nsINode* mResolverNode;
+ nsresult mLastError;
+ bool mIsCaseSensitive;
+};
+
+XPathEvaluator::XPathEvaluator(Document* aDocument) : mDocument(aDocument) {}
+
+XPathEvaluator::~XPathEvaluator() = default;
+
+UniquePtr<XPathExpression> XPathEvaluator::CreateExpression(
+ const nsAString& aExpression, XPathNSResolver* aResolver,
+ ErrorResult& aRv) {
+ nsCOMPtr<Document> doc(mDocument);
+ XPathEvaluatorParseContext pContext(aResolver,
+ !(doc && doc->IsHTMLDocument()));
+ return CreateExpression(aExpression, &pContext, doc, aRv);
+}
+
+UniquePtr<XPathExpression> XPathEvaluator::CreateExpression(
+ const nsAString& aExpression, nsINode* aResolver, ErrorResult& aRv) {
+ nsCOMPtr<Document> doc(mDocument);
+ XPathEvaluatorParseContext pContext(aResolver,
+ !(doc && doc->IsHTMLDocument()));
+ return CreateExpression(aExpression, &pContext, doc, aRv);
+}
+
+UniquePtr<XPathExpression> XPathEvaluator::CreateExpression(
+ const nsAString& aExpression, txIParseContext* aContext,
+ Document* aDocument, ErrorResult& aRv) {
+ if (!mRecycler) {
+ mRecycler = new txResultRecycler;
+ }
+
+ UniquePtr<Expr> expression;
+ aRv = txExprParser::createExpr(PromiseFlatString(aExpression), aContext,
+ getter_Transfers(expression));
+ if (aRv.Failed()) {
+ if (!aRv.ErrorCodeIs(NS_ERROR_DOM_NAMESPACE_ERR)) {
+ aRv.SuppressException();
+ aRv.ThrowSyntaxError("The expression is not a legal expression");
+ }
+
+ return nullptr;
+ }
+
+ return MakeUnique<XPathExpression>(std::move(expression), mRecycler,
+ aDocument);
+}
+
+bool XPathEvaluator::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return dom::XPathEvaluator_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+/* static */
+UniquePtr<XPathEvaluator> XPathEvaluator::Constructor(
+ const GlobalObject& aGlobal) {
+ return MakeUnique<XPathEvaluator>(nullptr);
+}
+
+already_AddRefed<XPathResult> XPathEvaluator::Evaluate(
+ JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
+ XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
+ ErrorResult& rv) {
+ UniquePtr<XPathExpression> expression(
+ CreateExpression(aExpression, aResolver, rv));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return expression->Evaluate(aCx, aContextNode, aType, aResult, rv);
+}
+
+/*
+ * Implementation of txIParseContext private to XPathEvaluator, based on a
+ * XPathNSResolver
+ */
+
+nsresult XPathEvaluatorParseContext::resolveNamespacePrefix(nsAtom* aPrefix,
+ int32_t& aID) {
+ aID = kNameSpaceID_Unknown;
+
+ if (!mResolver && !mResolverNode) {
+ return NS_ERROR_DOM_NAMESPACE_ERR;
+ }
+
+ nsAutoString prefix;
+ if (aPrefix) {
+ aPrefix->ToString(prefix);
+ }
+
+ nsAutoString ns;
+ if (mResolver) {
+ ErrorResult rv;
+ mResolver->LookupNamespaceURI(prefix, ns, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ } else {
+ if (aPrefix == nsGkAtoms::xml) {
+ ns.AssignLiteral("http://www.w3.org/XML/1998/namespace");
+ } else {
+ mResolverNode->LookupNamespaceURI(prefix, ns);
+ }
+ }
+
+ if (DOMStringIsNull(ns)) {
+ return NS_ERROR_DOM_NAMESPACE_ERR;
+ }
+
+ if (ns.IsEmpty()) {
+ aID = kNameSpaceID_None;
+
+ return NS_OK;
+ }
+
+ // get the namespaceID for the URI
+ return nsNameSpaceManager::GetInstance()->RegisterNameSpace(ns, aID);
+}
+
+nsresult XPathEvaluatorParseContext::resolveFunctionCall(nsAtom* aName,
+ int32_t aID,
+ FunctionCall** aFn) {
+ return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
+}
+
+bool XPathEvaluatorParseContext::caseInsensitiveNameTests() {
+ return !mIsCaseSensitive;
+}
+
+void XPathEvaluatorParseContext::SetErrorOffset(uint32_t aOffset) {}
+
+} // namespace mozilla::dom
diff --git a/dom/xslt/xpath/XPathEvaluator.h b/dom/xslt/xpath/XPathEvaluator.h
new file mode 100644
index 0000000000..dbf79373b9
--- /dev/null
+++ b/dom/xslt/xpath/XPathEvaluator.h
@@ -0,0 +1,65 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_XPathEvaluator_h
+#define mozilla_dom_XPathEvaluator_h
+
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Document.h"
+
+class nsINode;
+class txIParseContext;
+class txResultRecycler;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class XPathExpression;
+class XPathNSResolver;
+class XPathResult;
+
+/**
+ * A class for evaluating an XPath expression string
+ */
+class XPathEvaluator final : public NonRefcountedDOMObject {
+ public:
+ explicit XPathEvaluator(Document* aDocument = nullptr);
+ ~XPathEvaluator();
+
+ // WebIDL API
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+ Document* GetParentObject() { return mDocument; }
+ static UniquePtr<XPathEvaluator> Constructor(const GlobalObject& aGlobal);
+ UniquePtr<XPathExpression> CreateExpression(const nsAString& aExpression,
+ XPathNSResolver* aResolver,
+ ErrorResult& rv);
+ UniquePtr<XPathExpression> CreateExpression(const nsAString& aExpression,
+ nsINode* aResolver,
+ ErrorResult& aRv);
+ UniquePtr<XPathExpression> CreateExpression(const nsAString& aExpression,
+ txIParseContext* aContext,
+ Document* aDocument,
+ ErrorResult& aRv);
+ nsINode* CreateNSResolver(nsINode& aNodeResolver) { return &aNodeResolver; }
+ already_AddRefed<XPathResult> Evaluate(
+ JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
+ XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
+ ErrorResult& rv);
+
+ private:
+ WeakPtr<Document> mDocument;
+ RefPtr<txResultRecycler> mRecycler;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_XPathEvaluator_h */
diff --git a/dom/xslt/xpath/XPathExpression.cpp b/dom/xslt/xpath/XPathExpression.cpp
new file mode 100644
index 0000000000..0706763d79
--- /dev/null
+++ b/dom/xslt/xpath/XPathExpression.cpp
@@ -0,0 +1,210 @@
+/* -*- 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 "XPathExpression.h"
+
+#include <utility>
+
+#include "XPathResult.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/XPathResultBinding.h"
+#include "nsError.h"
+#include "nsINode.h"
+#include "txExpr.h"
+#include "txExprResult.h"
+#include "txIXPathContext.h"
+#include "txURIUtils.h"
+#include "txXPathTreeWalker.h"
+
+namespace mozilla::dom {
+
+class EvalContextImpl : public txIEvalContext {
+ public:
+ EvalContextImpl(const txXPathNode& aContextNode, uint32_t aContextPosition,
+ uint32_t aContextSize, txResultRecycler* aRecycler)
+ : mContextNode(aContextNode),
+ mContextPosition(aContextPosition),
+ mContextSize(aContextSize),
+ mLastError(NS_OK),
+ mRecycler(aRecycler) {}
+
+ nsresult getError() { return mLastError; }
+
+ TX_DECL_EVAL_CONTEXT;
+
+ private:
+ const txXPathNode& mContextNode;
+ uint32_t mContextPosition;
+ uint32_t mContextSize;
+ nsresult mLastError;
+ RefPtr<txResultRecycler> mRecycler;
+};
+
+XPathExpression::XPathExpression(UniquePtr<Expr>&& aExpression,
+ txResultRecycler* aRecycler,
+ Document* aDocument)
+ : mExpression(std::move(aExpression)),
+ mRecycler(aRecycler),
+ mDocument(do_GetWeakReference(aDocument)),
+ mCheckDocument(aDocument != nullptr) {}
+
+XPathExpression::~XPathExpression() = default;
+
+already_AddRefed<XPathResult> XPathExpression::EvaluateWithContext(
+ JSContext* aCx, nsINode& aContextNode, uint32_t aContextPosition,
+ uint32_t aContextSize, uint16_t aType, JS::Handle<JSObject*> aInResult,
+ ErrorResult& aRv) {
+ RefPtr<XPathResult> inResult;
+ if (aInResult) {
+ nsresult rv = UNWRAP_OBJECT(XPathResult, aInResult, inResult);
+ if (NS_FAILED(rv) && rv != NS_ERROR_XPC_BAD_CONVERT_JS) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ }
+
+ return EvaluateWithContext(aContextNode, aContextPosition, aContextSize,
+ aType, inResult, aRv);
+}
+
+already_AddRefed<XPathResult> XPathExpression::EvaluateWithContext(
+ nsINode& aContextNode, uint32_t aContextPosition, uint32_t aContextSize,
+ uint16_t aType, XPathResult* aInResult, ErrorResult& aRv) {
+ if (aContextPosition > aContextSize) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ if (aType > XPathResult_Binding::FIRST_ORDERED_NODE_TYPE) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aContextNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ if (mCheckDocument) {
+ nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
+ if (doc != aContextNode.OwnerDoc()) {
+ aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+ return nullptr;
+ }
+ }
+
+ uint16_t nodeType = aContextNode.NodeType();
+
+ if (nodeType == nsINode::TEXT_NODE ||
+ nodeType == nsINode::CDATA_SECTION_NODE) {
+ Text* textNode = aContextNode.GetAsText();
+ MOZ_ASSERT(textNode);
+
+ uint32_t textLength = textNode->Length();
+ if (textLength == 0) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ // XXX Need to get logical XPath text node for CDATASection
+ // and Text nodes.
+ } else if (nodeType != nsINode::DOCUMENT_NODE &&
+ nodeType != nsINode::ELEMENT_NODE &&
+ nodeType != nsINode::ATTRIBUTE_NODE &&
+ nodeType != nsINode::COMMENT_NODE &&
+ nodeType != nsINode::PROCESSING_INSTRUCTION_NODE) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ UniquePtr<txXPathNode> contextNode(
+ txXPathNativeNode::createXPathNode(&aContextNode));
+ if (!contextNode) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ EvalContextImpl eContext(*contextNode, aContextPosition, aContextSize,
+ mRecycler);
+ RefPtr<txAExprResult> exprResult;
+ aRv = mExpression->evaluate(&eContext, getter_AddRefs(exprResult));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ uint16_t resultType = aType;
+ if (aType == XPathResult::ANY_TYPE) {
+ short exprResultType = exprResult->getResultType();
+ switch (exprResultType) {
+ case txAExprResult::NUMBER:
+ resultType = XPathResult::NUMBER_TYPE;
+ break;
+ case txAExprResult::STRING:
+ resultType = XPathResult::STRING_TYPE;
+ break;
+ case txAExprResult::BOOLEAN:
+ resultType = XPathResult::BOOLEAN_TYPE;
+ break;
+ case txAExprResult::NODESET:
+ resultType = XPathResult::UNORDERED_NODE_ITERATOR_TYPE;
+ break;
+ case txAExprResult::RESULT_TREE_FRAGMENT:
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ RefPtr<XPathResult> xpathResult = aInResult;
+ if (!xpathResult) {
+ xpathResult = new XPathResult(&aContextNode);
+ }
+
+ xpathResult->SetExprResult(exprResult, resultType, &aContextNode, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return xpathResult.forget();
+}
+
+/*
+ * Implementation of the txIEvalContext private to XPathExpression
+ * EvalContextImpl bases on only one context node and no variables
+ */
+
+nsresult EvalContextImpl::getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) {
+ aResult = 0;
+ return NS_ERROR_INVALID_ARG;
+}
+
+nsresult EvalContextImpl::isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) {
+ aAllowed = false;
+
+ return NS_OK;
+}
+
+void* EvalContextImpl::getPrivateContext() {
+ // we don't have a private context here.
+ return nullptr;
+}
+
+txResultRecycler* EvalContextImpl::recycler() { return mRecycler; }
+
+void EvalContextImpl::receiveError(const nsAString& aMsg, nsresult aRes) {
+ mLastError = aRes;
+ // forward aMsg to console service?
+}
+
+const txXPathNode& EvalContextImpl::getContextNode() { return mContextNode; }
+
+uint32_t EvalContextImpl::size() { return mContextSize; }
+
+uint32_t EvalContextImpl::position() { return mContextPosition; }
+
+} // namespace mozilla::dom
diff --git a/dom/xslt/xpath/XPathExpression.h b/dom/xslt/xpath/XPathExpression.h
new file mode 100644
index 0000000000..e041649395
--- /dev/null
+++ b/dom/xslt/xpath/XPathExpression.h
@@ -0,0 +1,68 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_XPathExpression_h
+#define mozilla_dom_XPathExpression_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsIWeakReferenceUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "mozilla/dom/XPathExpressionBinding.h"
+#include "mozilla/UniquePtr.h"
+
+class Expr;
+
+class nsINode;
+class txResultRecycler;
+
+namespace mozilla::dom {
+
+class Document;
+class XPathResult;
+
+/**
+ * A class for evaluating an XPath expression string
+ */
+class XPathExpression final : public NonRefcountedDOMObject {
+ public:
+ XPathExpression(mozilla::UniquePtr<Expr>&& aExpression,
+ txResultRecycler* aRecycler, Document* aDocument);
+ ~XPathExpression();
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return XPathExpression_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+ }
+
+ already_AddRefed<XPathResult> Evaluate(JSContext* aCx, nsINode& aContextNode,
+ uint16_t aType,
+ JS::Handle<JSObject*> aInResult,
+ ErrorResult& aRv) {
+ return EvaluateWithContext(aCx, aContextNode, 1, 1, aType, aInResult, aRv);
+ }
+ already_AddRefed<XPathResult> EvaluateWithContext(
+ JSContext* aCx, nsINode& aContextNode, uint32_t aContextPosition,
+ uint32_t aContextSize, uint16_t aType, JS::Handle<JSObject*> aInResult,
+ ErrorResult& aRv);
+ already_AddRefed<XPathResult> Evaluate(nsINode& aContextNode, uint16_t aType,
+ XPathResult* aInResult,
+ ErrorResult& aRv) {
+ return EvaluateWithContext(aContextNode, 1, 1, aType, aInResult, aRv);
+ }
+ already_AddRefed<XPathResult> EvaluateWithContext(
+ nsINode& aContextNode, uint32_t aContextPosition, uint32_t aContextSize,
+ uint16_t aType, XPathResult* aInResult, ErrorResult& aRv);
+
+ private:
+ mozilla::UniquePtr<Expr> mExpression;
+ RefPtr<txResultRecycler> mRecycler;
+ nsWeakPtr mDocument;
+ bool mCheckDocument;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_XPathExpression_h */
diff --git a/dom/xslt/xpath/XPathResult.cpp b/dom/xslt/xpath/XPathResult.cpp
new file mode 100644
index 0000000000..091a83528f
--- /dev/null
+++ b/dom/xslt/xpath/XPathResult.cpp
@@ -0,0 +1,264 @@
+/* -*- 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 "XPathResult.h"
+#include "txExprResult.h"
+#include "txNodeSet.h"
+#include "nsError.h"
+#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/Element.h"
+#include "nsDOMString.h"
+#include "txXPathTreeWalker.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/XPathResultBinding.h"
+
+namespace mozilla::dom {
+
+XPathResult::XPathResult(nsINode* aParent)
+ : mParent(aParent),
+ mDocument(nullptr),
+ mCurrentPos(0),
+ mResultType(ANY_TYPE),
+ mInvalidIteratorState(true),
+ mBooleanResult(false),
+ mNumberResult(0) {}
+
+XPathResult::XPathResult(const XPathResult& aResult)
+ : mParent(aResult.mParent),
+ mResult(aResult.mResult),
+ mResultNodes(aResult.mResultNodes.Clone()),
+ mDocument(aResult.mDocument),
+ mContextNode(aResult.mContextNode),
+ mCurrentPos(0),
+ mResultType(aResult.mResultType),
+ mInvalidIteratorState(aResult.mInvalidIteratorState),
+ mBooleanResult(aResult.mBooleanResult),
+ mNumberResult(aResult.mNumberResult),
+ mStringResult(aResult.mStringResult) {
+ if (mDocument) {
+ mDocument->AddMutationObserver(this);
+ }
+}
+
+XPathResult::~XPathResult() { RemoveObserver(); }
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(XPathResult)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XPathResult)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) { tmp->RemoveObserver(); }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XPathResult)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultNodes)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(XPathResult)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(XPathResult)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPathResult)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* XPathResult::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XPathResult_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void XPathResult::RemoveObserver() {
+ if (mDocument) {
+ mDocument->RemoveMutationObserver(this);
+ }
+}
+
+nsINode* XPathResult::IterateNext(ErrorResult& aRv) {
+ if (!isIterator()) {
+ aRv.ThrowTypeError("Result is not an iterator");
+ return nullptr;
+ }
+
+ if (mDocument) {
+ mDocument->FlushPendingNotifications(FlushType::Content);
+ }
+
+ if (mInvalidIteratorState) {
+ aRv.ThrowInvalidStateError(
+ "The document has been mutated since the result was returned");
+ return nullptr;
+ }
+
+ return mResultNodes.SafeElementAt(mCurrentPos++);
+}
+
+void XPathResult::NodeWillBeDestroyed(nsINode* aNode) {
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ // Set to null to avoid unregistring unnecessarily
+ mDocument = nullptr;
+ Invalidate(aNode->IsContent() ? aNode->AsContent() : nullptr);
+}
+
+void XPathResult::CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo&) {
+ Invalidate(aContent);
+}
+
+void XPathResult::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ Invalidate(aElement);
+}
+
+void XPathResult::ContentAppended(nsIContent* aFirstNewContent) {
+ Invalidate(aFirstNewContent->GetParent());
+}
+
+void XPathResult::ContentInserted(nsIContent* aChild) {
+ Invalidate(aChild->GetParent());
+}
+
+void XPathResult::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ Invalidate(aChild->GetParent());
+}
+
+void XPathResult::SetExprResult(txAExprResult* aExprResult,
+ uint16_t aResultType, nsINode* aContextNode,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aExprResult);
+
+ if ((isSnapshot(aResultType) || isIterator(aResultType) ||
+ isNode(aResultType)) &&
+ aExprResult->getResultType() != txAExprResult::NODESET) {
+ // The DOM spec doesn't really say what should happen when reusing an
+ // XPathResult and an error is thrown. Let's not touch the XPathResult
+ // in that case.
+ aRv.ThrowTypeError("Result type mismatch");
+ return;
+ }
+
+ mResultType = aResultType;
+ mContextNode = do_GetWeakReference(aContextNode);
+
+ if (mDocument) {
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nullptr;
+ }
+
+ mResultNodes.Clear();
+
+ // XXX This will keep the recycler alive, should we clear it?
+ mResult = aExprResult;
+ switch (mResultType) {
+ case BOOLEAN_TYPE: {
+ mBooleanResult = mResult->booleanValue();
+ break;
+ }
+ case NUMBER_TYPE: {
+ mNumberResult = mResult->numberValue();
+ break;
+ }
+ case STRING_TYPE: {
+ mResult->stringValue(mStringResult);
+ break;
+ }
+ default: {
+ MOZ_ASSERT(isNode() || isIterator() || isSnapshot());
+ }
+ }
+
+ if (aExprResult->getResultType() == txAExprResult::NODESET) {
+ txNodeSet* nodeSet = static_cast<txNodeSet*>(aExprResult);
+ int32_t i, count = nodeSet->size();
+ for (i = 0; i < count; ++i) {
+ nsINode* node = txXPathNativeNode::getNode(nodeSet->get(i));
+ mResultNodes.AppendElement(node);
+ }
+
+ if (count > 0) {
+ mResult = nullptr;
+ }
+ }
+
+ if (!isIterator()) {
+ return;
+ }
+
+ mCurrentPos = 0;
+ mInvalidIteratorState = false;
+
+ if (!mResultNodes.IsEmpty()) {
+ // If we support the document() function in DOM-XPath we need to
+ // observe all documents that we have resultnodes in.
+ mDocument = mResultNodes[0]->OwnerDoc();
+ NS_ASSERTION(mDocument, "We need a document!");
+ if (mDocument) {
+ mDocument->AddMutationObserver(this);
+ }
+ }
+}
+
+void XPathResult::Invalidate(const nsIContent* aChangeRoot) {
+ nsCOMPtr<nsINode> contextNode = do_QueryReferent(mContextNode);
+ // If the changes are happening in a different anonymous trees, no
+ // invalidation should happen.
+ if (contextNode && aChangeRoot &&
+ !nsContentUtils::IsInSameAnonymousTree(contextNode, aChangeRoot)) {
+ return;
+ }
+
+ mInvalidIteratorState = true;
+ // Make sure nulling out mDocument is the last thing we do.
+ if (mDocument) {
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nullptr;
+ }
+}
+
+nsresult XPathResult::GetExprResult(txAExprResult** aExprResult) {
+ if (isIterator() && mInvalidIteratorState) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ if (mResult) {
+ NS_ADDREF(*aExprResult = mResult);
+
+ return NS_OK;
+ }
+
+ if (mResultNodes.IsEmpty()) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ RefPtr<txNodeSet> nodeSet = new txNodeSet(nullptr);
+ uint32_t i, count = mResultNodes.Length();
+ for (i = 0; i < count; ++i) {
+ UniquePtr<txXPathNode> node(
+ txXPathNativeNode::createXPathNode(mResultNodes[i]));
+ if (!node) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nodeSet->append(*node);
+ }
+
+ NS_ADDREF(*aExprResult = nodeSet);
+
+ return NS_OK;
+}
+
+already_AddRefed<XPathResult> XPathResult::Clone(ErrorResult& aError) {
+ if (isIterator() && mInvalidIteratorState) {
+ aError = NS_ERROR_DOM_INVALID_STATE_ERR;
+ return nullptr;
+ }
+
+ return do_AddRef(new XPathResult(*this));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xslt/xpath/XPathResult.h b/dom/xslt/xpath/XPathResult.h
new file mode 100644
index 0000000000..e073666a84
--- /dev/null
+++ b/dom/xslt/xpath/XPathResult.h
@@ -0,0 +1,156 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_XPathResult_h
+#define mozilla_dom_XPathResult_h
+
+#include "nsStubMutationObserver.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+#include "nsINode.h"
+
+class txAExprResult;
+
+namespace mozilla::dom {
+
+/**
+ * A class for evaluating an XPath expression string
+ */
+class XPathResult final : public nsStubMutationObserver, public nsWrapperCache {
+ ~XPathResult();
+
+ public:
+ explicit XPathResult(nsINode* aParent);
+ XPathResult(const XPathResult& aResult);
+
+ enum {
+ ANY_TYPE = 0U,
+ NUMBER_TYPE = 1U,
+ STRING_TYPE = 2U,
+ BOOLEAN_TYPE = 3U,
+ UNORDERED_NODE_ITERATOR_TYPE = 4U,
+ ORDERED_NODE_ITERATOR_TYPE = 5U,
+ UNORDERED_NODE_SNAPSHOT_TYPE = 6U,
+ ORDERED_NODE_SNAPSHOT_TYPE = 7U,
+ ANY_UNORDERED_NODE_TYPE = 8U,
+ FIRST_ORDERED_NODE_TYPE = 9U
+ };
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(XPathResult)
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsINode* GetParentObject() const { return mParent; }
+ uint16_t ResultType() const { return mResultType; }
+ double GetNumberValue(ErrorResult& aRv) const {
+ if (mResultType != NUMBER_TYPE) {
+ aRv.ThrowTypeError("Result is not a number");
+ return 0;
+ }
+
+ return mNumberResult;
+ }
+ void GetStringValue(nsAString& aStringValue, ErrorResult& aRv) const {
+ if (mResultType != STRING_TYPE) {
+ aRv.ThrowTypeError("Result is not a string");
+ return;
+ }
+
+ aStringValue = mStringResult;
+ }
+ bool GetBooleanValue(ErrorResult& aRv) const {
+ if (mResultType != BOOLEAN_TYPE) {
+ aRv.ThrowTypeError("Result is not a boolean");
+ return false;
+ }
+
+ return mBooleanResult;
+ }
+ nsINode* GetSingleNodeValue(ErrorResult& aRv) const {
+ if (!isNode()) {
+ aRv.ThrowTypeError("Result is not a node");
+ return nullptr;
+ }
+
+ return mResultNodes.SafeElementAt(0);
+ }
+ bool InvalidIteratorState() const {
+ return isIterator() && mInvalidIteratorState;
+ }
+ uint32_t GetSnapshotLength(ErrorResult& aRv) const {
+ if (!isSnapshot()) {
+ aRv.ThrowTypeError("Result is not a snapshot");
+ return 0;
+ }
+
+ return (uint32_t)mResultNodes.Length();
+ }
+ nsINode* IterateNext(ErrorResult& aRv);
+ nsINode* SnapshotItem(uint32_t aIndex, ErrorResult& aRv) const {
+ if (!isSnapshot()) {
+ aRv.ThrowTypeError("Result is not a snapshot");
+ return nullptr;
+ }
+
+ return mResultNodes.SafeElementAt(aIndex);
+ }
+
+ // nsIMutationObserver interface
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ void SetExprResult(txAExprResult* aExprResult, uint16_t aResultType,
+ nsINode* aContextNode, ErrorResult& aRv);
+ nsresult GetExprResult(txAExprResult** aExprResult);
+ already_AddRefed<XPathResult> Clone(ErrorResult& aError);
+ void RemoveObserver();
+
+ private:
+ static bool isSnapshot(uint16_t aResultType) {
+ return aResultType == UNORDERED_NODE_SNAPSHOT_TYPE ||
+ aResultType == ORDERED_NODE_SNAPSHOT_TYPE;
+ }
+ static bool isIterator(uint16_t aResultType) {
+ return aResultType == UNORDERED_NODE_ITERATOR_TYPE ||
+ aResultType == ORDERED_NODE_ITERATOR_TYPE;
+ }
+ static bool isNode(uint16_t aResultType) {
+ return aResultType == FIRST_ORDERED_NODE_TYPE ||
+ aResultType == ANY_UNORDERED_NODE_TYPE;
+ }
+ bool isSnapshot() const { return isSnapshot(mResultType); }
+ bool isIterator() const { return isIterator(mResultType); }
+ bool isNode() const { return isNode(mResultType); }
+
+ void Invalidate(const nsIContent* aChangeRoot);
+
+ nsCOMPtr<nsINode> mParent;
+ RefPtr<txAExprResult> mResult;
+ nsTArray<nsCOMPtr<nsINode>> mResultNodes;
+ RefPtr<Document> mDocument;
+ nsWeakPtr mContextNode;
+ uint32_t mCurrentPos;
+ uint16_t mResultType;
+ bool mInvalidIteratorState;
+ bool mBooleanResult;
+ double mNumberResult;
+ nsString mStringResult;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_XPathResult_h */
diff --git a/dom/xslt/xpath/moz.build b/dom/xslt/xpath/moz.build
new file mode 100644
index 0000000000..9cf562c0af
--- /dev/null
+++ b/dom/xslt/xpath/moz.build
@@ -0,0 +1,58 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "txIXPathContext.h",
+ "XPathEvaluator.h",
+ "XPathExpression.h",
+ "XPathResult.h",
+]
+
+UNIFIED_SOURCES += [
+ "txBooleanExpr.cpp",
+ "txBooleanResult.cpp",
+ "txCoreFunctionCall.cpp",
+ "txErrorExpr.cpp",
+ "txExpr.cpp",
+ "txExprLexer.cpp",
+ "txExprParser.cpp",
+ "txFilterExpr.cpp",
+ "txForwardContext.cpp",
+ "txFunctionCall.cpp",
+ "txLiteralExpr.cpp",
+ "txLocationStep.cpp",
+ "txMozillaXPathTreeWalker.cpp",
+ "txNamedAttributeStep.cpp",
+ "txNameTest.cpp",
+ "txNodeSet.cpp",
+ "txNodeSetContext.cpp",
+ "txNodeTypeTest.cpp",
+ "txNumberExpr.cpp",
+ "txNumberResult.cpp",
+ "txPathExpr.cpp",
+ "txPredicatedNodeTest.cpp",
+ "txPredicateList.cpp",
+ "txRelationalExpr.cpp",
+ "txResultRecycler.cpp",
+ "txRootExpr.cpp",
+ "txStringResult.cpp",
+ "txUnaryExpr.cpp",
+ "txUnionExpr.cpp",
+ "txUnionNodeTest.cpp",
+ "txVariableRefExpr.cpp",
+ "txXPathOptimizer.cpp",
+ "XPathEvaluator.cpp",
+ "XPathExpression.cpp",
+ "XPathResult.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "../base",
+ "../xml",
+ "../xslt",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/xslt/xpath/txBooleanExpr.cpp b/dom/xslt/xpath/txBooleanExpr.cpp
new file mode 100644
index 0000000000..0f4366ff76
--- /dev/null
+++ b/dom/xslt/xpath/txBooleanExpr.cpp
@@ -0,0 +1,78 @@
+/* -*- 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/. */
+
+/**
+ * Represents a BooleanExpr, a binary expression that
+ * performs a boolean operation between its lvalue and rvalue.
+ **/
+
+#include "txExpr.h"
+#include "txIXPathContext.h"
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult BooleanExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ bool lval;
+ nsresult rv = leftExpr->evaluateToBool(aContext, lval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check for early decision
+ if (op == OR && lval) {
+ aContext->recycler()->getBoolResult(true, aResult);
+
+ return NS_OK;
+ }
+ if (op == AND && !lval) {
+ aContext->recycler()->getBoolResult(false, aResult);
+
+ return NS_OK;
+ }
+
+ bool rval;
+ rv = rightExpr->evaluateToBool(aContext, rval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // just use rval, since we already checked lval
+ aContext->recycler()->getBoolResult(rval, aResult);
+
+ return NS_OK;
+} //-- evaluate
+
+TX_IMPL_EXPR_STUBS_2(BooleanExpr, BOOLEAN_RESULT, leftExpr, rightExpr)
+
+bool BooleanExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return leftExpr->isSensitiveTo(aContext) ||
+ rightExpr->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void BooleanExpr::toString(nsAString& str) {
+ if (leftExpr)
+ leftExpr->toString(str);
+ else
+ str.AppendLiteral("null");
+
+ switch (op) {
+ case OR:
+ str.AppendLiteral(" or ");
+ break;
+ default:
+ str.AppendLiteral(" and ");
+ break;
+ }
+ if (rightExpr)
+ rightExpr->toString(str);
+ else
+ str.AppendLiteral("null");
+}
+#endif
diff --git a/dom/xslt/xpath/txBooleanResult.cpp b/dom/xslt/xpath/txBooleanResult.cpp
new file mode 100644
index 0000000000..1d69a8c347
--- /dev/null
+++ b/dom/xslt/xpath/txBooleanResult.cpp
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+
+/*
+ * Boolean Expression result
+ */
+
+#include "txExprResult.h"
+
+/**
+ * Creates a new BooleanResult with the value of the given bool parameter
+ * @param boolean the bool to use for initialization of this BooleanResult's
+ * value
+ **/
+BooleanResult::BooleanResult(bool boolean) : txAExprResult(nullptr) {
+ this->value = boolean;
+} //-- BooleanResult
+
+/*
+ * Virtual Methods from ExprResult
+ */
+
+short BooleanResult::getResultType() {
+ return txAExprResult::BOOLEAN;
+} //-- getResultType
+
+void BooleanResult::stringValue(nsString& aResult) {
+ if (value) {
+ aResult.AppendLiteral("true");
+ } else {
+ aResult.AppendLiteral("false");
+ }
+}
+
+const nsString* BooleanResult::stringValuePointer() {
+ // In theory we could set strings containing "true" and "false" somewhere,
+ // but most stylesheets never get the stringvalue of a bool so that won't
+ // really buy us anything.
+ return nullptr;
+}
+
+bool BooleanResult::booleanValue() { return this->value; } //-- toBoolean
+
+double BooleanResult::numberValue() {
+ return (value) ? 1.0 : 0.0;
+} //-- toNumber
diff --git a/dom/xslt/xpath/txCoreFunctionCall.cpp b/dom/xslt/xpath/txCoreFunctionCall.cpp
new file mode 100644
index 0000000000..6e2285ed18
--- /dev/null
+++ b/dom/xslt/xpath/txCoreFunctionCall.cpp
@@ -0,0 +1,665 @@
+/* -*- 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 "mozilla/ArrayUtils.h"
+#include "mozilla/FloatingPoint.h"
+
+#include "txExpr.h"
+#include "txNodeSet.h"
+#include "nsGkAtoms.h"
+#include "txIXPathContext.h"
+#include "nsWhitespaceTokenizer.h"
+#include "txXPathTreeWalker.h"
+#include <math.h>
+#include "txStringUtils.h"
+#include "txXMLUtils.h"
+
+using namespace mozilla;
+
+struct txCoreFunctionDescriptor {
+ const int8_t mMinParams;
+ const int8_t mMaxParams;
+ const Expr::ResultType mReturnType;
+ const nsStaticAtom* const mName;
+};
+
+// This must be ordered in the same order as txCoreFunctionCall::eType.
+// If you change one, change the other.
+static const txCoreFunctionDescriptor descriptTable[] = {
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::count}, // COUNT
+ {1, 1, Expr::NODESET_RESULT, nsGkAtoms::id}, // ID
+ {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::last}, // LAST
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::localName}, // LOCAL_NAME
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::namespaceUri}, // NAMESPACE_URI
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::name}, // NAME
+ {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::position}, // POSITION
+
+ {2, -1, Expr::STRING_RESULT, nsGkAtoms::concat}, // CONCAT
+ {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::contains}, // CONTAINS
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::normalizeSpace}, // NORMALIZE_SPACE
+ {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::startsWith}, // STARTS_WITH
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::string}, // STRING
+ {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::stringLength}, // STRING_LENGTH
+ {2, 3, Expr::STRING_RESULT, nsGkAtoms::substring}, // SUBSTRING
+ {2, 2, Expr::STRING_RESULT, nsGkAtoms::substringAfter}, // SUBSTRING_AFTER
+ {2, 2, Expr::STRING_RESULT,
+ nsGkAtoms::substringBefore}, // SUBSTRING_BEFORE
+ {3, 3, Expr::STRING_RESULT, nsGkAtoms::translate}, // TRANSLATE
+
+ {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::number}, // NUMBER
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::round}, // ROUND
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::floor}, // FLOOR
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::ceiling}, // CEILING
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::sum}, // SUM
+
+ {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::boolean}, // BOOLEAN
+ {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_false}, // _FALSE
+ {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::lang}, // LANG
+ {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::_not}, // _NOT
+ {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_true} // _TRUE
+};
+
+/*
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ */
+nsresult txCoreFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ if (!requireParams(descriptTable[mType].mMinParams,
+ descriptTable[mType].mMaxParams, aContext)) {
+ return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+ }
+
+ nsresult rv = NS_OK;
+ switch (mType) {
+ case COUNT: {
+ RefPtr<txNodeSet> nodes;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aContext->recycler()->getNumberResult(nodes->size(), aResult);
+ }
+ case ID: {
+ RefPtr<txAExprResult> exprResult;
+ rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txNodeSet> resultSet;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathTreeWalker walker(aContext->getContextNode());
+
+ if (exprResult->getResultType() == txAExprResult::NODESET) {
+ txNodeSet* nodes =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult));
+ int32_t i;
+ for (i = 0; i < nodes->size(); ++i) {
+ nsAutoString idList;
+ txXPathNodeUtils::appendNodeValue(nodes->get(i), idList);
+ nsWhitespaceTokenizer tokenizer(idList);
+ while (tokenizer.hasMoreTokens()) {
+ if (walker.moveToElementById(tokenizer.nextToken())) {
+ resultSet->add(walker.getCurrentPosition());
+ }
+ }
+ }
+ } else {
+ nsAutoString idList;
+ exprResult->stringValue(idList);
+ nsWhitespaceTokenizer tokenizer(idList);
+ while (tokenizer.hasMoreTokens()) {
+ if (walker.moveToElementById(tokenizer.nextToken())) {
+ resultSet->add(walker.getCurrentPosition());
+ }
+ }
+ }
+
+ *aResult = resultSet;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+ }
+ case LAST: {
+ return aContext->recycler()->getNumberResult(aContext->size(), aResult);
+ }
+ case LOCAL_NAME:
+ case NAME:
+ case NAMESPACE_URI: {
+ // Check for optional arg
+ RefPtr<txNodeSet> nodes;
+ if (!mParams.IsEmpty()) {
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nodes->isEmpty()) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+ }
+
+ const txXPathNode& node =
+ nodes ? nodes->get(0) : aContext->getContextNode();
+ switch (mType) {
+ case LOCAL_NAME: {
+ StringResult* strRes = nullptr;
+ rv = aContext->recycler()->getStringResult(&strRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = strRes;
+ txXPathNodeUtils::getLocalName(node, strRes->mValue);
+
+ return NS_OK;
+ }
+ case NAMESPACE_URI: {
+ StringResult* strRes = nullptr;
+ rv = aContext->recycler()->getStringResult(&strRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = strRes;
+ txXPathNodeUtils::getNamespaceURI(node, strRes->mValue);
+
+ return NS_OK;
+ }
+ case NAME: {
+ // XXX Namespace: namespaces have a name
+ if (txXPathNodeUtils::isAttribute(node) ||
+ txXPathNodeUtils::isElement(node) ||
+ txXPathNodeUtils::isProcessingInstruction(node)) {
+ StringResult* strRes = nullptr;
+ rv = aContext->recycler()->getStringResult(&strRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = strRes;
+ txXPathNodeUtils::getNodeName(node, strRes->mValue);
+ } else {
+ aContext->recycler()->getEmptyStringResult(aResult);
+ }
+
+ return NS_OK;
+ }
+ default: {
+ MOZ_CRASH("Unexpected mType?!");
+ }
+ }
+ MOZ_CRASH("Inner mType switch should have returned!");
+ }
+ case POSITION: {
+ return aContext->recycler()->getNumberResult(aContext->position(),
+ aResult);
+ }
+
+ // String functions
+
+ case CONCAT: {
+ RefPtr<StringResult> strRes;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i, len = mParams.Length();
+ for (i = 0; i < len; ++i) {
+ rv = mParams[i]->evaluateToString(aContext, strRes->mValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ADDREF(*aResult = strRes);
+
+ return NS_OK;
+ }
+ case CONTAINS: {
+ nsAutoString arg2;
+ rv = mParams[1]->evaluateToString(aContext, arg2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (arg2.IsEmpty()) {
+ aContext->recycler()->getBoolResult(true, aResult);
+ } else {
+ nsAutoString arg1;
+ rv = mParams[0]->evaluateToString(aContext, arg1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContext->recycler()->getBoolResult(FindInReadable(arg2, arg1),
+ aResult);
+ }
+
+ return NS_OK;
+ }
+ case NORMALIZE_SPACE: {
+ nsAutoString resultStr;
+ if (!mParams.IsEmpty()) {
+ rv = mParams[0]->evaluateToString(aContext, resultStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ txXPathNodeUtils::appendNodeValue(aContext->getContextNode(),
+ resultStr);
+ }
+
+ RefPtr<StringResult> strRes;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool addSpace = false;
+ bool first = true;
+ strRes->mValue.SetCapacity(resultStr.Length());
+ char16_t c;
+ uint32_t src;
+ for (src = 0; src < resultStr.Length(); src++) {
+ c = resultStr.CharAt(src);
+ if (XMLUtils::isWhitespace(c)) {
+ addSpace = true;
+ } else {
+ if (addSpace && !first) strRes->mValue.Append(char16_t(' '));
+
+ strRes->mValue.Append(c);
+ addSpace = false;
+ first = false;
+ }
+ }
+ *aResult = strRes;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+ }
+ case STARTS_WITH: {
+ nsAutoString arg2;
+ rv = mParams[1]->evaluateToString(aContext, arg2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool result = false;
+ if (arg2.IsEmpty()) {
+ result = true;
+ } else {
+ nsAutoString arg1;
+ rv = mParams[0]->evaluateToString(aContext, arg1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ result = StringBeginsWith(arg1, arg2);
+ }
+
+ aContext->recycler()->getBoolResult(result, aResult);
+
+ return NS_OK;
+ }
+ case STRING: {
+ RefPtr<StringResult> strRes;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mParams.IsEmpty()) {
+ rv = mParams[0]->evaluateToString(aContext, strRes->mValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ txXPathNodeUtils::appendNodeValue(aContext->getContextNode(),
+ strRes->mValue);
+ }
+
+ NS_ADDREF(*aResult = strRes);
+
+ return NS_OK;
+ }
+ case STRING_LENGTH: {
+ nsAutoString resultStr;
+ if (!mParams.IsEmpty()) {
+ rv = mParams[0]->evaluateToString(aContext, resultStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ txXPathNodeUtils::appendNodeValue(aContext->getContextNode(),
+ resultStr);
+ }
+ rv = aContext->recycler()->getNumberResult(resultStr.Length(), aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+ case SUBSTRING: {
+ nsAutoString src;
+ rv = mParams[0]->evaluateToString(aContext, src);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double start;
+ rv = evaluateToNumber(mParams[1], aContext, &start);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check for NaN or +/-Inf
+ if (std::isnan(start) || std::isinf(start) ||
+ start >= src.Length() + 0.5) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ start = floor(start + 0.5) - 1;
+
+ double end;
+ if (mParams.Length() == 3) {
+ rv = evaluateToNumber(mParams[2], aContext, &end);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ end += start;
+ if (std::isnan(end) || end < 0) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ if (end > src.Length())
+ end = src.Length();
+ else
+ end = floor(end + 0.5);
+ } else {
+ end = src.Length();
+ }
+
+ if (start < 0) start = 0;
+
+ if (start > end) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ return aContext->recycler()->getStringResult(
+ Substring(src, (uint32_t)start, (uint32_t)(end - start)), aResult);
+ }
+ case SUBSTRING_AFTER: {
+ nsAutoString arg1;
+ rv = mParams[0]->evaluateToString(aContext, arg1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString arg2;
+ rv = mParams[1]->evaluateToString(aContext, arg2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (arg2.IsEmpty()) {
+ return aContext->recycler()->getStringResult(arg1, aResult);
+ }
+
+ int32_t idx = arg1.Find(arg2);
+ if (idx == kNotFound) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ const nsAString& result = Substring(arg1, idx + arg2.Length());
+ return aContext->recycler()->getStringResult(result, aResult);
+ }
+ case SUBSTRING_BEFORE: {
+ nsAutoString arg2;
+ rv = mParams[1]->evaluateToString(aContext, arg2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (arg2.IsEmpty()) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ nsAutoString arg1;
+ rv = mParams[0]->evaluateToString(aContext, arg1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t idx = arg1.Find(arg2);
+ if (idx == kNotFound) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ return aContext->recycler()->getStringResult(StringHead(arg1, idx),
+ aResult);
+ }
+ case TRANSLATE: {
+ nsAutoString src;
+ rv = mParams[0]->evaluateToString(aContext, src);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (src.IsEmpty()) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ RefPtr<StringResult> strRes;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ strRes->mValue.SetCapacity(src.Length());
+
+ nsAutoString oldChars, newChars;
+ rv = mParams[1]->evaluateToString(aContext, oldChars);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mParams[2]->evaluateToString(aContext, newChars);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i;
+ int32_t newCharsLength = (int32_t)newChars.Length();
+ for (i = 0; i < src.Length(); i++) {
+ int32_t idx = oldChars.FindChar(src.CharAt(i));
+ if (idx != kNotFound) {
+ if (idx < newCharsLength)
+ strRes->mValue.Append(newChars.CharAt((uint32_t)idx));
+ } else {
+ strRes->mValue.Append(src.CharAt(i));
+ }
+ }
+
+ NS_ADDREF(*aResult = strRes);
+
+ return NS_OK;
+ }
+
+ // Number functions
+
+ case NUMBER: {
+ double res;
+ if (!mParams.IsEmpty()) {
+ rv = evaluateToNumber(mParams[0], aContext, &res);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsAutoString resultStr;
+ txXPathNodeUtils::appendNodeValue(aContext->getContextNode(),
+ resultStr);
+ res = txDouble::toDouble(resultStr);
+ }
+ return aContext->recycler()->getNumberResult(res, aResult);
+ }
+ case ROUND: {
+ double dbl;
+ rv = evaluateToNumber(mParams[0], aContext, &dbl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (std::isfinite(dbl)) {
+ if (mozilla::IsNegative(dbl) && dbl >= -0.5) {
+ dbl *= 0;
+ } else {
+ dbl = floor(dbl + 0.5);
+ }
+ }
+
+ return aContext->recycler()->getNumberResult(dbl, aResult);
+ }
+ case FLOOR: {
+ double dbl;
+ rv = evaluateToNumber(mParams[0], aContext, &dbl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (std::isfinite(dbl) && !mozilla::IsNegativeZero(dbl)) dbl = floor(dbl);
+
+ return aContext->recycler()->getNumberResult(dbl, aResult);
+ }
+ case CEILING: {
+ double dbl;
+ rv = evaluateToNumber(mParams[0], aContext, &dbl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (std::isfinite(dbl)) {
+ if (mozilla::IsNegative(dbl) && dbl > -1)
+ dbl *= 0;
+ else
+ dbl = ceil(dbl);
+ }
+
+ return aContext->recycler()->getNumberResult(dbl, aResult);
+ }
+ case SUM: {
+ RefPtr<txNodeSet> nodes;
+ nsresult rv =
+ evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double res = 0;
+ int32_t i;
+ for (i = 0; i < nodes->size(); ++i) {
+ nsAutoString resultStr;
+ txXPathNodeUtils::appendNodeValue(nodes->get(i), resultStr);
+ res += txDouble::toDouble(resultStr);
+ }
+ return aContext->recycler()->getNumberResult(res, aResult);
+ }
+
+ // Boolean functions
+
+ case BOOLEAN: {
+ bool result;
+ nsresult rv = mParams[0]->evaluateToBool(aContext, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContext->recycler()->getBoolResult(result, aResult);
+
+ return NS_OK;
+ }
+ case _FALSE: {
+ aContext->recycler()->getBoolResult(false, aResult);
+
+ return NS_OK;
+ }
+ case LANG: {
+ txXPathTreeWalker walker(aContext->getContextNode());
+
+ nsAutoString lang;
+ bool found;
+ do {
+ found = walker.getAttr(nsGkAtoms::lang, kNameSpaceID_XML, lang);
+ } while (!found && walker.moveToParent());
+
+ if (!found) {
+ aContext->recycler()->getBoolResult(false, aResult);
+
+ return NS_OK;
+ }
+
+ nsAutoString arg;
+ rv = mParams[0]->evaluateToString(aContext, arg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool result =
+ StringBeginsWith(lang, arg, nsCaseInsensitiveStringComparator) &&
+ (lang.Length() == arg.Length() || lang.CharAt(arg.Length()) == '-');
+
+ aContext->recycler()->getBoolResult(result, aResult);
+
+ return NS_OK;
+ }
+ case _NOT: {
+ bool result;
+ rv = mParams[0]->evaluateToBool(aContext, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContext->recycler()->getBoolResult(!result, aResult);
+
+ return NS_OK;
+ }
+ case _TRUE: {
+ aContext->recycler()->getBoolResult(true, aResult);
+
+ return NS_OK;
+ }
+ }
+
+ aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+}
+
+Expr::ResultType txCoreFunctionCall::getReturnType() {
+ return descriptTable[mType].mReturnType;
+}
+
+bool txCoreFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ switch (mType) {
+ case COUNT:
+ case CONCAT:
+ case CONTAINS:
+ case STARTS_WITH:
+ case SUBSTRING:
+ case SUBSTRING_AFTER:
+ case SUBSTRING_BEFORE:
+ case TRANSLATE:
+ case ROUND:
+ case FLOOR:
+ case CEILING:
+ case SUM:
+ case BOOLEAN:
+ case _NOT:
+ case _FALSE:
+ case _TRUE: {
+ return argsSensitiveTo(aContext);
+ }
+ case ID: {
+ return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext);
+ }
+ case LAST: {
+ return !!(aContext & SIZE_CONTEXT);
+ }
+ case LOCAL_NAME:
+ case NAME:
+ case NAMESPACE_URI:
+ case NORMALIZE_SPACE:
+ case STRING:
+ case STRING_LENGTH:
+ case NUMBER: {
+ if (mParams.IsEmpty()) {
+ return !!(aContext & NODE_CONTEXT);
+ }
+ return argsSensitiveTo(aContext);
+ }
+ case POSITION: {
+ return !!(aContext & POSITION_CONTEXT);
+ }
+ case LANG: {
+ return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext);
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("how'd we get here?");
+ return true;
+}
+
+// static
+bool txCoreFunctionCall::getTypeFromAtom(nsAtom* aName, eType& aType) {
+ uint32_t i;
+ for (i = 0; i < ArrayLength(descriptTable); ++i) {
+ if (aName == descriptTable[i].mName) {
+ aType = static_cast<eType>(i);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void txCoreFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(descriptTable[mType].mName->GetUTF16String());
+}
+#endif
diff --git a/dom/xslt/xpath/txErrorExpr.cpp b/dom/xslt/xpath/txErrorExpr.cpp
new file mode 100644
index 0000000000..806b324259
--- /dev/null
+++ b/dom/xslt/xpath/txErrorExpr.cpp
@@ -0,0 +1,35 @@
+/* -*- 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 "nsError.h"
+#include "txExpr.h"
+#include "nsString.h"
+#include "txIXPathContext.h"
+
+nsresult txErrorExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ nsAutoString err(u"Invalid expression evaluated"_ns);
+#ifdef TX_TO_STRING
+ err.AppendLiteral(": ");
+ toString(err);
+#endif
+ aContext->receiveError(err, NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED);
+
+ return NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED;
+}
+
+TX_IMPL_EXPR_STUBS_0(txErrorExpr, ANY_RESULT)
+
+bool txErrorExpr::isSensitiveTo(ContextSensitivity aContext) {
+ // It doesn't really matter what we return here, but it might
+ // be a good idea to try to keep this as unoptimizable as possible
+ return true;
+}
+
+#ifdef TX_TO_STRING
+void txErrorExpr::toString(nsAString& aStr) { aStr.Append(mStr); }
+#endif
diff --git a/dom/xslt/xpath/txExpr.cpp b/dom/xslt/xpath/txExpr.cpp
new file mode 100644
index 0000000000..d5ea774f62
--- /dev/null
+++ b/dom/xslt/xpath/txExpr.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "txExpr.h"
+
+nsresult Expr::evaluateToBool(txIEvalContext* aContext, bool& aResult) {
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult = exprRes->booleanValue();
+
+ return NS_OK;
+}
+
+nsresult Expr::evaluateToString(txIEvalContext* aContext, nsString& aResult) {
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ exprRes->stringValue(aResult);
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xpath/txExpr.h b/dom/xslt/xpath/txExpr.h
new file mode 100644
index 0000000000..2622a3fe9f
--- /dev/null
+++ b/dom/xslt/xpath/txExpr.h
@@ -0,0 +1,847 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_EXPR_H
+#define TRANSFRMX_EXPR_H
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "txExprResult.h"
+#include "txCore.h"
+#include "nsString.h"
+#include "txOwningArray.h"
+#include "nsAtom.h"
+
+#ifdef DEBUG
+# define TX_TO_STRING
+#endif
+
+/*
+ XPath class definitions.
+ Much of this code was ported from XSL:P.
+*/
+
+class nsAtom;
+class txIMatchContext;
+class txIEvalContext;
+class txNodeSet;
+class txXPathNode;
+class txXPathTreeWalker;
+
+/**
+ * A Base Class for all XSL Expressions
+ **/
+class Expr {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(Expr)
+ MOZ_COUNTED_DTOR_VIRTUAL(Expr)
+
+ /**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+ virtual nsresult evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) = 0;
+
+ /**
+ * Returns the type of this expression.
+ */
+ enum ExprType {
+ LOCATIONSTEP_EXPR,
+ PATH_EXPR,
+ UNION_EXPR,
+ LITERAL_EXPR,
+ OTHER_EXPR
+ };
+ virtual ExprType getType() { return OTHER_EXPR; }
+
+ /**
+ * Returns the type or types of results this Expr return.
+ */
+ using ResultType = uint16_t;
+ enum {
+ NODESET_RESULT = 0x01,
+ BOOLEAN_RESULT = 0x02,
+ NUMBER_RESULT = 0x04,
+ STRING_RESULT = 0x08,
+ RTF_RESULT = 0x10,
+ ANY_RESULT = 0xFFFF
+ };
+ virtual ResultType getReturnType() = 0;
+ bool canReturnType(ResultType aType) {
+ return (getReturnType() & aType) != 0;
+ }
+
+ using ContextSensitivity = uint16_t;
+ enum {
+ NO_CONTEXT = 0x00,
+ NODE_CONTEXT = 0x01,
+ POSITION_CONTEXT = 0x02,
+ SIZE_CONTEXT = 0x04,
+ NODESET_CONTEXT = POSITION_CONTEXT | SIZE_CONTEXT,
+ VARIABLES_CONTEXT = 0x08,
+ PRIVATE_CONTEXT = 0x10,
+ ANY_CONTEXT = 0xFFFF
+ };
+
+ /**
+ * Returns true if this expression is sensitive to *any* of
+ * the requested contexts in aContexts.
+ */
+ virtual bool isSensitiveTo(ContextSensitivity aContexts) = 0;
+
+ /**
+ * Returns sub-expression at given position
+ */
+ virtual Expr* getSubExprAt(uint32_t aPos) = 0;
+
+ /**
+ * Replace sub-expression at given position. Does not delete the old
+ * expression, that is the responsibility of the caller.
+ */
+ virtual void setSubExprAt(uint32_t aPos, Expr* aExpr) = 0;
+
+ virtual nsresult evaluateToBool(txIEvalContext* aContext, bool& aResult);
+
+ virtual nsresult evaluateToString(txIEvalContext* aContext,
+ nsString& aResult);
+
+#ifdef TX_TO_STRING
+ /**
+ * Returns the String representation of this Expr.
+ * @param dest the String to use when creating the String
+ * representation. The String representation will be appended to
+ * any data in the destination String, to allow cascading calls to
+ * other #toString() methods for Expressions.
+ * @return the String representation of this Expr.
+ **/
+ virtual void toString(nsAString& str) = 0;
+#endif
+}; //-- Expr
+
+#ifdef TX_TO_STRING
+# define TX_DECL_TOSTRING void toString(nsAString& aDest) override;
+# define TX_DECL_APPENDNAME void appendName(nsAString& aDest) override;
+#else
+# define TX_DECL_TOSTRING
+# define TX_DECL_APPENDNAME
+#endif
+
+#define TX_DECL_EXPR_BASE \
+ nsresult evaluate(txIEvalContext* aContext, txAExprResult** aResult) \
+ override; \
+ ResultType getReturnType() override; \
+ bool isSensitiveTo(ContextSensitivity aContexts) override;
+
+#define TX_DECL_EXPR \
+ TX_DECL_EXPR_BASE \
+ TX_DECL_TOSTRING \
+ Expr* getSubExprAt(uint32_t aPos) override; \
+ void setSubExprAt(uint32_t aPos, Expr* aExpr) override;
+
+#define TX_DECL_OPTIMIZABLE_EXPR \
+ TX_DECL_EXPR \
+ ExprType getType() override;
+
+#define TX_DECL_FUNCTION \
+ TX_DECL_APPENDNAME \
+ TX_DECL_EXPR_BASE
+
+#define TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr::ResultType _class::getReturnType() { return _ReturnType; }
+
+#define TX_IMPL_EXPR_STUBS_0(_class, _ReturnType) \
+ TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr* _class::getSubExprAt(uint32_t aPos) { return nullptr; } \
+ void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \
+ MOZ_ASSERT_UNREACHABLE("setting bad subexpression index"); \
+ }
+
+#define TX_IMPL_EXPR_STUBS_1(_class, _ReturnType, _Expr1) \
+ TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr* _class::getSubExprAt(uint32_t aPos) { \
+ if (aPos == 0) { \
+ return _Expr1.get(); \
+ } \
+ return nullptr; \
+ } \
+ void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \
+ NS_ASSERTION(aPos < 1, "setting bad subexpression index"); \
+ mozilla::Unused << _Expr1.release(); \
+ _Expr1 = mozilla::WrapUnique(aExpr); \
+ }
+
+#define TX_IMPL_EXPR_STUBS_2(_class, _ReturnType, _Expr1, _Expr2) \
+ TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr* _class::getSubExprAt(uint32_t aPos) { \
+ switch (aPos) { \
+ case 0: \
+ return _Expr1.get(); \
+ case 1: \
+ return _Expr2.get(); \
+ default: \
+ break; \
+ } \
+ return nullptr; \
+ } \
+ void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \
+ NS_ASSERTION(aPos < 2, "setting bad subexpression index"); \
+ if (aPos == 0) { \
+ mozilla::Unused << _Expr1.release(); \
+ _Expr1 = mozilla::WrapUnique(aExpr); \
+ } else { \
+ mozilla::Unused << _Expr2.release(); \
+ _Expr2 = mozilla::WrapUnique(aExpr); \
+ } \
+ }
+
+#define TX_IMPL_EXPR_STUBS_LIST(_class, _ReturnType, _ExprList) \
+ TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr* _class::getSubExprAt(uint32_t aPos) { \
+ return _ExprList.SafeElementAt(aPos); \
+ } \
+ void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \
+ NS_ASSERTION(aPos < _ExprList.Length(), \
+ "setting bad subexpression index"); \
+ _ExprList[aPos] = aExpr; \
+ }
+
+/**
+ * This class represents a FunctionCall as defined by the XPath 1.0
+ * Recommendation.
+ **/
+class FunctionCall : public Expr {
+ public:
+ /**
+ * Adds the given parameter to this FunctionCall's parameter list.
+ * The ownership of the given Expr is passed over to the FunctionCall,
+ * even on failure.
+ * @param aExpr the Expr to add to this FunctionCall's parameter list
+ */
+ void addParam(Expr* aExpr) { mParams.AppendElement(aExpr); }
+
+ /**
+ * Check if the number of parameters falls within a range.
+ *
+ * @param aParamCountMin minimum number of required parameters.
+ * @param aParamCountMax maximum number of parameters. If aParamCountMax
+ * is negative the maximum number is not checked.
+ * @return boolean representing whether the number of parameters falls
+ * within the expected range or not.
+ *
+ * XXX txIEvalContext should be txIParseContest, bug 143291
+ */
+ virtual bool requireParams(int32_t aParamCountMin, int32_t aParamCountMax,
+ txIEvalContext* aContext);
+
+ TX_DECL_TOSTRING
+ Expr* getSubExprAt(uint32_t aPos) override;
+ void setSubExprAt(uint32_t aPos, Expr* aExpr) override;
+
+ protected:
+ txOwningArray<Expr> mParams;
+
+ /*
+ * Evaluates the given Expression and converts its result to a number.
+ */
+ static nsresult evaluateToNumber(Expr* aExpr, txIEvalContext* aContext,
+ double* aResult);
+
+ /*
+ * Evaluates the given Expression and converts its result to a NodeSet.
+ * If the result is not a NodeSet an error is returned.
+ */
+ static nsresult evaluateToNodeSet(Expr* aExpr, txIEvalContext* aContext,
+ txNodeSet** aResult);
+
+ /**
+ * Returns true if any argument is sensitive to the given context.
+ */
+ bool argsSensitiveTo(ContextSensitivity aContexts);
+
+#ifdef TX_TO_STRING
+ /*
+ * Appends the name of the function to `aStr`.
+ */
+ virtual void appendName(nsAString& aStr) = 0;
+#endif
+};
+
+class txCoreFunctionCall : public FunctionCall {
+ public:
+ // This must be ordered in the same order as descriptTable in
+ // txCoreFunctionCall.cpp. If you change one, change the other.
+ enum eType {
+ COUNT = 0, // count()
+ ID, // id()
+ LAST, // last()
+ LOCAL_NAME, // local-name()
+ NAMESPACE_URI, // namespace-uri()
+ NAME, // name()
+ POSITION, // position()
+
+ CONCAT, // concat()
+ CONTAINS, // contains()
+ NORMALIZE_SPACE, // normalize-space()
+ STARTS_WITH, // starts-with()
+ STRING, // string()
+ STRING_LENGTH, // string-length()
+ SUBSTRING, // substring()
+ SUBSTRING_AFTER, // substring-after()
+ SUBSTRING_BEFORE, // substring-before()
+ TRANSLATE, // translate()
+
+ NUMBER, // number()
+ ROUND, // round()
+ FLOOR, // floor()
+ CEILING, // ceiling()
+ SUM, // sum()
+
+ BOOLEAN, // boolean()
+ _FALSE, // false()
+ LANG, // lang()
+ _NOT, // not()
+ _TRUE // true()
+ };
+
+ /*
+ * Creates a txCoreFunctionCall of the given type
+ */
+ explicit txCoreFunctionCall(eType aType) : mType(aType) {}
+
+ TX_DECL_FUNCTION
+
+ static bool getTypeFromAtom(nsAtom* aName, eType& aType);
+
+ private:
+ eType mType;
+};
+
+/*
+ * This class represents a NodeTest as defined by the XPath spec
+ */
+class txNodeTest {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(txNodeTest)
+ MOZ_COUNTED_DTOR_VIRTUAL(txNodeTest)
+
+ /*
+ * Virtual methods
+ * pretty much a txPattern, but not supposed to be used
+ * standalone. The NodeTest node() is different to the
+ * Pattern "node()" (document node isn't matched)
+ */
+ virtual nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext,
+ bool& aMatched) = 0;
+ virtual double getDefaultPriority() = 0;
+
+ /**
+ * Returns the type of this nodetest.
+ */
+ enum NodeTestType { NAME_TEST, NODETYPE_TEST, OTHER_TEST };
+ virtual NodeTestType getType() { return OTHER_TEST; }
+
+ /**
+ * Returns true if this expression is sensitive to *any* of
+ * the requested flags.
+ */
+ virtual bool isSensitiveTo(Expr::ContextSensitivity aContext) = 0;
+
+#ifdef TX_TO_STRING
+ virtual void toString(nsAString& aDest) = 0;
+#endif
+};
+
+#define TX_DECL_NODE_TEST \
+ TX_DECL_TOSTRING \
+ nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext, \
+ bool& aMatched) override; \
+ double getDefaultPriority() override; \
+ bool isSensitiveTo(Expr::ContextSensitivity aContext) override;
+
+/*
+ * This class represents a NameTest as defined by the XPath spec
+ */
+class txNameTest : public txNodeTest {
+ public:
+ /*
+ * Creates a new txNameTest with the given type and the given
+ * principal node type
+ */
+ txNameTest(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID,
+ uint16_t aNodeType);
+
+ NodeTestType getType() override;
+
+ TX_DECL_NODE_TEST
+
+ RefPtr<nsAtom> mPrefix;
+ RefPtr<nsAtom> mLocalName;
+ int32_t mNamespace;
+
+ private:
+ uint16_t mNodeType;
+};
+
+/*
+ * This class represents a NodeType as defined by the XPath spec
+ */
+class txNodeTypeTest : public txNodeTest {
+ public:
+ enum NodeType { COMMENT_TYPE, TEXT_TYPE, PI_TYPE, NODE_TYPE };
+
+ /*
+ * Creates a new txNodeTypeTest of the given type
+ */
+ explicit txNodeTypeTest(NodeType aNodeType) : mNodeType(aNodeType) {}
+
+ /*
+ * Sets the name of the node to match. Only availible for pi nodes
+ */
+ void setNodeName(const nsAString& aName) { mNodeName = NS_Atomize(aName); }
+
+ NodeType getNodeTestType() { return mNodeType; }
+
+ NodeTestType getType() override;
+
+ TX_DECL_NODE_TEST
+
+ private:
+ NodeType mNodeType;
+ RefPtr<nsAtom> mNodeName;
+};
+
+/**
+ * Class representing a nodetest combined with a predicate. May only be used
+ * if the predicate is not sensitive to the context-nodelist.
+ */
+class txPredicatedNodeTest : public txNodeTest {
+ public:
+ txPredicatedNodeTest(txNodeTest* aNodeTest, Expr* aPredicate);
+ TX_DECL_NODE_TEST
+
+ private:
+ mozilla::UniquePtr<txNodeTest> mNodeTest;
+ mozilla::UniquePtr<Expr> mPredicate;
+};
+
+/**
+ * Represents an ordered list of Predicates,
+ * for use with Step and Filter Expressions
+ **/
+class PredicateList {
+ public:
+ /**
+ * Adds the given Expr to the list.
+ * The ownership of the given Expr is passed over the PredicateList,
+ * even on failure.
+ * @param aExpr the Expr to add to the list
+ */
+ void add(Expr* aExpr) {
+ NS_ASSERTION(aExpr, "missing expression");
+ mPredicates.AppendElement(aExpr);
+ }
+
+ nsresult evaluatePredicates(txNodeSet* aNodes, txIMatchContext* aContext);
+
+ /**
+ * Drops the first predicate without deleting it.
+ */
+ void dropFirst() { mPredicates.RemoveElementAt(0); }
+
+ /**
+ * returns true if this predicate list is empty
+ **/
+ bool isEmpty() { return mPredicates.IsEmpty(); }
+
+#ifdef TX_TO_STRING
+ /**
+ * Returns the String representation of this PredicateList.
+ * @param dest the String to use when creating the String
+ * representation. The String representation will be appended to
+ * any data in the destination String, to allow cascading calls to
+ * other #toString() methods for Expressions.
+ * @return the String representation of this PredicateList.
+ **/
+ void toString(nsAString& dest);
+#endif
+
+ protected:
+ bool isSensitiveTo(Expr::ContextSensitivity aContext);
+ Expr* getSubExprAt(uint32_t aPos) { return mPredicates.SafeElementAt(aPos); }
+ void setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ NS_ASSERTION(aPos < mPredicates.Length(),
+ "setting bad subexpression index");
+ mPredicates[aPos] = aExpr;
+ }
+
+ //-- list of predicates
+ txOwningArray<Expr> mPredicates;
+}; //-- PredicateList
+
+class LocationStep : public Expr, public PredicateList {
+ public:
+ enum LocationStepType {
+ ANCESTOR_AXIS = 0,
+ ANCESTOR_OR_SELF_AXIS,
+ ATTRIBUTE_AXIS,
+ CHILD_AXIS,
+ DESCENDANT_AXIS,
+ DESCENDANT_OR_SELF_AXIS,
+ FOLLOWING_AXIS,
+ FOLLOWING_SIBLING_AXIS,
+ NAMESPACE_AXIS,
+ PARENT_AXIS,
+ PRECEDING_AXIS,
+ PRECEDING_SIBLING_AXIS,
+ SELF_AXIS
+ };
+
+ /**
+ * Creates a new LocationStep using the given NodeExpr and Axis Identifier
+ * @param nodeExpr the NodeExpr to use when matching Nodes
+ * @param axisIdentifier the Axis Identifier in which to search for nodes
+ **/
+ LocationStep(txNodeTest* aNodeTest, LocationStepType aAxisIdentifier)
+ : mNodeTest(aNodeTest), mAxisIdentifier(aAxisIdentifier) {}
+
+ TX_DECL_OPTIMIZABLE_EXPR
+
+ txNodeTest* getNodeTest() { return mNodeTest.get(); }
+ void setNodeTest(txNodeTest* aNodeTest) {
+ mozilla::Unused << mNodeTest.release();
+ mNodeTest = mozilla::WrapUnique(aNodeTest);
+ }
+ LocationStepType getAxisIdentifier() { return mAxisIdentifier; }
+ void setAxisIdentifier(LocationStepType aAxisIdentifier) {
+ mAxisIdentifier = aAxisIdentifier;
+ }
+
+ private:
+ /**
+ * Append the current position of aWalker to aNodes if it matches mNodeTest,
+ * using aContext as the context for matching.
+ */
+ nsresult appendIfMatching(const txXPathTreeWalker& aWalker,
+ txIMatchContext* aContext, txNodeSet* aNodes);
+
+ /**
+ * Append the descendants of the current position of aWalker to aNodes if
+ * they match mNodeTest, using aContext as the context for matching.
+ */
+ nsresult appendMatchingDescendants(const txXPathTreeWalker& aWalker,
+ txIMatchContext* aContext,
+ txNodeSet* aNodes);
+
+ /**
+ * Append the descendants of the current position of aWalker to aNodes in
+ * reverse order if they match mNodeTest, using aContext as the context for
+ * matching.
+ */
+ nsresult appendMatchingDescendantsRev(const txXPathTreeWalker& aWalker,
+ txIMatchContext* aContext,
+ txNodeSet* aNodes);
+
+ mozilla::UniquePtr<txNodeTest> mNodeTest;
+ LocationStepType mAxisIdentifier;
+};
+
+class FilterExpr : public Expr, public PredicateList {
+ public:
+ /**
+ * Creates a new FilterExpr using the given Expr
+ * @param expr the Expr to use for evaluation
+ */
+ explicit FilterExpr(Expr* aExpr) : expr(aExpr) {}
+
+ TX_DECL_EXPR
+
+ private:
+ mozilla::UniquePtr<Expr> expr;
+
+}; //-- FilterExpr
+
+class txLiteralExpr : public Expr {
+ public:
+ explicit txLiteralExpr(double aDbl)
+ : mValue(new NumberResult(aDbl, nullptr)) {}
+ explicit txLiteralExpr(const nsAString& aStr)
+ : mValue(new StringResult(aStr, nullptr)) {}
+ explicit txLiteralExpr(txAExprResult* aValue) : mValue(aValue) {}
+
+ TX_DECL_EXPR
+
+ private:
+ RefPtr<txAExprResult> mValue;
+};
+
+/**
+ * Represents an UnaryExpr. Returns the negative value of its expr.
+ **/
+class UnaryExpr : public Expr {
+ public:
+ explicit UnaryExpr(Expr* aExpr) : expr(aExpr) {}
+
+ TX_DECL_EXPR
+
+ private:
+ mozilla::UniquePtr<Expr> expr;
+}; //-- UnaryExpr
+
+/**
+ * Represents a BooleanExpr, a binary expression that
+ * performs a boolean operation between its lvalue and rvalue.
+ **/
+class BooleanExpr : public Expr {
+ public:
+ //-- BooleanExpr Types
+ enum _BooleanExprType { AND = 1, OR };
+
+ BooleanExpr(Expr* aLeftExpr, Expr* aRightExpr, short aOp)
+ : leftExpr(aLeftExpr), rightExpr(aRightExpr), op(aOp) {}
+
+ TX_DECL_EXPR
+
+ private:
+ mozilla::UniquePtr<Expr> leftExpr, rightExpr;
+ short op;
+}; //-- BooleanExpr
+
+/**
+ * Represents a MultiplicativeExpr, a binary expression that
+ * performs a multiplicative operation between its lvalue and rvalue:
+ * * : multiply
+ * mod : modulus
+ * div : divide
+ *
+ **/
+class txNumberExpr : public Expr {
+ public:
+ enum eOp { ADD, SUBTRACT, DIVIDE, MULTIPLY, MODULUS };
+
+ txNumberExpr(Expr* aLeftExpr, Expr* aRightExpr, eOp aOp)
+ : mLeftExpr(aLeftExpr), mRightExpr(aRightExpr), mOp(aOp) {}
+
+ TX_DECL_EXPR
+
+ private:
+ mozilla::UniquePtr<Expr> mLeftExpr, mRightExpr;
+ eOp mOp;
+}; //-- MultiplicativeExpr
+
+/**
+ * Represents a RelationalExpr, an expression that compares its lvalue
+ * to its rvalue using:
+ * = : equal to
+ * < : less than
+ * > : greater than
+ * <= : less than or equal to
+ * >= : greater than or equal to
+ *
+ **/
+class RelationalExpr : public Expr {
+ public:
+ enum RelationalExprType {
+ EQUAL,
+ NOT_EQUAL,
+ LESS_THAN,
+ GREATER_THAN,
+ LESS_OR_EQUAL,
+ GREATER_OR_EQUAL
+ };
+
+ RelationalExpr(Expr* aLeftExpr, Expr* aRightExpr, RelationalExprType aOp)
+ : mLeftExpr(aLeftExpr), mRightExpr(aRightExpr), mOp(aOp) {}
+
+ TX_DECL_EXPR
+
+ private:
+ bool compareResults(txIEvalContext* aContext, txAExprResult* aLeft,
+ txAExprResult* aRight);
+
+ mozilla::UniquePtr<Expr> mLeftExpr;
+ mozilla::UniquePtr<Expr> mRightExpr;
+ RelationalExprType mOp;
+};
+
+/**
+ * VariableRefExpr
+ * Represents a variable reference ($refname)
+ **/
+class VariableRefExpr : public Expr {
+ public:
+ VariableRefExpr(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID);
+
+ TX_DECL_EXPR
+
+ private:
+ RefPtr<nsAtom> mPrefix;
+ RefPtr<nsAtom> mLocalName;
+ int32_t mNamespace;
+};
+
+/**
+ * Represents a PathExpr
+ **/
+class PathExpr : public Expr {
+ public:
+ //-- Path Operators
+ //-- RELATIVE_OP is the default
+ //-- LF, changed from static const short to enum
+ enum PathOperator { RELATIVE_OP, DESCENDANT_OP };
+
+ /**
+ * Adds the Expr to this PathExpr
+ * The ownership of the given Expr is passed over the PathExpr,
+ * even on failure.
+ * @param aExpr the Expr to add to this PathExpr
+ */
+ void addExpr(Expr* aExpr, PathOperator pathOp);
+
+ /**
+ * Removes and deletes the expression at the given index.
+ */
+ void deleteExprAt(uint32_t aPos) {
+ NS_ASSERTION(aPos < mItems.Length(), "killing bad expression index");
+ mItems.RemoveElementAt(aPos);
+ }
+
+ TX_DECL_OPTIMIZABLE_EXPR
+
+ PathOperator getPathOpAt(uint32_t aPos) {
+ NS_ASSERTION(aPos < mItems.Length(), "getting bad pathop index");
+ return mItems[aPos].pathOp;
+ }
+ void setPathOpAt(uint32_t aPos, PathOperator aPathOp) {
+ NS_ASSERTION(aPos < mItems.Length(), "setting bad pathop index");
+ mItems[aPos].pathOp = aPathOp;
+ }
+
+ private:
+ class PathExprItem {
+ public:
+ mozilla::UniquePtr<Expr> expr;
+ PathOperator pathOp;
+ };
+
+ nsTArray<PathExprItem> mItems;
+
+ /*
+ * Selects from the descendants of the context node
+ * all nodes that match the Expr
+ */
+ nsresult evalDescendants(Expr* aStep, const txXPathNode& aNode,
+ txIMatchContext* aContext, txNodeSet* resNodes);
+};
+
+/**
+ * This class represents a RootExpr, which only matches the Document node
+ **/
+class RootExpr : public Expr {
+ public:
+ /**
+ * Creates a new RootExpr
+ */
+ RootExpr()
+#ifdef TX_TO_STRING
+ : mSerialize(true)
+#endif
+ {
+ }
+
+ TX_DECL_EXPR
+
+#ifdef TX_TO_STRING
+ public:
+ void setSerialize(bool aSerialize) { mSerialize = aSerialize; }
+
+ private:
+ // When a RootExpr is used in a PathExpr it shouldn't be serialized
+ bool mSerialize;
+#endif
+}; //-- RootExpr
+
+/**
+ * Represents a UnionExpr
+ **/
+class UnionExpr : public Expr {
+ public:
+ /**
+ * Adds the PathExpr to this UnionExpr
+ * The ownership of the given Expr is passed over the UnionExpr,
+ * even on failure.
+ * @param aExpr the Expr to add to this UnionExpr
+ */
+ void addExpr(Expr* aExpr) { mExpressions.AppendElement(aExpr); }
+
+ /**
+ * Removes and deletes the expression at the given index.
+ */
+ void deleteExprAt(uint32_t aPos) {
+ NS_ASSERTION(aPos < mExpressions.Length(), "killing bad expression index");
+
+ delete mExpressions[aPos];
+ mExpressions.RemoveElementAt(aPos);
+ }
+
+ TX_DECL_OPTIMIZABLE_EXPR
+
+ private:
+ txOwningArray<Expr> mExpressions;
+
+}; //-- UnionExpr
+
+/**
+ * Class specializing in executing expressions like "@foo" where we are
+ * interested in different result-types, and expressions like "@foo = 'hi'"
+ */
+class txNamedAttributeStep : public Expr {
+ public:
+ txNamedAttributeStep(int32_t aNsID, nsAtom* aPrefix, nsAtom* aLocalName);
+
+ TX_DECL_EXPR
+
+ private:
+ int32_t mNamespace;
+ RefPtr<nsAtom> mPrefix;
+ RefPtr<nsAtom> mLocalName;
+};
+
+/**
+ *
+ */
+class txUnionNodeTest : public txNodeTest {
+ public:
+ void addNodeTest(txNodeTest* aNodeTest) {
+ mNodeTests.AppendElement(aNodeTest);
+ }
+
+ TX_DECL_NODE_TEST
+
+ private:
+ txOwningArray<txNodeTest> mNodeTests;
+};
+
+/**
+ * Expression that failed to parse
+ */
+class txErrorExpr : public Expr {
+ public:
+#ifdef TX_TO_STRING
+ explicit txErrorExpr(const nsAString& aStr) : mStr(aStr) {}
+#endif
+
+ TX_DECL_EXPR
+
+#ifdef TX_TO_STRING
+ private:
+ nsString mStr;
+#endif
+};
+
+#endif
diff --git a/dom/xslt/xpath/txExprLexer.cpp b/dom/xslt/xpath/txExprLexer.cpp
new file mode 100644
index 0000000000..391dc2e875
--- /dev/null
+++ b/dom/xslt/xpath/txExprLexer.cpp
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/**
+ * Lexical analyzer for XPath expressions
+ */
+
+#include "txExprLexer.h"
+#include "nsGkAtoms.h"
+#include "nsString.h"
+#include "nsError.h"
+#include "txXMLUtils.h"
+
+/**
+ * Creates a new ExprLexer
+ */
+txExprLexer::txExprLexer()
+ : mPosition(nullptr),
+ mCurrentItem(nullptr),
+ mFirstItem(nullptr),
+ mLastItem(nullptr),
+ mTokenCount(0) {}
+
+/**
+ * Destroys this instance of an txExprLexer
+ */
+txExprLexer::~txExprLexer() {
+ //-- delete tokens
+ Token* tok = mFirstItem;
+ while (tok) {
+ Token* temp = tok->mNext;
+ delete tok;
+ tok = temp;
+ }
+ mCurrentItem = nullptr;
+}
+
+Token* txExprLexer::nextToken() {
+ if (!mCurrentItem) {
+ MOZ_ASSERT_UNREACHABLE("nextToken called on uninitialized lexer");
+ return nullptr;
+ }
+
+ if (mCurrentItem->mType == Token::END) {
+ // Do not progress beyond the end token
+ return mCurrentItem;
+ }
+
+ Token* token = mCurrentItem;
+ mCurrentItem = mCurrentItem->mNext;
+ return token;
+}
+
+void txExprLexer::addToken(Token* aToken) {
+ if (mLastItem) {
+ mLastItem->mNext = aToken;
+ }
+ if (!mFirstItem) {
+ mFirstItem = aToken;
+ mCurrentItem = aToken;
+ }
+ mLastItem = aToken;
+ ++mTokenCount;
+}
+
+/**
+ * Returns true if the following Token should be an operator.
+ * This is a helper for the first bullet of [XPath 3.7]
+ * Lexical Structure
+ */
+bool txExprLexer::nextIsOperatorToken(Token* aToken) {
+ if (!aToken || aToken->mType == Token::NULL_TOKEN) {
+ return false;
+ }
+ /* This relies on the tokens having the right order in txExprLexer.h */
+ return aToken->mType < Token::COMMA || aToken->mType > Token::UNION_OP;
+}
+
+/**
+ * Parses the given string into a sequence of Tokens
+ */
+nsresult txExprLexer::parse(const nsAString& aPattern) {
+ iterator end;
+ aPattern.BeginReading(mPosition);
+ aPattern.EndReading(end);
+
+ //-- initialize previous token, this will automatically get
+ //-- deleted when it goes out of scope
+ Token nullToken(nullptr, nullptr, Token::NULL_TOKEN);
+
+ Token::Type defType;
+ Token* newToken = nullptr;
+ Token* prevToken = &nullToken;
+ bool isToken;
+
+ while (mPosition < end) {
+ defType = Token::CNAME;
+ isToken = true;
+
+ if (*mPosition == DOLLAR_SIGN) {
+ if (++mPosition == end || !XMLUtils::isLetter(*mPosition)) {
+ return NS_ERROR_XPATH_INVALID_VAR_NAME;
+ }
+ defType = Token::VAR_REFERENCE;
+ }
+ // just reuse the QName parsing, which will use defType
+ // the token to construct
+
+ if (XMLUtils::isLetter(*mPosition)) {
+ // NCName, can get QName or OperatorName;
+ // FunctionName, NodeName, and AxisSpecifier may want whitespace,
+ // and are dealt with below
+ iterator start = mPosition;
+ while (++mPosition < end && XMLUtils::isNCNameChar(*mPosition)) {
+ /* just go */
+ }
+ if (mPosition < end && *mPosition == COLON) {
+ // try QName or wildcard, might need to step back for axis
+ if (++mPosition == end) {
+ return NS_ERROR_XPATH_UNEXPECTED_END;
+ }
+ if (XMLUtils::isLetter(*mPosition)) {
+ while (++mPosition < end && XMLUtils::isNCNameChar(*mPosition)) {
+ /* just go */
+ }
+ } else if (*mPosition == '*' && defType != Token::VAR_REFERENCE) {
+ // eat wildcard for NameTest, bail for var ref at COLON
+ ++mPosition;
+ } else {
+ --mPosition; // step back
+ }
+ }
+ if (nextIsOperatorToken(prevToken)) {
+ nsDependentSubstring op(Substring(start, mPosition));
+ if (nsGkAtoms::_and->Equals(op)) {
+ defType = Token::AND_OP;
+ } else if (nsGkAtoms::_or->Equals(op)) {
+ defType = Token::OR_OP;
+ } else if (nsGkAtoms::mod->Equals(op)) {
+ defType = Token::MODULUS_OP;
+ } else if (nsGkAtoms::div->Equals(op)) {
+ defType = Token::DIVIDE_OP;
+ } else {
+ // XXX QUESTION: spec is not too precise
+ // badops is sure an error, but is bad:ops, too? We say yes!
+ return NS_ERROR_XPATH_OPERATOR_EXPECTED;
+ }
+ }
+ newToken = new Token(start, mPosition, defType);
+ } else if (isXPathDigit(*mPosition)) {
+ iterator start = mPosition;
+ while (++mPosition < end && isXPathDigit(*mPosition)) {
+ /* just go */
+ }
+ if (mPosition < end && *mPosition == '.') {
+ while (++mPosition < end && isXPathDigit(*mPosition)) {
+ /* just go */
+ }
+ }
+ newToken = new Token(start, mPosition, Token::NUMBER);
+ } else {
+ switch (*mPosition) {
+ //-- ignore whitespace
+ case SPACE:
+ case TX_TAB:
+ case TX_CR:
+ case TX_LF:
+ ++mPosition;
+ isToken = false;
+ break;
+ case S_QUOTE:
+ case D_QUOTE: {
+ iterator start = mPosition;
+ while (++mPosition < end && *mPosition != *start) {
+ // eat literal
+ }
+ if (mPosition == end) {
+ mPosition = start;
+ return NS_ERROR_XPATH_UNCLOSED_LITERAL;
+ }
+ newToken = new Token(start + 1, mPosition, Token::LITERAL);
+ ++mPosition;
+ } break;
+ case PERIOD:
+ // period can be .., .(DIGITS)+ or ., check next
+ if (++mPosition == end) {
+ newToken = new Token(mPosition - 1, Token::SELF_NODE);
+ } else if (isXPathDigit(*mPosition)) {
+ iterator start = mPosition - 1;
+ while (++mPosition < end && isXPathDigit(*mPosition)) {
+ /* just go */
+ }
+ newToken = new Token(start, mPosition, Token::NUMBER);
+ } else if (*mPosition == PERIOD) {
+ ++mPosition;
+ newToken = new Token(mPosition - 2, mPosition, Token::PARENT_NODE);
+ } else {
+ newToken = new Token(mPosition - 1, Token::SELF_NODE);
+ }
+ break;
+ case COLON: // QNames are dealt above, must be axis ident
+ if (++mPosition >= end || *mPosition != COLON ||
+ prevToken->mType != Token::CNAME) {
+ return NS_ERROR_XPATH_BAD_COLON;
+ }
+ prevToken->mType = Token::AXIS_IDENTIFIER;
+ ++mPosition;
+ isToken = false;
+ break;
+ case FORWARD_SLASH:
+ if (++mPosition < end && *mPosition == FORWARD_SLASH) {
+ ++mPosition;
+ newToken = new Token(mPosition - 2, mPosition, Token::ANCESTOR_OP);
+ } else {
+ newToken = new Token(mPosition - 1, Token::PARENT_OP);
+ }
+ break;
+ case BANG: // can only be !=
+ if (++mPosition < end && *mPosition == EQUAL) {
+ ++mPosition;
+ newToken = new Token(mPosition - 2, mPosition, Token::NOT_EQUAL_OP);
+ break;
+ }
+ // Error ! is not not()
+ return NS_ERROR_XPATH_BAD_BANG;
+ case EQUAL:
+ newToken = new Token(mPosition, Token::EQUAL_OP);
+ ++mPosition;
+ break;
+ case L_ANGLE:
+ if (++mPosition == end) {
+ return NS_ERROR_XPATH_UNEXPECTED_END;
+ }
+ if (*mPosition == EQUAL) {
+ ++mPosition;
+ newToken =
+ new Token(mPosition - 2, mPosition, Token::LESS_OR_EQUAL_OP);
+ } else {
+ newToken = new Token(mPosition - 1, Token::LESS_THAN_OP);
+ }
+ break;
+ case R_ANGLE:
+ if (++mPosition == end) {
+ return NS_ERROR_XPATH_UNEXPECTED_END;
+ }
+ if (*mPosition == EQUAL) {
+ ++mPosition;
+ newToken =
+ new Token(mPosition - 2, mPosition, Token::GREATER_OR_EQUAL_OP);
+ } else {
+ newToken = new Token(mPosition - 1, Token::GREATER_THAN_OP);
+ }
+ break;
+ case HYPHEN:
+ newToken = new Token(mPosition, Token::SUBTRACTION_OP);
+ ++mPosition;
+ break;
+ case ASTERISK:
+ if (nextIsOperatorToken(prevToken)) {
+ newToken = new Token(mPosition, Token::MULTIPLY_OP);
+ } else {
+ newToken = new Token(mPosition, Token::CNAME);
+ }
+ ++mPosition;
+ break;
+ case L_PAREN:
+ if (prevToken->mType == Token::CNAME) {
+ const nsDependentSubstring& val = prevToken->Value();
+ if (val.EqualsLiteral("comment")) {
+ prevToken->mType = Token::COMMENT_AND_PAREN;
+ } else if (val.EqualsLiteral("node")) {
+ prevToken->mType = Token::NODE_AND_PAREN;
+ } else if (val.EqualsLiteral("processing-instruction")) {
+ prevToken->mType = Token::PROC_INST_AND_PAREN;
+ } else if (val.EqualsLiteral("text")) {
+ prevToken->mType = Token::TEXT_AND_PAREN;
+ } else {
+ prevToken->mType = Token::FUNCTION_NAME_AND_PAREN;
+ }
+ isToken = false;
+ } else {
+ newToken = new Token(mPosition, Token::L_PAREN);
+ }
+ ++mPosition;
+ break;
+ case R_PAREN:
+ newToken = new Token(mPosition, Token::R_PAREN);
+ ++mPosition;
+ break;
+ case L_BRACKET:
+ newToken = new Token(mPosition, Token::L_BRACKET);
+ ++mPosition;
+ break;
+ case R_BRACKET:
+ newToken = new Token(mPosition, Token::R_BRACKET);
+ ++mPosition;
+ break;
+ case COMMA:
+ newToken = new Token(mPosition, Token::COMMA);
+ ++mPosition;
+ break;
+ case AT_SIGN:
+ newToken = new Token(mPosition, Token::AT_SIGN);
+ ++mPosition;
+ break;
+ case PLUS:
+ newToken = new Token(mPosition, Token::ADDITION_OP);
+ ++mPosition;
+ break;
+ case VERT_BAR:
+ newToken = new Token(mPosition, Token::UNION_OP);
+ ++mPosition;
+ break;
+ default:
+ // Error, don't grok character :-(
+ return NS_ERROR_XPATH_ILLEGAL_CHAR;
+ }
+ }
+ if (isToken) {
+ NS_ENSURE_TRUE(newToken != mLastItem, NS_ERROR_FAILURE);
+ prevToken = newToken;
+ addToken(newToken);
+ }
+ }
+
+ // add a endToken to the list
+ newToken = new Token(end, end, Token::END);
+ addToken(newToken);
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xpath/txExprLexer.h b/dom/xslt/xpath/txExprLexer.h
new file mode 100644
index 0000000000..9d3ca2810e
--- /dev/null
+++ b/dom/xslt/xpath/txExprLexer.h
@@ -0,0 +1,200 @@
+/* -*- 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/. */
+
+#ifndef MITREXSL_EXPRLEXER_H
+#define MITREXSL_EXPRLEXER_H
+
+#include "txCore.h"
+#include "nsString.h"
+
+/**
+ * A Token class for the ExprLexer.
+ *
+ * This class was ported from XSL:P, an open source Java based
+ * XSLT processor, written by yours truly.
+ */
+class Token {
+ public:
+ /**
+ * Token types
+ */
+ enum Type {
+ //-- Trivial Tokens
+ NULL_TOKEN = 1,
+ LITERAL,
+ NUMBER,
+ CNAME,
+ VAR_REFERENCE,
+ PARENT_NODE,
+ SELF_NODE,
+ R_PAREN,
+ R_BRACKET, // 9
+ /**
+ * start of tokens for 3.7, bullet 1
+ * ExprLexer::nextIsOperatorToken bails if the tokens aren't
+ * consecutive.
+ */
+ COMMA,
+ AT_SIGN,
+ L_PAREN,
+ L_BRACKET,
+ AXIS_IDENTIFIER,
+
+ // These tokens include their following left parenthesis
+ FUNCTION_NAME_AND_PAREN, // 15
+ COMMENT_AND_PAREN,
+ NODE_AND_PAREN,
+ PROC_INST_AND_PAREN,
+ TEXT_AND_PAREN,
+
+ /**
+ * operators
+ */
+ //-- boolean ops
+ AND_OP, // 20
+ OR_OP,
+
+ //-- relational
+ EQUAL_OP, // 22
+ NOT_EQUAL_OP,
+ LESS_THAN_OP,
+ GREATER_THAN_OP,
+ LESS_OR_EQUAL_OP,
+ GREATER_OR_EQUAL_OP,
+ //-- additive operators
+ ADDITION_OP, // 28
+ SUBTRACTION_OP,
+ //-- multiplicative
+ DIVIDE_OP, // 30
+ MULTIPLY_OP,
+ MODULUS_OP,
+ //-- path operators
+ PARENT_OP, // 33
+ ANCESTOR_OP,
+ UNION_OP,
+ /**
+ * end of tokens for 3.7, bullet 1 -/
+ */
+ //-- Special endtoken
+ END // 36
+ };
+
+ /**
+ * Constructors
+ */
+ using iterator = nsAString::const_char_iterator;
+
+ Token(iterator aStart, iterator aEnd, Type aType)
+ : mStart(aStart), mEnd(aEnd), mType(aType), mNext(nullptr) {}
+ Token(iterator aChar, Type aType)
+ : mStart(aChar), mEnd(aChar + 1), mType(aType), mNext(nullptr) {}
+
+ const nsDependentSubstring Value() { return Substring(mStart, mEnd); }
+
+ iterator mStart, mEnd;
+ Type mType;
+ Token* mNext;
+};
+
+/**
+ * A class for splitting an "Expr" String into tokens and
+ * performing basic Lexical Analysis.
+ *
+ * This class was ported from XSL:P, an open source Java based XSL processor
+ */
+
+class txExprLexer {
+ public:
+ txExprLexer();
+ ~txExprLexer();
+
+ /**
+ * Parse the given string.
+ * returns an error result if lexing failed.
+ * The given string must outlive the use of the lexer, as the
+ * generated Tokens point to Substrings of it.
+ * mPosition points to the offending location in case of an error.
+ */
+ nsresult parse(const nsAString& aPattern);
+
+ using iterator = nsAString::const_char_iterator;
+ iterator mPosition;
+
+ /**
+ * Functions for iterating over the TokenList
+ */
+
+ Token* nextToken();
+ Token* peek() {
+ NS_ASSERTION(mCurrentItem, "peek called uninitialized lexer");
+ return mCurrentItem;
+ }
+ Token* peekAhead() {
+ NS_ASSERTION(mCurrentItem, "peekAhead called on uninitialized lexer");
+ // Don't peek past the end node
+ return (mCurrentItem && mCurrentItem->mNext) ? mCurrentItem->mNext
+ : mCurrentItem;
+ }
+ bool hasMoreTokens() {
+ NS_ASSERTION(mCurrentItem, "HasMoreTokens called on uninitialized lexer");
+ return (mCurrentItem && mCurrentItem->mType != Token::END);
+ }
+
+ /**
+ * Trivial Tokens
+ */
+ //-- LF, changed to enum
+ enum _TrivialTokens {
+ D_QUOTE = '\"',
+ S_QUOTE = '\'',
+ L_PAREN = '(',
+ R_PAREN = ')',
+ L_BRACKET = '[',
+ R_BRACKET = ']',
+ L_ANGLE = '<',
+ R_ANGLE = '>',
+ COMMA = ',',
+ PERIOD = '.',
+ ASTERISK = '*',
+ FORWARD_SLASH = '/',
+ EQUAL = '=',
+ BANG = '!',
+ VERT_BAR = '|',
+ AT_SIGN = '@',
+ DOLLAR_SIGN = '$',
+ PLUS = '+',
+ HYPHEN = '-',
+ COLON = ':',
+ //-- whitespace tokens
+ SPACE = ' ',
+ TX_TAB = '\t',
+ TX_CR = '\n',
+ TX_LF = '\r'
+ };
+
+ private:
+ Token* mCurrentItem;
+ Token* mFirstItem;
+ Token* mLastItem;
+
+ int mTokenCount;
+
+ void addToken(Token* aToken);
+
+ /**
+ * Returns true if the following Token should be an operator.
+ * This is a helper for the first bullet of [XPath 3.7]
+ * Lexical Structure
+ */
+ bool nextIsOperatorToken(Token* aToken);
+
+ /**
+ * Returns true if the given character represents a numeric letter (digit)
+ * Implemented in ExprLexerChars.cpp
+ */
+ static bool isXPathDigit(char16_t ch) { return (ch >= '0' && ch <= '9'); }
+};
+
+#endif
diff --git a/dom/xslt/xpath/txExprParser.cpp b/dom/xslt/xpath/txExprParser.cpp
new file mode 100644
index 0000000000..47f142d023
--- /dev/null
+++ b/dom/xslt/xpath/txExprParser.cpp
@@ -0,0 +1,855 @@
+/* -*- 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/. */
+
+/**
+ * ExprParser
+ * This class is used to parse XSL Expressions
+ * @see ExprLexer
+ **/
+
+#include "txExprParser.h"
+
+#include <utility>
+
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "txExpr.h"
+#include "txExprLexer.h"
+#include "txIXPathContext.h"
+#include "txStack.h"
+#include "txStringUtils.h"
+#include "txXPathNode.h"
+#include "txXPathOptimizer.h"
+
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+using mozilla::Unused;
+using mozilla::WrapUnique;
+
+/**
+ * Creates an Attribute Value Template using the given value
+ * This should move to XSLProcessor class
+ */
+nsresult txExprParser::createAVT(const nsAString& aAttrValue,
+ txIParseContext* aContext, Expr** aResult) {
+ *aResult = nullptr;
+ nsresult rv = NS_OK;
+ UniquePtr<Expr> expr;
+ FunctionCall* concat = nullptr;
+
+ nsAutoString literalString;
+ bool inExpr = false;
+ nsAString::const_char_iterator iter, start, end, avtStart;
+ aAttrValue.BeginReading(iter);
+ aAttrValue.EndReading(end);
+ avtStart = iter;
+
+ while (iter != end) {
+ // Every iteration through this loop parses either a literal section
+ // or an expression
+ start = iter;
+ UniquePtr<Expr> newExpr;
+ if (!inExpr) {
+ // Parse literal section
+ literalString.Truncate();
+ while (iter != end) {
+ char16_t q = *iter;
+ if (q == '{' || q == '}') {
+ // Store what we've found so far and set a new |start| to
+ // skip the (first) brace
+ literalString.Append(Substring(start, iter));
+ start = ++iter;
+ // Unless another brace follows we've found the start of
+ // an expression (in case of '{') or an unbalanced brace
+ // (in case of '}')
+ if (iter == end || *iter != q) {
+ if (q == '}') {
+ aContext->SetErrorOffset(iter - avtStart);
+ return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE;
+ }
+
+ inExpr = true;
+ break;
+ }
+ // We found a second brace, let that be part of the next
+ // literal section being parsed and continue looping
+ }
+ ++iter;
+ }
+
+ if (start == iter && literalString.IsEmpty()) {
+ // Restart the loop since we didn't create an expression
+ continue;
+ }
+ newExpr =
+ MakeUnique<txLiteralExpr>(literalString + Substring(start, iter));
+ } else {
+ // Parse expressions, iter is already past the initial '{' when
+ // we get here.
+ while (iter != end) {
+ if (*iter == '}') {
+ rv = createExprInternal(Substring(start, iter), start - avtStart,
+ aContext, getter_Transfers(newExpr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ inExpr = false;
+ ++iter; // skip closing '}'
+ break;
+ } else if (*iter == '\'' || *iter == '"') {
+ char16_t q = *iter;
+ while (++iter != end && *iter != q) {
+ } /* do nothing */
+ if (iter == end) {
+ break;
+ }
+ }
+ ++iter;
+ }
+
+ if (inExpr) {
+ aContext->SetErrorOffset(start - avtStart);
+ return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE;
+ }
+ }
+
+ // Add expression, create a concat() call if necessary
+ if (!expr) {
+ expr = std::move(newExpr);
+ } else {
+ if (!concat) {
+ concat = new txCoreFunctionCall(txCoreFunctionCall::CONCAT);
+ concat->addParam(expr.release());
+ expr = WrapUnique(concat);
+ }
+
+ concat->addParam(newExpr.release());
+ }
+ }
+
+ if (inExpr) {
+ aContext->SetErrorOffset(iter - avtStart);
+ return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE;
+ }
+
+ if (!expr) {
+ expr = MakeUnique<txLiteralExpr>(u""_ns);
+ }
+
+ *aResult = expr.release();
+
+ return NS_OK;
+}
+
+nsresult txExprParser::createExprInternal(const nsAString& aExpression,
+ uint32_t aSubStringPos,
+ txIParseContext* aContext,
+ Expr** aExpr) {
+ NS_ENSURE_ARG_POINTER(aExpr);
+ *aExpr = nullptr;
+ txExprLexer lexer;
+ nsresult rv = lexer.parse(aExpression);
+ if (NS_FAILED(rv)) {
+ nsAString::const_char_iterator start;
+ aExpression.BeginReading(start);
+ aContext->SetErrorOffset(lexer.mPosition - start + aSubStringPos);
+ return rv;
+ }
+ UniquePtr<Expr> expr;
+ rv = createExpr(lexer, aContext, getter_Transfers(expr));
+ if (NS_SUCCEEDED(rv) && lexer.peek()->mType != Token::END) {
+ rv = NS_ERROR_XPATH_BINARY_EXPECTED;
+ }
+ if (NS_FAILED(rv)) {
+ nsAString::const_char_iterator start;
+ aExpression.BeginReading(start);
+ aContext->SetErrorOffset(lexer.peek()->mStart - start + aSubStringPos);
+
+ return rv;
+ }
+
+ txXPathOptimizer optimizer;
+ Expr* newExpr = nullptr;
+ optimizer.optimize(expr.get(), &newExpr);
+
+ *aExpr = newExpr ? newExpr : expr.release();
+
+ return NS_OK;
+}
+
+/**
+ * Private Methods
+ */
+
+/**
+ * Creates a binary Expr for the given operator
+ */
+nsresult txExprParser::createBinaryExpr(UniquePtr<Expr>& left,
+ UniquePtr<Expr>& right, Token* op,
+ Expr** aResult) {
+ NS_ASSERTION(op, "internal error");
+ *aResult = nullptr;
+
+ Expr* expr = nullptr;
+ switch (op->mType) {
+ //-- math ops
+ case Token::ADDITION_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::ADD);
+ break;
+ case Token::SUBTRACTION_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::SUBTRACT);
+ break;
+ case Token::DIVIDE_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::DIVIDE);
+ break;
+ case Token::MODULUS_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::MODULUS);
+ break;
+ case Token::MULTIPLY_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::MULTIPLY);
+ break;
+
+ //-- case boolean ops
+ case Token::AND_OP:
+ expr = new BooleanExpr(left.get(), right.get(), BooleanExpr::AND);
+ break;
+ case Token::OR_OP:
+ expr = new BooleanExpr(left.get(), right.get(), BooleanExpr::OR);
+ break;
+
+ //-- equality ops
+ case Token::EQUAL_OP:
+ expr = new RelationalExpr(left.get(), right.get(), RelationalExpr::EQUAL);
+ break;
+ case Token::NOT_EQUAL_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::NOT_EQUAL);
+ break;
+
+ //-- relational ops
+ case Token::LESS_THAN_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::LESS_THAN);
+ break;
+ case Token::GREATER_THAN_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::GREATER_THAN);
+ break;
+ case Token::LESS_OR_EQUAL_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::LESS_OR_EQUAL);
+ break;
+ case Token::GREATER_OR_EQUAL_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::GREATER_OR_EQUAL);
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("operator tokens should be already checked");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ Unused << left.release();
+ Unused << right.release();
+
+ *aResult = expr;
+ return NS_OK;
+}
+
+nsresult txExprParser::createExpr(txExprLexer& lexer, txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ nsresult rv = NS_OK;
+ bool done = false;
+
+ UniquePtr<Expr> expr;
+
+ txStack exprs;
+ txStack ops;
+
+ while (!done) {
+ uint16_t negations = 0;
+ while (lexer.peek()->mType == Token::SUBTRACTION_OP) {
+ negations++;
+ lexer.nextToken();
+ }
+
+ rv = createUnionExpr(lexer, aContext, getter_Transfers(expr));
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ if (negations > 0) {
+ if (negations % 2 == 0) {
+ auto fcExpr =
+ MakeUnique<txCoreFunctionCall>(txCoreFunctionCall::NUMBER);
+
+ fcExpr->addParam(expr.release());
+ expr = std::move(fcExpr);
+ } else {
+ expr = MakeUnique<UnaryExpr>(expr.release());
+ }
+ }
+
+ short tokPrecedence = precedence(lexer.peek());
+ if (tokPrecedence != 0) {
+ Token* tok = lexer.nextToken();
+ while (!exprs.isEmpty() &&
+ tokPrecedence <= precedence(static_cast<Token*>(ops.peek()))) {
+ // can't use expr as argument due to order of evaluation
+ UniquePtr<Expr> left(static_cast<Expr*>(exprs.pop()));
+ UniquePtr<Expr> right(std::move(expr));
+ rv = createBinaryExpr(left, right, static_cast<Token*>(ops.pop()),
+ getter_Transfers(expr));
+ if (NS_FAILED(rv)) {
+ done = true;
+ break;
+ }
+ }
+ exprs.push(expr.release());
+ ops.push(tok);
+ } else {
+ done = true;
+ }
+ }
+
+ while (NS_SUCCEEDED(rv) && !exprs.isEmpty()) {
+ UniquePtr<Expr> left(static_cast<Expr*>(exprs.pop()));
+ UniquePtr<Expr> right(std::move(expr));
+ rv = createBinaryExpr(left, right, static_cast<Token*>(ops.pop()),
+ getter_Transfers(expr));
+ }
+ // clean up on error
+ while (!exprs.isEmpty()) {
+ delete static_cast<Expr*>(exprs.pop());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = expr.release();
+ return NS_OK;
+}
+
+nsresult txExprParser::createFilterOrStep(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ nsresult rv = NS_OK;
+ Token* tok = lexer.peek();
+
+ UniquePtr<Expr> expr;
+ switch (tok->mType) {
+ case Token::FUNCTION_NAME_AND_PAREN:
+ rv = createFunctionCall(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case Token::VAR_REFERENCE:
+ lexer.nextToken();
+ {
+ RefPtr<nsAtom> prefix, lName;
+ int32_t nspace;
+ nsresult rv = resolveQName(tok->Value(), getter_AddRefs(prefix),
+ aContext, getter_AddRefs(lName), nspace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ expr = MakeUnique<VariableRefExpr>(prefix, lName, nspace);
+ }
+ break;
+ case Token::L_PAREN:
+ lexer.nextToken();
+ rv = createExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (lexer.peek()->mType != Token::R_PAREN) {
+ return NS_ERROR_XPATH_PAREN_EXPECTED;
+ }
+ lexer.nextToken();
+ break;
+ case Token::LITERAL:
+ lexer.nextToken();
+ expr = MakeUnique<txLiteralExpr>(tok->Value());
+ break;
+ case Token::NUMBER: {
+ lexer.nextToken();
+ expr = MakeUnique<txLiteralExpr>(txDouble::toDouble(tok->Value()));
+ break;
+ }
+ default:
+ return createLocationStep(lexer, aContext, aResult);
+ }
+
+ if (lexer.peek()->mType == Token::L_BRACKET) {
+ UniquePtr<FilterExpr> filterExpr(new FilterExpr(expr.get()));
+
+ Unused << expr.release();
+
+ //-- handle predicates
+ rv = parsePredicates(filterExpr.get(), lexer, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ expr = std::move(filterExpr);
+ }
+
+ *aResult = expr.release();
+ return NS_OK;
+}
+
+nsresult txExprParser::createFunctionCall(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ UniquePtr<FunctionCall> fnCall;
+
+ Token* tok = lexer.nextToken();
+ NS_ASSERTION(tok->mType == Token::FUNCTION_NAME_AND_PAREN,
+ "FunctionCall expected");
+
+ //-- compare function names
+ RefPtr<nsAtom> prefix, lName;
+ int32_t namespaceID;
+ nsresult rv = resolveQName(tok->Value(), getter_AddRefs(prefix), aContext,
+ getter_AddRefs(lName), namespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txCoreFunctionCall::eType type;
+ if (namespaceID == kNameSpaceID_None &&
+ txCoreFunctionCall::getTypeFromAtom(lName, type)) {
+ // It is a known built-in function.
+ fnCall = MakeUnique<txCoreFunctionCall>(type);
+ }
+
+ // check extension functions and xslt
+ if (!fnCall) {
+ rv = aContext->resolveFunctionCall(lName, namespaceID,
+ getter_Transfers(fnCall));
+
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // this should just happen for unparsed-entity-uri()
+ NS_ASSERTION(!fnCall, "Now is it implemented or not?");
+ rv = parseParameters(0, lexer, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = new txLiteralExpr(tok->Value() + u" not implemented."_ns);
+
+ return NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ //-- handle parametes
+ rv = parseParameters(fnCall.get(), lexer, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = fnCall.release();
+ return NS_OK;
+}
+
+nsresult txExprParser::createLocationStep(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aExpr) {
+ *aExpr = nullptr;
+
+ //-- child axis is default
+ LocationStep::LocationStepType axisIdentifier = LocationStep::CHILD_AXIS;
+ UniquePtr<txNodeTest> nodeTest;
+
+ //-- get Axis Identifier or AbbreviatedStep, if present
+ Token* tok = lexer.peek();
+ switch (tok->mType) {
+ case Token::AXIS_IDENTIFIER: {
+ //-- eat token
+ lexer.nextToken();
+ RefPtr<nsAtom> axis = NS_Atomize(tok->Value());
+ if (axis == nsGkAtoms::ancestor) {
+ axisIdentifier = LocationStep::ANCESTOR_AXIS;
+ } else if (axis == nsGkAtoms::ancestorOrSelf) {
+ axisIdentifier = LocationStep::ANCESTOR_OR_SELF_AXIS;
+ } else if (axis == nsGkAtoms::attribute) {
+ axisIdentifier = LocationStep::ATTRIBUTE_AXIS;
+ } else if (axis == nsGkAtoms::child) {
+ axisIdentifier = LocationStep::CHILD_AXIS;
+ } else if (axis == nsGkAtoms::descendant) {
+ axisIdentifier = LocationStep::DESCENDANT_AXIS;
+ } else if (axis == nsGkAtoms::descendantOrSelf) {
+ axisIdentifier = LocationStep::DESCENDANT_OR_SELF_AXIS;
+ } else if (axis == nsGkAtoms::following) {
+ axisIdentifier = LocationStep::FOLLOWING_AXIS;
+ } else if (axis == nsGkAtoms::followingSibling) {
+ axisIdentifier = LocationStep::FOLLOWING_SIBLING_AXIS;
+ } else if (axis == nsGkAtoms::_namespace) {
+ axisIdentifier = LocationStep::NAMESPACE_AXIS;
+ } else if (axis == nsGkAtoms::parent) {
+ axisIdentifier = LocationStep::PARENT_AXIS;
+ } else if (axis == nsGkAtoms::preceding) {
+ axisIdentifier = LocationStep::PRECEDING_AXIS;
+ } else if (axis == nsGkAtoms::precedingSibling) {
+ axisIdentifier = LocationStep::PRECEDING_SIBLING_AXIS;
+ } else if (axis == nsGkAtoms::self) {
+ axisIdentifier = LocationStep::SELF_AXIS;
+ } else {
+ return NS_ERROR_XPATH_INVALID_AXIS;
+ }
+ break;
+ }
+ case Token::AT_SIGN:
+ //-- eat token
+ lexer.nextToken();
+ axisIdentifier = LocationStep::ATTRIBUTE_AXIS;
+ break;
+ case Token::PARENT_NODE:
+ //-- eat token
+ lexer.nextToken();
+ axisIdentifier = LocationStep::PARENT_AXIS;
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE);
+ break;
+ case Token::SELF_NODE:
+ //-- eat token
+ lexer.nextToken();
+ axisIdentifier = LocationStep::SELF_AXIS;
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE);
+ break;
+ default:
+ break;
+ }
+
+ //-- get NodeTest unless an AbbreviatedStep was found
+ nsresult rv = NS_OK;
+ if (!nodeTest) {
+ tok = lexer.peek();
+
+ if (tok->mType == Token::CNAME) {
+ lexer.nextToken();
+ // resolve QName
+ RefPtr<nsAtom> prefix, lName;
+ int32_t nspace;
+ rv = resolveQName(tok->Value(), getter_AddRefs(prefix), aContext,
+ getter_AddRefs(lName), nspace, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nodeTest = MakeUnique<txNameTest>(
+ prefix, lName, nspace,
+ axisIdentifier == LocationStep::ATTRIBUTE_AXIS
+ ? static_cast<uint16_t>(txXPathNodeType::ATTRIBUTE_NODE)
+ : static_cast<uint16_t>(txXPathNodeType::ELEMENT_NODE));
+ } else {
+ rv = createNodeTypeTest(lexer, getter_Transfers(nodeTest));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ UniquePtr<LocationStep> lstep(
+ new LocationStep(nodeTest.get(), axisIdentifier));
+
+ Unused << nodeTest.release();
+
+ //-- handle predicates
+ rv = parsePredicates(lstep.get(), lexer, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aExpr = lstep.release();
+ return NS_OK;
+}
+
+/**
+ * This method only handles comment(), text(), processing-instructing()
+ * and node()
+ */
+nsresult txExprParser::createNodeTypeTest(txExprLexer& lexer,
+ txNodeTest** aTest) {
+ *aTest = 0;
+ UniquePtr<txNodeTypeTest> nodeTest;
+
+ Token* nodeTok = lexer.peek();
+
+ switch (nodeTok->mType) {
+ case Token::COMMENT_AND_PAREN:
+ lexer.nextToken();
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::COMMENT_TYPE);
+ break;
+ case Token::NODE_AND_PAREN:
+ lexer.nextToken();
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE);
+ break;
+ case Token::PROC_INST_AND_PAREN:
+ lexer.nextToken();
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::PI_TYPE);
+ break;
+ case Token::TEXT_AND_PAREN:
+ lexer.nextToken();
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::TEXT_TYPE);
+ break;
+ default:
+ return NS_ERROR_XPATH_NO_NODE_TYPE_TEST;
+ }
+
+ if (nodeTok->mType == Token::PROC_INST_AND_PAREN &&
+ lexer.peek()->mType == Token::LITERAL) {
+ Token* tok = lexer.nextToken();
+ nodeTest->setNodeName(tok->Value());
+ }
+ if (lexer.peek()->mType != Token::R_PAREN) {
+ return NS_ERROR_XPATH_PAREN_EXPECTED;
+ }
+ lexer.nextToken();
+
+ *aTest = nodeTest.release();
+ return NS_OK;
+}
+
+/**
+ * Creates a PathExpr using the given txExprLexer
+ * @param lexer the txExprLexer for retrieving Tokens
+ */
+nsresult txExprParser::createPathExpr(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ UniquePtr<Expr> expr;
+
+ Token* tok = lexer.peek();
+
+ // is this a root expression?
+ if (tok->mType == Token::PARENT_OP) {
+ if (!isLocationStepToken(lexer.peekAhead())) {
+ lexer.nextToken();
+ *aResult = new RootExpr();
+ return NS_OK;
+ }
+ }
+
+ // parse first step (possibly a FilterExpr)
+ nsresult rv = NS_OK;
+ if (tok->mType != Token::PARENT_OP && tok->mType != Token::ANCESTOR_OP) {
+ rv = createFilterOrStep(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // is this a singlestep path expression?
+ tok = lexer.peek();
+ if (tok->mType != Token::PARENT_OP && tok->mType != Token::ANCESTOR_OP) {
+ *aResult = expr.release();
+ return NS_OK;
+ }
+ } else {
+ expr = MakeUnique<RootExpr>();
+
+#ifdef TX_TO_STRING
+ static_cast<RootExpr*>(expr.get())->setSerialize(false);
+#endif
+ }
+
+ // We have a PathExpr containing several steps
+ UniquePtr<PathExpr> pathExpr(new PathExpr());
+ pathExpr->addExpr(expr.release(), PathExpr::RELATIVE_OP);
+
+ // this is ugly
+ while (1) {
+ PathExpr::PathOperator pathOp;
+ switch (lexer.peek()->mType) {
+ case Token::ANCESTOR_OP:
+ pathOp = PathExpr::DESCENDANT_OP;
+ break;
+ case Token::PARENT_OP:
+ pathOp = PathExpr::RELATIVE_OP;
+ break;
+ default:
+ *aResult = pathExpr.release();
+ return NS_OK;
+ }
+ lexer.nextToken();
+
+ rv = createLocationStep(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pathExpr->addExpr(expr.release(), pathOp);
+ }
+ MOZ_ASSERT_UNREACHABLE("internal xpath parser error");
+ return NS_ERROR_UNEXPECTED;
+}
+
+/**
+ * Creates a PathExpr using the given txExprLexer
+ * @param lexer the txExprLexer for retrieving Tokens
+ */
+nsresult txExprParser::createUnionExpr(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ UniquePtr<Expr> expr;
+ nsresult rv = createPathExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (lexer.peek()->mType != Token::UNION_OP) {
+ *aResult = expr.release();
+ return NS_OK;
+ }
+
+ UniquePtr<UnionExpr> unionExpr(new UnionExpr());
+ unionExpr->addExpr(expr.release());
+
+ while (lexer.peek()->mType == Token::UNION_OP) {
+ lexer.nextToken(); //-- eat token
+
+ rv = createPathExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ unionExpr->addExpr(expr.release());
+ }
+
+ *aResult = unionExpr.release();
+ return NS_OK;
+}
+
+bool txExprParser::isLocationStepToken(Token* aToken) {
+ // We could put these in consecutive order in ExprLexer.h for speed
+ return aToken->mType == Token::AXIS_IDENTIFIER ||
+ aToken->mType == Token::AT_SIGN ||
+ aToken->mType == Token::PARENT_NODE ||
+ aToken->mType == Token::SELF_NODE || aToken->mType == Token::CNAME ||
+ aToken->mType == Token::COMMENT_AND_PAREN ||
+ aToken->mType == Token::NODE_AND_PAREN ||
+ aToken->mType == Token::PROC_INST_AND_PAREN ||
+ aToken->mType == Token::TEXT_AND_PAREN;
+}
+
+/**
+ * Using the given lexer, parses the tokens if they represent a predicate list
+ * If an error occurs a non-zero String pointer will be returned containing the
+ * error message.
+ * @param predicateList, the PredicateList to add predicate expressions to
+ * @param lexer the txExprLexer to use for parsing tokens
+ * @return 0 if successful, or a String pointer to the error message
+ */
+nsresult txExprParser::parsePredicates(PredicateList* aPredicateList,
+ txExprLexer& lexer,
+ txIParseContext* aContext) {
+ UniquePtr<Expr> expr;
+ nsresult rv = NS_OK;
+ while (lexer.peek()->mType == Token::L_BRACKET) {
+ //-- eat Token
+ lexer.nextToken();
+
+ rv = createExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPredicateList->add(expr.release());
+
+ if (lexer.peek()->mType != Token::R_BRACKET) {
+ return NS_ERROR_XPATH_BRACKET_EXPECTED;
+ }
+ lexer.nextToken();
+ }
+ return NS_OK;
+}
+
+/**
+ * Using the given lexer, parses the tokens if they represent a parameter list
+ * If an error occurs a non-zero String pointer will be returned containing the
+ * error message.
+ * @param list, the List to add parameter expressions to
+ * @param lexer the txExprLexer to use for parsing tokens
+ * @return NS_OK if successful, or another rv otherwise
+ */
+nsresult txExprParser::parseParameters(FunctionCall* aFnCall,
+ txExprLexer& lexer,
+ txIParseContext* aContext) {
+ if (lexer.peek()->mType == Token::R_PAREN) {
+ lexer.nextToken();
+ return NS_OK;
+ }
+
+ UniquePtr<Expr> expr;
+ nsresult rv = NS_OK;
+ while (1) {
+ rv = createExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aFnCall) {
+ aFnCall->addParam(expr.release());
+ }
+
+ switch (lexer.peek()->mType) {
+ case Token::R_PAREN:
+ lexer.nextToken();
+ return NS_OK;
+ case Token::COMMA: //-- param separator
+ lexer.nextToken();
+ break;
+ default:
+ return NS_ERROR_XPATH_PAREN_EXPECTED;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("internal xpath parser error");
+ return NS_ERROR_UNEXPECTED;
+}
+
+short txExprParser::precedence(Token* aToken) {
+ switch (aToken->mType) {
+ case Token::OR_OP:
+ return 1;
+ case Token::AND_OP:
+ return 2;
+ //-- equality
+ case Token::EQUAL_OP:
+ case Token::NOT_EQUAL_OP:
+ return 3;
+ //-- relational
+ case Token::LESS_THAN_OP:
+ case Token::GREATER_THAN_OP:
+ case Token::LESS_OR_EQUAL_OP:
+ case Token::GREATER_OR_EQUAL_OP:
+ return 4;
+ //-- additive operators
+ case Token::ADDITION_OP:
+ case Token::SUBTRACTION_OP:
+ return 5;
+ //-- multiplicative
+ case Token::DIVIDE_OP:
+ case Token::MULTIPLY_OP:
+ case Token::MODULUS_OP:
+ return 6;
+ default:
+ break;
+ }
+ return 0;
+}
+
+nsresult txExprParser::resolveQName(const nsAString& aQName, nsAtom** aPrefix,
+ txIParseContext* aContext,
+ nsAtom** aLocalName, int32_t& aNamespace,
+ bool aIsNameTest) {
+ aNamespace = kNameSpaceID_None;
+ int32_t idx = aQName.FindChar(':');
+ if (idx > 0) {
+ *aPrefix = NS_Atomize(StringHead(aQName, (uint32_t)idx)).take();
+ if (!*aPrefix) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aLocalName = NS_Atomize(Substring(aQName, (uint32_t)idx + 1,
+ aQName.Length() - (idx + 1)))
+ .take();
+ if (!*aLocalName) {
+ NS_RELEASE(*aPrefix);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return aContext->resolveNamespacePrefix(*aPrefix, aNamespace);
+ }
+ // the lexer dealt with idx == 0
+ *aPrefix = 0;
+ if (aIsNameTest && aContext->caseInsensitiveNameTests()) {
+ nsAutoString lcname;
+ nsContentUtils::ASCIIToLower(aQName, lcname);
+ *aLocalName = NS_Atomize(lcname).take();
+ } else {
+ *aLocalName = NS_Atomize(aQName).take();
+ }
+ if (!*aLocalName) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
diff --git a/dom/xslt/xpath/txExprParser.h b/dom/xslt/xpath/txExprParser.h
new file mode 100644
index 0000000000..06c07cf232
--- /dev/null
+++ b/dom/xslt/xpath/txExprParser.h
@@ -0,0 +1,95 @@
+/* -*- 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/. */
+
+/**
+ * ExprParser
+ * This class is used to parse XSL Expressions
+ * @see ExprLexer
+ **/
+
+#ifndef MITREXSL_EXPRPARSER_H
+#define MITREXSL_EXPRPARSER_H
+
+#include "txCore.h"
+#include "mozilla/UniquePtr.h"
+#include "nsString.h"
+
+class Expr;
+class txExprLexer;
+class FunctionCall;
+class LocationStep;
+class nsAtom;
+class PredicateList;
+class Token;
+class txIParseContext;
+class txNodeTest;
+
+class txExprParser {
+ public:
+ static nsresult createExpr(const nsAString& aExpression,
+ txIParseContext* aContext, Expr** aExpr) {
+ return createExprInternal(aExpression, 0, aContext, aExpr);
+ }
+
+ /**
+ * Creates an Attribute Value Template using the given value
+ */
+ static nsresult createAVT(const nsAString& aAttrValue,
+ txIParseContext* aContext, Expr** aResult);
+
+ protected:
+ static nsresult createExprInternal(const nsAString& aExpression,
+ uint32_t aSubStringPos,
+ txIParseContext* aContext, Expr** aExpr);
+ /**
+ * Using nsAutoPtr& to optimize passing the ownership to the
+ * created binary expression objects.
+ */
+ static nsresult createBinaryExpr(mozilla::UniquePtr<Expr>& left,
+ mozilla::UniquePtr<Expr>& right, Token* op,
+ Expr** aResult);
+ static nsresult createExpr(txExprLexer& lexer, txIParseContext* aContext,
+ Expr** aResult);
+ static nsresult createFilterOrStep(txExprLexer& lexer,
+ txIParseContext* aContext, Expr** aResult);
+ static nsresult createFunctionCall(txExprLexer& lexer,
+ txIParseContext* aContext, Expr** aResult);
+ static nsresult createLocationStep(txExprLexer& lexer,
+ txIParseContext* aContext, Expr** aResult);
+ static nsresult createNodeTypeTest(txExprLexer& lexer, txNodeTest** aResult);
+ static nsresult createPathExpr(txExprLexer& lexer, txIParseContext* aContext,
+ Expr** aResult);
+ static nsresult createUnionExpr(txExprLexer& lexer, txIParseContext* aContext,
+ Expr** aResult);
+
+ static bool isLocationStepToken(Token* aToken);
+
+ static short precedence(Token* aToken);
+
+ /**
+ * Resolve a QName, given the mContext parse context.
+ * Returns prefix and localName as well as namespace ID
+ */
+ static nsresult resolveQName(const nsAString& aQName, nsAtom** aPrefix,
+ txIParseContext* aContext, nsAtom** aLocalName,
+ int32_t& aNamespace, bool aIsNameTest = false);
+
+ /**
+ * Using the given lexer, parses the tokens if they represent a
+ * predicate list
+ * If an error occurs a non-zero String pointer will be returned
+ * containing the error message.
+ * @param predicateList, the PredicateList to add predicate expressions to
+ * @param lexer the ExprLexer to use for parsing tokens
+ * @return 0 if successful, or a String pointer to the error message
+ */
+ static nsresult parsePredicates(PredicateList* aPredicateList,
+ txExprLexer& lexer,
+ txIParseContext* aContext);
+ static nsresult parseParameters(FunctionCall* aFnCall, txExprLexer& lexer,
+ txIParseContext* aContext);
+};
+
+#endif
diff --git a/dom/xslt/xpath/txExprResult.h b/dom/xslt/xpath/txExprResult.h
new file mode 100644
index 0000000000..5572b222f5
--- /dev/null
+++ b/dom/xslt/xpath/txExprResult.h
@@ -0,0 +1,118 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_EXPRRESULT_H
+#define TRANSFRMX_EXPRRESULT_H
+
+#include "nsString.h"
+#include "txCore.h"
+#include "txResultRecycler.h"
+
+/*
+ * ExprResult
+ *
+ * Classes Represented:
+ * BooleanResult, ExprResult, NumberResult, StringResult
+ *
+ * Note: for NodeSet, see NodeSet.h
+ */
+
+class txAExprResult {
+ public:
+ friend class txResultRecycler;
+
+ // Update txLiteralExpr::getReturnType and sTypes in txEXSLTFunctions.cpp if
+ // this enum is changed.
+ enum ResultType {
+ NODESET = 0,
+ BOOLEAN,
+ NUMBER,
+ STRING,
+ RESULT_TREE_FRAGMENT
+ };
+
+ explicit txAExprResult(txResultRecycler* aRecycler) : mRecycler(aRecycler) {}
+ virtual ~txAExprResult() = default;
+
+ void AddRef() {
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "txAExprResult", sizeof(*this));
+ }
+
+ void Release(); // Implemented in txResultRecycler.cpp
+
+ /**
+ * Returns the type of ExprResult represented
+ * @return the type of ExprResult represented
+ **/
+ virtual short getResultType() = 0;
+
+ /**
+ * Creates a String representation of this ExprResult
+ * @param aResult the destination string to append the String
+ * representation to.
+ **/
+ virtual void stringValue(nsString& aResult) = 0;
+
+ /**
+ * Returns a pointer to the stringvalue if possible. Otherwise null is
+ * returned.
+ */
+ virtual const nsString* stringValuePointer() = 0;
+
+ /**
+ * Converts this ExprResult to a Boolean (bool) value
+ * @return the Boolean value
+ **/
+ virtual bool booleanValue() = 0;
+
+ /**
+ * Converts this ExprResult to a Number (double) value
+ * @return the Number value
+ **/
+ virtual double numberValue() = 0;
+
+ private:
+ nsAutoRefCnt mRefCnt;
+ RefPtr<txResultRecycler> mRecycler;
+};
+
+#define TX_DECL_EXPRRESULT \
+ virtual short getResultType() override; \
+ virtual void stringValue(nsString& aString) override; \
+ virtual const nsString* stringValuePointer() override; \
+ virtual bool booleanValue() override; \
+ virtual double numberValue() override;
+
+class BooleanResult : public txAExprResult {
+ public:
+ explicit BooleanResult(bool aValue);
+
+ TX_DECL_EXPRRESULT
+
+ private:
+ bool value;
+};
+
+class NumberResult : public txAExprResult {
+ public:
+ NumberResult(double aValue, txResultRecycler* aRecycler);
+
+ TX_DECL_EXPRRESULT
+
+ double value;
+};
+
+class StringResult : public txAExprResult {
+ public:
+ explicit StringResult(txResultRecycler* aRecycler);
+ StringResult(const nsAString& aValue, txResultRecycler* aRecycler);
+
+ TX_DECL_EXPRRESULT
+
+ nsString mValue;
+};
+
+#endif
diff --git a/dom/xslt/xpath/txFilterExpr.cpp b/dom/xslt/xpath/txFilterExpr.cpp
new file mode 100644
index 0000000000..f632683290
--- /dev/null
+++ b/dom/xslt/xpath/txFilterExpr.cpp
@@ -0,0 +1,88 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txIXPathContext.h"
+
+using mozilla::Unused;
+using mozilla::WrapUnique;
+
+//-- Implementation of FilterExpr --/
+
+//-----------------------------/
+//- Virtual methods from Expr -/
+//-----------------------------/
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ProcessorState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ * @see Expr
+ **/
+nsresult FilterExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = expr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(exprRes->getResultType() == txAExprResult::NODESET,
+ NS_ERROR_XSLT_NODESET_EXPECTED);
+
+ RefPtr<txNodeSet> nodes =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes));
+ // null out exprRes so that we can test for shared-ness
+ exprRes = nullptr;
+
+ RefPtr<txNodeSet> nonShared;
+ rv = aContext->recycler()->getNonSharedNodeSet(nodes,
+ getter_AddRefs(nonShared));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = evaluatePredicates(nonShared, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = nonShared;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+} //-- evaluate
+
+TX_IMPL_EXPR_STUBS_BASE(FilterExpr, NODESET_RESULT)
+
+Expr* FilterExpr::getSubExprAt(uint32_t aPos) {
+ if (aPos == 0) {
+ return expr.get();
+ }
+ return PredicateList::getSubExprAt(aPos - 1);
+}
+
+void FilterExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ if (aPos == 0) {
+ Unused << expr.release();
+ expr = WrapUnique(aExpr);
+ } else {
+ PredicateList::setSubExprAt(aPos - 1, aExpr);
+ }
+}
+
+bool FilterExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return expr->isSensitiveTo(aContext) ||
+ PredicateList::isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void FilterExpr::toString(nsAString& str) {
+ if (expr)
+ expr->toString(str);
+ else
+ str.AppendLiteral("null");
+ PredicateList::toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txForwardContext.cpp b/dom/xslt/xpath/txForwardContext.cpp
new file mode 100644
index 0000000000..44b53fefbc
--- /dev/null
+++ b/dom/xslt/xpath/txForwardContext.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "txForwardContext.h"
+#include "txNodeSet.h"
+
+const txXPathNode& txForwardContext::getContextNode() { return mContextNode; }
+
+uint32_t txForwardContext::size() { return (uint32_t)mContextSet->size(); }
+
+uint32_t txForwardContext::position() {
+ int32_t pos = mContextSet->indexOf(mContextNode);
+ NS_ASSERTION(pos >= 0, "Context is not member of context node list.");
+ return (uint32_t)(pos + 1);
+}
+
+nsresult txForwardContext::getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getVariable(aNamespace, aLName, aResult);
+}
+
+nsresult txForwardContext::isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->isStripSpaceAllowed(aNode, aAllowed);
+}
+
+void* txForwardContext::getPrivateContext() {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getPrivateContext();
+}
+
+txResultRecycler* txForwardContext::recycler() {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->recycler();
+}
+
+void txForwardContext::receiveError(const nsAString& aMsg, nsresult aRes) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+#ifdef DEBUG
+ nsAutoString error(u"forwarded error: "_ns);
+ error.Append(aMsg);
+ mInner->receiveError(error, aRes);
+#else
+ mInner->receiveError(aMsg, aRes);
+#endif
+}
diff --git a/dom/xslt/xpath/txForwardContext.h b/dom/xslt/xpath/txForwardContext.h
new file mode 100644
index 0000000000..447f2fbfc1
--- /dev/null
+++ b/dom/xslt/xpath/txForwardContext.h
@@ -0,0 +1,28 @@
+/* -*- 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/. */
+
+#ifndef __TX_XPATH_CONTEXT
+#define __TX_XPATH_CONTEXT
+
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+
+class txForwardContext : public txIEvalContext {
+ public:
+ txForwardContext(txIMatchContext* aContext, const txXPathNode& aContextNode,
+ txNodeSet* aContextNodeSet)
+ : mInner(aContext),
+ mContextNode(aContextNode),
+ mContextSet(aContextNodeSet) {}
+
+ TX_DECL_EVAL_CONTEXT;
+
+ private:
+ txIMatchContext* mInner;
+ const txXPathNode& mContextNode;
+ RefPtr<txNodeSet> mContextSet;
+};
+
+#endif // __TX_XPATH_CONTEXT
diff --git a/dom/xslt/xpath/txFunctionCall.cpp b/dom/xslt/xpath/txFunctionCall.cpp
new file mode 100644
index 0000000000..17a325e2ad
--- /dev/null
+++ b/dom/xslt/xpath/txFunctionCall.cpp
@@ -0,0 +1,110 @@
+/* -*- 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 "txExpr.h"
+#include "nsAtom.h"
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+
+#ifdef TX_TO_STRING
+# include "nsReadableUtils.h"
+#endif
+
+/**
+ * This class represents a FunctionCall as defined by the XSL Working Draft
+ **/
+
+//------------------/
+//- Public Methods -/
+//------------------/
+
+/*
+ * Evaluates the given Expression and converts its result to a number.
+ */
+// static
+nsresult FunctionCall::evaluateToNumber(Expr* aExpr, txIEvalContext* aContext,
+ double* aResult) {
+ NS_ASSERTION(aExpr, "missing expression");
+ RefPtr<txAExprResult> exprResult;
+ nsresult rv = aExpr->evaluate(aContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = exprResult->numberValue();
+
+ return NS_OK;
+}
+
+/*
+ * Evaluates the given Expression and converts its result to a NodeSet.
+ * If the result is not a NodeSet nullptr is returned.
+ */
+nsresult FunctionCall::evaluateToNodeSet(Expr* aExpr, txIEvalContext* aContext,
+ txNodeSet** aResult) {
+ NS_ASSERTION(aExpr, "Missing expression to evaluate");
+ *aResult = nullptr;
+
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = aExpr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exprRes->getResultType() != txAExprResult::NODESET) {
+ aContext->receiveError(u"NodeSet expected as argument"_ns,
+ NS_ERROR_XSLT_NODESET_EXPECTED);
+ return NS_ERROR_XSLT_NODESET_EXPECTED;
+ }
+
+ *aResult = static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes));
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+bool FunctionCall::requireParams(int32_t aParamCountMin, int32_t aParamCountMax,
+ txIEvalContext* aContext) {
+ int32_t argc = mParams.Length();
+ if (argc < aParamCountMin || (aParamCountMax > -1 && argc > aParamCountMax)) {
+ nsAutoString err(u"invalid number of parameters for function"_ns);
+#ifdef TX_TO_STRING
+ err.AppendLiteral(": ");
+ toString(err);
+#endif
+ aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG);
+
+ return false;
+ }
+
+ return true;
+}
+
+Expr* FunctionCall::getSubExprAt(uint32_t aPos) {
+ return mParams.SafeElementAt(aPos);
+}
+
+void FunctionCall::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ NS_ASSERTION(aPos < mParams.Length(), "setting bad subexpression index");
+ mParams[aPos] = aExpr;
+}
+
+bool FunctionCall::argsSensitiveTo(ContextSensitivity aContext) {
+ uint32_t i, len = mParams.Length();
+ for (i = 0; i < len; ++i) {
+ if (mParams[i]->isSensitiveTo(aContext)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void FunctionCall::toString(nsAString& aDest) {
+ appendName(aDest);
+ aDest.AppendLiteral("(");
+ StringJoinAppend(
+ aDest, u","_ns, mParams,
+ [](nsAString& dest, const auto& param) { param->toString(dest); });
+ aDest.Append(char16_t(')'));
+}
+#endif
diff --git a/dom/xslt/xpath/txIXPathContext.h b/dom/xslt/xpath/txIXPathContext.h
new file mode 100644
index 0000000000..8d5b5df4be
--- /dev/null
+++ b/dom/xslt/xpath/txIXPathContext.h
@@ -0,0 +1,132 @@
+/* -*- 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/. */
+
+#ifndef __TX_I_XPATH_CONTEXT
+#define __TX_I_XPATH_CONTEXT
+
+#include "nscore.h"
+#include "nsISupportsImpl.h"
+#include "nsStringFwd.h"
+
+class FunctionCall;
+class nsAtom;
+class txAExprResult;
+class txResultRecycler;
+class txXPathNode;
+
+/*
+ * txIParseContext
+ *
+ * This interface describes the context needed to create
+ * XPath Expressions and XSLT Patters.
+ * (not completely though. key() requires the ProcessorState, which is
+ * not part of this interface.)
+ */
+
+class txIParseContext {
+ public:
+ virtual ~txIParseContext() = default;
+
+ /*
+ * Return a namespaceID for a given prefix.
+ */
+ virtual nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) = 0;
+
+ /*
+ * Create a FunctionCall, needed for extension function calls and
+ * XSLT. XPath function calls are resolved by the Parser.
+ */
+ virtual nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
+ FunctionCall** aFunction) = 0;
+
+ /**
+ * Should nametests parsed in this context be case-sensitive
+ */
+ virtual bool caseInsensitiveNameTests() = 0;
+
+ /*
+ * Callback to be used by the Parser if errors are detected.
+ */
+ virtual void SetErrorOffset(uint32_t aOffset) = 0;
+
+ enum Allowed { KEY_FUNCTION = 1 << 0 };
+ virtual bool allowed(Allowed aAllowed) { return true; }
+};
+
+/*
+ * txIMatchContext
+ *
+ * Interface used for matching XSLT Patters.
+ * This is the part of txIEvalContext (see below), that is independent
+ * of the context node when evaluating a XPath expression, too.
+ * When evaluating a XPath expression, |txIMatchContext|s are used
+ * to transport the information from Step to Step.
+ */
+class txIMatchContext {
+ public:
+ virtual ~txIMatchContext() = default;
+
+ /*
+ * Return the ExprResult associated with the variable with the
+ * given namespace and local name.
+ */
+ virtual nsresult getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) = 0;
+
+ /*
+ * Is whitespace stripping allowed for the given node?
+ * See http://www.w3.org/TR/xslt#strip
+ */
+ virtual nsresult isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) = 0;
+
+ /**
+ * Returns a pointer to the private context
+ */
+ virtual void* getPrivateContext() = 0;
+
+ virtual txResultRecycler* recycler() = 0;
+
+ /*
+ * Callback to be used by the expression/pattern if errors are detected.
+ */
+ virtual void receiveError(const nsAString& aMsg, nsresult aRes) = 0;
+};
+
+#define TX_DECL_MATCH_CONTEXT \
+ nsresult getVariable(int32_t aNamespace, nsAtom* aLName, \
+ txAExprResult*& aResult) override; \
+ nsresult isStripSpaceAllowed(const txXPathNode& aNode, bool& aAllowed) \
+ override; \
+ void* getPrivateContext() override; \
+ txResultRecycler* recycler() override; \
+ void receiveError(const nsAString& aMsg, nsresult aRes) override
+
+class txIEvalContext : public txIMatchContext {
+ public:
+ /*
+ * Get the context node.
+ */
+ virtual const txXPathNode& getContextNode() = 0;
+
+ /*
+ * Get the size of the context node set.
+ */
+ virtual uint32_t size() = 0;
+
+ /*
+ * Get the position of the context node in the context node set,
+ * starting with 1.
+ */
+ virtual uint32_t position() = 0;
+};
+
+#define TX_DECL_EVAL_CONTEXT \
+ TX_DECL_MATCH_CONTEXT; \
+ const txXPathNode& getContextNode() override; \
+ uint32_t size() override; \
+ uint32_t position() override
+
+#endif // __TX_I_XPATH_CONTEXT
diff --git a/dom/xslt/xpath/txLiteralExpr.cpp b/dom/xslt/xpath/txLiteralExpr.cpp
new file mode 100644
index 0000000000..93ea2c8f1d
--- /dev/null
+++ b/dom/xslt/xpath/txLiteralExpr.cpp
@@ -0,0 +1,74 @@
+/* -*- 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 "txExpr.h"
+
+nsresult txLiteralExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ NS_ENSURE_TRUE(mValue, NS_ERROR_OUT_OF_MEMORY);
+
+ *aResult = mValue;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+static Expr::ResultType resultTypes[] = {
+ Expr::NODESET_RESULT, // NODESET
+ Expr::BOOLEAN_RESULT, // BOOLEAN
+ Expr::NUMBER_RESULT, // NUMBER
+ Expr::STRING_RESULT, // STRING
+ Expr::RTF_RESULT // RESULT_TREE_FRAGMENT
+};
+
+Expr::ResultType txLiteralExpr::getReturnType() {
+ return resultTypes[mValue->getResultType()];
+}
+
+Expr* txLiteralExpr::getSubExprAt(uint32_t aPos) { return nullptr; }
+void txLiteralExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ MOZ_ASSERT_UNREACHABLE("setting bad subexpression index");
+}
+
+bool txLiteralExpr::isSensitiveTo(ContextSensitivity aContext) { return false; }
+
+#ifdef TX_TO_STRING
+void txLiteralExpr::toString(nsAString& aStr) {
+ switch (mValue->getResultType()) {
+ case txAExprResult::NODESET: {
+ aStr.AppendLiteral(" { Nodeset literal } ");
+ return;
+ }
+ case txAExprResult::BOOLEAN: {
+ if (mValue->booleanValue()) {
+ aStr.AppendLiteral("true()");
+ } else {
+ aStr.AppendLiteral("false()");
+ }
+ return;
+ }
+ case txAExprResult::NUMBER: {
+ txDouble::toString(mValue->numberValue(), aStr);
+ return;
+ }
+ case txAExprResult::STRING: {
+ StringResult* strRes =
+ static_cast<StringResult*>(static_cast<txAExprResult*>(mValue));
+ char16_t ch = '\'';
+ if (strRes->mValue.Contains(ch)) {
+ ch = '\"';
+ }
+ aStr.Append(ch);
+ aStr.Append(strRes->mValue);
+ aStr.Append(ch);
+ return;
+ }
+ case txAExprResult::RESULT_TREE_FRAGMENT: {
+ aStr.AppendLiteral(" { RTF literal } ");
+ return;
+ }
+ }
+}
+#endif
diff --git a/dom/xslt/xpath/txLocationStep.cpp b/dom/xslt/xpath/txLocationStep.cpp
new file mode 100644
index 0000000000..c10d9e8cb1
--- /dev/null
+++ b/dom/xslt/xpath/txLocationStep.cpp
@@ -0,0 +1,308 @@
+/* -*- 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/. */
+
+/*
+ Implementation of an XPath LocationStep
+*/
+
+#include "txExpr.h"
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+#include "txXPathTreeWalker.h"
+
+//-----------------------------/
+//- Virtual methods from Expr -/
+//-----------------------------/
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ProcessorState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ * @see Expr
+ **/
+nsresult LocationStep::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ NS_ASSERTION(aContext, "internal error");
+ *aResult = nullptr;
+
+ RefPtr<txNodeSet> nodes;
+ nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathTreeWalker walker(aContext->getContextNode());
+
+ switch (mAxisIdentifier) {
+ case ANCESTOR_AXIS: {
+ if (!walker.moveToParent()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+ case ANCESTOR_OR_SELF_AXIS: {
+ nodes->setReverse();
+
+ do {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToParent());
+
+ break;
+ }
+ case ATTRIBUTE_AXIS: {
+ if (!walker.moveToFirstAttribute()) {
+ break;
+ }
+
+ do {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToNextAttribute());
+ break;
+ }
+ case DESCENDANT_OR_SELF_AXIS: {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ [[fallthrough]];
+ }
+ case DESCENDANT_AXIS: {
+ rv = appendMatchingDescendants(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case FOLLOWING_AXIS: {
+ if (txXPathNodeUtils::isAttribute(walker.getCurrentPosition())) {
+ walker.moveToParent();
+ rv = appendMatchingDescendants(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ bool cont = true;
+ while (!walker.moveToNextSibling()) {
+ if (!walker.moveToParent()) {
+ cont = false;
+ break;
+ }
+ }
+ while (cont) {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appendMatchingDescendants(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (!walker.moveToNextSibling()) {
+ if (!walker.moveToParent()) {
+ cont = false;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case FOLLOWING_SIBLING_AXIS: {
+ while (walker.moveToNextSibling()) {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ case NAMESPACE_AXIS: //-- not yet implemented
+#if 0
+ // XXX DEBUG OUTPUT
+ cout << "namespace axis not yet implemented"<<endl;
+#endif
+ break;
+ case PARENT_AXIS: {
+ if (walker.moveToParent()) {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ case PRECEDING_AXIS: {
+ nodes->setReverse();
+
+ bool cont = true;
+ while (!walker.moveToPreviousSibling()) {
+ if (!walker.moveToParent()) {
+ cont = false;
+ break;
+ }
+ }
+ while (cont) {
+ rv = appendMatchingDescendantsRev(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (!walker.moveToPreviousSibling()) {
+ if (!walker.moveToParent()) {
+ cont = false;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case PRECEDING_SIBLING_AXIS: {
+ nodes->setReverse();
+
+ while (walker.moveToPreviousSibling()) {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ case SELF_AXIS: {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ default: // Children Axis
+ {
+ if (!walker.moveToFirstChild()) {
+ break;
+ }
+
+ do {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToNextSibling());
+ break;
+ }
+ }
+
+ // Apply predicates
+ if (!isEmpty()) {
+ rv = evaluatePredicates(nodes, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nodes->unsetReverse();
+
+ NS_ADDREF(*aResult = nodes);
+
+ return NS_OK;
+}
+
+nsresult LocationStep::appendIfMatching(const txXPathTreeWalker& aWalker,
+ txIMatchContext* aContext,
+ txNodeSet* aNodes) {
+ bool matched;
+ const txXPathNode& child = aWalker.getCurrentPosition();
+ nsresult rv = mNodeTest->matches(child, aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ aNodes->append(child);
+ }
+ return NS_OK;
+}
+
+nsresult LocationStep::appendMatchingDescendants(
+ const txXPathTreeWalker& aWalker, txIMatchContext* aContext,
+ txNodeSet* aNodes) {
+ txXPathTreeWalker walker(aWalker);
+ if (!walker.moveToFirstChild()) {
+ return NS_OK;
+ }
+
+ do {
+ nsresult rv = appendIfMatching(walker, aContext, aNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appendMatchingDescendants(walker, aContext, aNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToNextSibling());
+
+ return NS_OK;
+}
+
+nsresult LocationStep::appendMatchingDescendantsRev(
+ const txXPathTreeWalker& aWalker, txIMatchContext* aContext,
+ txNodeSet* aNodes) {
+ txXPathTreeWalker walker(aWalker);
+ if (!walker.moveToLastChild()) {
+ return NS_OK;
+ }
+
+ do {
+ nsresult rv = appendMatchingDescendantsRev(walker, aContext, aNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appendIfMatching(walker, aContext, aNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToPreviousSibling());
+
+ return NS_OK;
+}
+
+Expr::ExprType LocationStep::getType() { return LOCATIONSTEP_EXPR; }
+
+TX_IMPL_EXPR_STUBS_BASE(LocationStep, NODESET_RESULT)
+
+Expr* LocationStep::getSubExprAt(uint32_t aPos) {
+ return PredicateList::getSubExprAt(aPos);
+}
+
+void LocationStep::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ PredicateList::setSubExprAt(aPos, aExpr);
+}
+
+bool LocationStep::isSensitiveTo(ContextSensitivity aContext) {
+ return (aContext & NODE_CONTEXT) || mNodeTest->isSensitiveTo(aContext) ||
+ PredicateList::isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void LocationStep::toString(nsAString& str) {
+ switch (mAxisIdentifier) {
+ case ANCESTOR_AXIS:
+ str.AppendLiteral("ancestor::");
+ break;
+ case ANCESTOR_OR_SELF_AXIS:
+ str.AppendLiteral("ancestor-or-self::");
+ break;
+ case ATTRIBUTE_AXIS:
+ str.Append(char16_t('@'));
+ break;
+ case DESCENDANT_AXIS:
+ str.AppendLiteral("descendant::");
+ break;
+ case DESCENDANT_OR_SELF_AXIS:
+ str.AppendLiteral("descendant-or-self::");
+ break;
+ case FOLLOWING_AXIS:
+ str.AppendLiteral("following::");
+ break;
+ case FOLLOWING_SIBLING_AXIS:
+ str.AppendLiteral("following-sibling::");
+ break;
+ case NAMESPACE_AXIS:
+ str.AppendLiteral("namespace::");
+ break;
+ case PARENT_AXIS:
+ str.AppendLiteral("parent::");
+ break;
+ case PRECEDING_AXIS:
+ str.AppendLiteral("preceding::");
+ break;
+ case PRECEDING_SIBLING_AXIS:
+ str.AppendLiteral("preceding-sibling::");
+ break;
+ case SELF_AXIS:
+ str.AppendLiteral("self::");
+ break;
+ default:
+ break;
+ }
+ NS_ASSERTION(mNodeTest, "mNodeTest is null, that's verboten");
+ mNodeTest->toString(str);
+
+ PredicateList::toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp b/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp
new file mode 100644
index 0000000000..8150d7307e
--- /dev/null
+++ b/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp
@@ -0,0 +1,674 @@
+/* -*- 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 <stdint.h>
+#include <algorithm>
+
+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<nsIContent> 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<nsIContent*>(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<nsAtom> txXPathNodeUtils::getLocalName(
+ const txXPathNode& aNode) {
+ if (aNode.isDocument()) {
+ return nullptr;
+ }
+
+ if (aNode.isContent()) {
+ if (aNode.mNode->IsElement()) {
+ RefPtr<nsAtom> 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<nsAtom> 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<CharacterData*>(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<nsINode*, 8> 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<uint32_t> indexOfNode = parent->ComputeIndexOf(node);
+ const Maybe<uint32_t> 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<uint32_t> index = parent->ComputeIndexOf(node);
+ const Maybe<uint32_t> 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<Attr*>(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> 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();
+}
diff --git a/dom/xslt/xpath/txNameTest.cpp b/dom/xslt/xpath/txNameTest.cpp
new file mode 100644
index 0000000000..f76113146c
--- /dev/null
+++ b/dom/xslt/xpath/txNameTest.cpp
@@ -0,0 +1,92 @@
+/* -*- 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 "txExpr.h"
+#include "nsAtom.h"
+#include "nsGkAtoms.h"
+#include "txXPathTreeWalker.h"
+#include "txIXPathContext.h"
+
+txNameTest::txNameTest(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID,
+ uint16_t aNodeType)
+ : mPrefix(aPrefix),
+ mLocalName(aLocalName),
+ mNamespace(aNSID),
+ mNodeType(aNodeType) {
+ if (aPrefix == nsGkAtoms::_empty) mPrefix = nullptr;
+ NS_ASSERTION(aLocalName, "txNameTest without a local name?");
+ NS_ASSERTION(aNodeType == txXPathNodeType::DOCUMENT_NODE ||
+ aNodeType == txXPathNodeType::ELEMENT_NODE ||
+ aNodeType == txXPathNodeType::ATTRIBUTE_NODE,
+ "Go fix txNameTest::matches");
+}
+
+nsresult txNameTest::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ if ((mNodeType == txXPathNodeType::ELEMENT_NODE &&
+ !txXPathNodeUtils::isElement(aNode)) ||
+ (mNodeType == txXPathNodeType::ATTRIBUTE_NODE &&
+ !txXPathNodeUtils::isAttribute(aNode)) ||
+ (mNodeType == txXPathNodeType::DOCUMENT_NODE &&
+ !txXPathNodeUtils::isRoot(aNode))) {
+ aMatched = false;
+ return NS_OK;
+ }
+
+ // Totally wild?
+ if (mLocalName == nsGkAtoms::_asterisk && !mPrefix) {
+ aMatched = true;
+ return NS_OK;
+ }
+
+ // Compare namespaces
+ if (mNamespace != txXPathNodeUtils::getNamespaceID(aNode) &&
+ !(mNamespace == kNameSpaceID_None &&
+ txXPathNodeUtils::isHTMLElementInHTMLDocument(aNode))) {
+ aMatched = false;
+ return NS_OK;
+ }
+
+ // Name wild?
+ if (mLocalName == nsGkAtoms::_asterisk) {
+ aMatched = true;
+ return NS_OK;
+ }
+
+ // Compare local-names
+ aMatched = txXPathNodeUtils::localNameEquals(aNode, mLocalName);
+ return NS_OK;
+}
+
+/*
+ * Returns the default priority of this txNodeTest
+ */
+double txNameTest::getDefaultPriority() {
+ if (mLocalName == nsGkAtoms::_asterisk) {
+ if (!mPrefix) return -0.5;
+ return -0.25;
+ }
+ return 0;
+}
+
+txNodeTest::NodeTestType txNameTest::getType() { return NAME_TEST; }
+
+bool txNameTest::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ return !!(aContext & Expr::NODE_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void txNameTest::toString(nsAString& aDest) {
+ if (mPrefix) {
+ nsAutoString prefix;
+ mPrefix->ToString(prefix);
+ aDest.Append(prefix);
+ aDest.Append(char16_t(':'));
+ }
+ nsAutoString localName;
+ mLocalName->ToString(localName);
+ aDest.Append(localName);
+}
+#endif
diff --git a/dom/xslt/xpath/txNamedAttributeStep.cpp b/dom/xslt/xpath/txNamedAttributeStep.cpp
new file mode 100644
index 0000000000..18938e1dd3
--- /dev/null
+++ b/dom/xslt/xpath/txNamedAttributeStep.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "nsAtom.h"
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+#include "txExpr.h"
+#include "txXPathTreeWalker.h"
+
+txNamedAttributeStep::txNamedAttributeStep(int32_t aNsID, nsAtom* aPrefix,
+ nsAtom* aLocalName)
+ : mNamespace(aNsID), mPrefix(aPrefix), mLocalName(aLocalName) {}
+
+nsresult txNamedAttributeStep::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ RefPtr<txNodeSet> nodes;
+ nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathTreeWalker walker(aContext->getContextNode());
+ if (walker.moveToNamedAttribute(mLocalName, mNamespace)) {
+ rv = nodes->append(walker.getCurrentPosition());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ NS_ADDREF(*aResult = nodes);
+
+ return NS_OK;
+}
+
+TX_IMPL_EXPR_STUBS_0(txNamedAttributeStep, NODESET_RESULT)
+
+bool txNamedAttributeStep::isSensitiveTo(ContextSensitivity aContext) {
+ return !!(aContext & NODE_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void txNamedAttributeStep::toString(nsAString& aDest) {
+ aDest.Append(char16_t('@'));
+ if (mPrefix) {
+ nsAutoString prefix;
+ mPrefix->ToString(prefix);
+ aDest.Append(prefix);
+ aDest.Append(char16_t(':'));
+ }
+ nsAutoString localName;
+ mLocalName->ToString(localName);
+ aDest.Append(localName);
+}
+#endif
diff --git a/dom/xslt/xpath/txNodeSet.cpp b/dom/xslt/xpath/txNodeSet.cpp
new file mode 100644
index 0000000000..60eed8952d
--- /dev/null
+++ b/dom/xslt/xpath/txNodeSet.cpp
@@ -0,0 +1,567 @@
+/* -*- 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 "txNodeSet.h"
+#include "txLog.h"
+#include "txXPathTreeWalker.h"
+#include <algorithm>
+
+/**
+ * Implementation of an XPath nodeset
+ */
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+# define LOG_CHUNK_MOVE(_start, _new_start, _count) \
+ { \
+ txXPathNode* start = const_cast<txXPathNode*>(_start); \
+ while (start < _start + _count) { \
+ NS_LogDtor(start, "txXPathNode", sizeof(*start)); \
+ ++start; \
+ } \
+ start = const_cast<txXPathNode*>(_new_start); \
+ while (start < _new_start + _count) { \
+ NS_LogCtor(start, "txXPathNode", sizeof(*start)); \
+ ++start; \
+ } \
+ }
+#else
+# define LOG_CHUNK_MOVE(_start, _new_start, _count)
+#endif
+
+static const int32_t kTxNodeSetMinSize = 4;
+static const int32_t kTxNodeSetGrowFactor = 2;
+
+#define kForward 1
+#define kReversed -1
+
+txNodeSet::txNodeSet(txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler),
+ mStart(nullptr),
+ mEnd(nullptr),
+ mStartBuffer(nullptr),
+ mEndBuffer(nullptr),
+ mDirection(kForward),
+ mMarks(nullptr) {}
+
+txNodeSet::txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler),
+ mStart(nullptr),
+ mEnd(nullptr),
+ mStartBuffer(nullptr),
+ mEndBuffer(nullptr),
+ mDirection(kForward),
+ mMarks(nullptr) {
+ if (!ensureGrowSize(1)) {
+ return;
+ }
+
+ new (mStart) txXPathNode(aNode);
+ ++mEnd;
+}
+
+txNodeSet::txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler),
+ mStart(nullptr),
+ mEnd(nullptr),
+ mStartBuffer(nullptr),
+ mEndBuffer(nullptr),
+ mDirection(kForward),
+ mMarks(nullptr) {
+ append(aSource);
+}
+
+txNodeSet::~txNodeSet() {
+ delete[] mMarks;
+
+ if (mStartBuffer) {
+ destroyElements(mStart, mEnd);
+
+ free(mStartBuffer);
+ }
+}
+
+nsresult txNodeSet::add(const txXPathNode& aNode) {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+
+ if (isEmpty()) {
+ return append(aNode);
+ }
+
+ bool dupe;
+ txXPathNode* pos = findPosition(aNode, mStart, mEnd, dupe);
+
+ if (dupe) {
+ return NS_OK;
+ }
+
+ // save pos, ensureGrowSize messes with the pointers
+ int32_t moveSize = mEnd - pos;
+ int32_t offset = pos - mStart;
+ if (!ensureGrowSize(1)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // set pos to where it was
+ pos = mStart + offset;
+
+ if (moveSize > 0) {
+ LOG_CHUNK_MOVE(pos, pos + 1, moveSize);
+ memmove(pos + 1, pos, moveSize * sizeof(txXPathNode));
+ }
+
+ new (pos) txXPathNode(aNode);
+ ++mEnd;
+
+ return NS_OK;
+}
+
+nsresult txNodeSet::add(const txNodeSet& aNodes) {
+ return add(aNodes, copyElements, nullptr);
+}
+
+nsresult txNodeSet::addAndTransfer(txNodeSet* aNodes) {
+ // failure is out-of-memory, transfer didn't happen
+ nsresult rv = add(*aNodes, transferElements, destroyElements);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef TX_DONT_RECYCLE_BUFFER
+ if (aNodes->mStartBuffer) {
+ free(aNodes->mStartBuffer);
+ aNodes->mStartBuffer = aNodes->mEndBuffer = nullptr;
+ }
+#endif
+ aNodes->mStart = aNodes->mEnd = aNodes->mStartBuffer;
+
+ return NS_OK;
+}
+
+/**
+ * add(aNodeSet, aTransferOp)
+ *
+ * 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
+ *
+ *
+ * When merging these nodesets into the result, the nodes are transfered
+ * in chunks to the end of the buffer so that each chunk does not contain
+ * a node from the other nodeset, in document order.
+ *
+ * We select the last non-transfered node in the first 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 transfer them to the end of the resulting nodeset. Which in this case
+ * means that we first transfered the 8 and 9 nodes, giving us the following:
+ *
+ * 1 3 7 <- source 1
+ * 2 3 6 <- source 2
+ * _ _ _ _ _ _ 8 9 <- result
+ *
+ * The corresponding procedure is done for the second nodeset, that is
+ * the insertion position of the 6 in the first nodeset is found, which
+ * is between the 3 and the 7. The 7 is memmoved (as it stays within
+ * the same nodeset) to the result buffer.
+ *
+ * As the result buffer is filled from the end, it is safe to share the
+ * buffer between this nodeset and the result.
+ *
+ * This is repeated until both of the nodesets are empty.
+ *
+ * If we find a duplicate node when searching for where insertposition we
+ * check for sequences of duplicate nodes, which can be optimized.
+ *
+ */
+nsresult txNodeSet::add(const txNodeSet& aNodes, transferOp aTransfer,
+ destroyOp aDestroy) {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+
+ if (aNodes.isEmpty()) {
+ return NS_OK;
+ }
+
+ if (!ensureGrowSize(aNodes.size())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // This is probably a rather common case, so lets try to shortcut.
+ if (mStart == mEnd ||
+ txXPathNodeUtils::comparePosition(mEnd[-1], *aNodes.mStart) < 0) {
+ aTransfer(mEnd, aNodes.mStart, aNodes.mEnd);
+ mEnd += aNodes.size();
+
+ return NS_OK;
+ }
+
+ // Last element in this nodeset
+ txXPathNode* thisPos = mEnd;
+
+ // Last element of the other nodeset
+ txXPathNode* otherPos = aNodes.mEnd;
+
+ // Pointer to the insertion point in this nodeset
+ txXPathNode* insertPos = mEndBuffer;
+
+ bool dupe;
+ txXPathNode* pos;
+ int32_t count;
+ while (thisPos > mStart || otherPos > aNodes.mStart) {
+ // Find where the last remaining node of this nodeset would
+ // be inserted in the other nodeset.
+ if (thisPos > mStart) {
+ pos = findPosition(thisPos[-1], aNodes.mStart, otherPos, dupe);
+
+ if (dupe) {
+ const txXPathNode* deletePos = thisPos;
+ --thisPos; // this is already added
+ // check dupe sequence
+ while (thisPos > mStart && pos > aNodes.mStart &&
+ thisPos[-1] == pos[-1]) {
+ --thisPos;
+ --pos;
+ }
+
+ if (aDestroy) {
+ aDestroy(thisPos, deletePos);
+ }
+ }
+ } else {
+ pos = aNodes.mStart;
+ }
+
+ // Transfer the otherNodes after the insertion point to the result
+ count = otherPos - pos;
+ if (count > 0) {
+ insertPos -= count;
+ aTransfer(insertPos, pos, otherPos);
+ otherPos -= count;
+ }
+
+ // Find where the last remaining node of the otherNodeset would
+ // be inserted in this nodeset.
+ if (otherPos > aNodes.mStart) {
+ pos = findPosition(otherPos[-1], mStart, thisPos, dupe);
+
+ if (dupe) {
+ const txXPathNode* deletePos = otherPos;
+ --otherPos; // this is already added
+ // check dupe sequence
+ while (otherPos > aNodes.mStart && pos > mStart &&
+ otherPos[-1] == pos[-1]) {
+ --otherPos;
+ --pos;
+ }
+
+ if (aDestroy) {
+ aDestroy(otherPos, deletePos);
+ }
+ }
+ } else {
+ pos = mStart;
+ }
+
+ // Move the nodes from this nodeset after the insertion point
+ // to the result
+ count = thisPos - pos;
+ if (count > 0) {
+ insertPos -= count;
+ LOG_CHUNK_MOVE(pos, insertPos, count);
+ memmove(insertPos, pos, count * sizeof(txXPathNode));
+ thisPos -= count;
+ }
+ }
+ mStart = insertPos;
+ mEnd = mEndBuffer;
+
+ 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
+ * order info operations will be performed.
+ */
+
+nsresult txNodeSet::append(const txXPathNode& aNode) {
+ if (!ensureGrowSize(1)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mDirection == kForward) {
+ new (mEnd) txXPathNode(aNode);
+ ++mEnd;
+
+ return NS_OK;
+ }
+
+ new (--mStart) txXPathNode(aNode);
+
+ return NS_OK;
+}
+
+nsresult txNodeSet::append(const txNodeSet& aNodes) {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+
+ if (aNodes.isEmpty()) {
+ return NS_OK;
+ }
+
+ int32_t appended = aNodes.size();
+ if (!ensureGrowSize(appended)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ copyElements(mEnd, aNodes.mStart, aNodes.mEnd);
+ mEnd += appended;
+
+ return NS_OK;
+}
+
+nsresult txNodeSet::mark(int32_t aIndex) {
+ NS_ASSERTION(aIndex >= 0 && mStart && mEnd - mStart > aIndex,
+ "index out of bounds");
+ if (!mMarks) {
+ int32_t length = size();
+ mMarks = new bool[length];
+ memset(mMarks, 0, length * sizeof(bool));
+ }
+ if (mDirection == kForward) {
+ mMarks[aIndex] = true;
+ } else {
+ mMarks[size() - aIndex - 1] = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult txNodeSet::sweep() {
+ if (!mMarks) {
+ // sweep everything
+ clear();
+ }
+
+ int32_t chunk, pos = 0;
+ int32_t length = size();
+ txXPathNode* insertion = mStartBuffer;
+
+ while (pos < length) {
+ while (pos < length && !mMarks[pos]) {
+ // delete unmarked
+ mStart[pos].~txXPathNode();
+ ++pos;
+ }
+ // find chunk to move
+ chunk = 0;
+ while (pos < length && mMarks[pos]) {
+ ++pos;
+ ++chunk;
+ }
+ // move chunk
+ if (chunk > 0) {
+ LOG_CHUNK_MOVE(mStart + pos - chunk, insertion, chunk);
+ memmove(insertion, mStart + pos - chunk, chunk * sizeof(txXPathNode));
+ insertion += chunk;
+ }
+ }
+ mStart = mStartBuffer;
+ mEnd = insertion;
+ delete[] mMarks;
+ mMarks = nullptr;
+
+ return NS_OK;
+}
+
+void txNodeSet::clear() {
+ destroyElements(mStart, mEnd);
+#ifdef TX_DONT_RECYCLE_BUFFER
+ if (mStartBuffer) {
+ free(mStartBuffer);
+ mStartBuffer = mEndBuffer = nullptr;
+ }
+#endif
+ mStart = mEnd = mStartBuffer;
+ delete[] mMarks;
+ mMarks = nullptr;
+ mDirection = kForward;
+}
+
+int32_t txNodeSet::indexOf(const txXPathNode& aNode, uint32_t aStart) const {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+
+ if (!mStart || mStart == mEnd) {
+ return -1;
+ }
+
+ txXPathNode* pos = mStart + aStart;
+ for (; pos < mEnd; ++pos) {
+ if (*pos == aNode) {
+ return pos - mStart;
+ }
+ }
+
+ return -1;
+}
+
+const txXPathNode& txNodeSet::get(int32_t aIndex) const {
+ if (mDirection == kForward) {
+ return mStart[aIndex];
+ }
+
+ return mEnd[-aIndex - 1];
+}
+
+short txNodeSet::getResultType() { return txAExprResult::NODESET; }
+
+bool txNodeSet::booleanValue() { return !isEmpty(); }
+double txNodeSet::numberValue() {
+ nsAutoString str;
+ stringValue(str);
+
+ return txDouble::toDouble(str);
+}
+
+void txNodeSet::stringValue(nsString& aStr) {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+ if (isEmpty()) {
+ return;
+ }
+ txXPathNodeUtils::appendNodeValue(get(0), aStr);
+}
+
+const nsString* txNodeSet::stringValuePointer() { return nullptr; }
+
+bool txNodeSet::ensureGrowSize(int32_t aSize) {
+ // check if there is enough place in the buffer as is
+ if (mDirection == kForward && aSize <= mEndBuffer - mEnd) {
+ return true;
+ }
+
+ if (mDirection == kReversed && aSize <= mStart - mStartBuffer) {
+ return true;
+ }
+
+ // check if we just have to align mStart to have enough space
+ int32_t oldSize = mEnd - mStart;
+ int32_t oldLength = mEndBuffer - mStartBuffer;
+ int32_t ensureSize = oldSize + aSize;
+ if (ensureSize <= oldLength) {
+ // just move the buffer
+ txXPathNode* dest = mStartBuffer;
+ if (mDirection == kReversed) {
+ dest = mEndBuffer - oldSize;
+ }
+ LOG_CHUNK_MOVE(mStart, dest, oldSize);
+ memmove(dest, mStart, oldSize * sizeof(txXPathNode));
+ mStart = dest;
+ mEnd = dest + oldSize;
+
+ return true;
+ }
+
+ // This isn't 100% safe. But until someone manages to make a 1gig nodeset
+ // it should be ok.
+ int32_t newLength = std::max(oldLength, kTxNodeSetMinSize);
+
+ while (newLength < ensureSize) {
+ newLength *= kTxNodeSetGrowFactor;
+ }
+
+ txXPathNode* newArr =
+ static_cast<txXPathNode*>(moz_xmalloc(newLength * sizeof(txXPathNode)));
+
+ txXPathNode* dest = newArr;
+ if (mDirection == kReversed) {
+ dest += newLength - oldSize;
+ }
+
+ if (oldSize > 0) {
+ LOG_CHUNK_MOVE(mStart, dest, oldSize);
+ memcpy(dest, mStart, oldSize * sizeof(txXPathNode));
+ }
+
+ if (mStartBuffer) {
+#ifdef DEBUG
+ memset(mStartBuffer, 0, (mEndBuffer - mStartBuffer) * sizeof(txXPathNode));
+#endif
+ free(mStartBuffer);
+ }
+
+ mStartBuffer = newArr;
+ mEndBuffer = mStartBuffer + newLength;
+ mStart = dest;
+ mEnd = dest + oldSize;
+
+ return true;
+}
+
+txXPathNode* txNodeSet::findPosition(const txXPathNode& aNode,
+ txXPathNode* aFirst, txXPathNode* aLast,
+ bool& aDupe) const {
+ aDupe = false;
+ if (aLast - aFirst <= 2) {
+ // If we search 2 nodes or less there is no point in further divides
+ txXPathNode* pos = aFirst;
+ for (; pos < aLast; ++pos) {
+ int cmp = txXPathNodeUtils::comparePosition(aNode, *pos);
+ if (cmp < 0) {
+ return pos;
+ }
+
+ if (cmp == 0) {
+ aDupe = true;
+
+ return pos;
+ }
+ }
+ return pos;
+ }
+
+ // (cannot add two pointers)
+ txXPathNode* midpos = aFirst + (aLast - aFirst) / 2;
+ int cmp = txXPathNodeUtils::comparePosition(aNode, *midpos);
+ if (cmp == 0) {
+ aDupe = true;
+
+ return midpos;
+ }
+
+ if (cmp > 0) {
+ return findPosition(aNode, midpos + 1, aLast, aDupe);
+ }
+
+ // midpos excluded as end of range
+
+ return findPosition(aNode, aFirst, midpos, aDupe);
+}
+
+/* static */
+void txNodeSet::copyElements(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd) {
+ const txXPathNode* pos = aStart;
+ while (pos < aEnd) {
+ new (aDest) txXPathNode(*pos);
+ ++aDest;
+ ++pos;
+ }
+}
+
+/* static */
+void txNodeSet::transferElements(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd) {
+ LOG_CHUNK_MOVE(aStart, aDest, (aEnd - aStart));
+ memcpy(aDest, aStart, (aEnd - aStart) * sizeof(txXPathNode));
+}
diff --git a/dom/xslt/xpath/txNodeSet.h b/dom/xslt/xpath/txNodeSet.h
new file mode 100644
index 0000000000..8c5e805d6b
--- /dev/null
+++ b/dom/xslt/xpath/txNodeSet.h
@@ -0,0 +1,199 @@
+/* -*- 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/. */
+
+/**
+ * Implementation of an XPath NodeSet
+ */
+
+#ifndef txNodeSet_h__
+#define txNodeSet_h__
+
+#include "txExprResult.h"
+#include "nsError.h"
+#include "txXPathNode.h"
+
+class txNodeSet : public txAExprResult {
+ public:
+ /**
+ * Creates a new empty NodeSet
+ */
+ explicit txNodeSet(txResultRecycler* aRecycler);
+
+ /**
+ * Creates a new NodeSet with one node.
+ */
+ txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler);
+
+ /**
+ * Creates a new txNodeSet, copying the node references from the source
+ * NodeSet.
+ */
+ txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler);
+
+ /**
+ * Destructor for txNodeSet, deletes the nodes.
+ */
+ virtual ~txNodeSet();
+
+ /**
+ * Adds the specified txXPathNode to this NodeSet if it is not already
+ * in this NodeSet. The node is inserted according to document order.
+ *
+ * @param aNode the txXPathNode to add to the NodeSet
+ * @return errorcode.
+ */
+ nsresult add(const txXPathNode& aNode);
+
+ /**
+ * 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 errorcode.
+ */
+ nsresult add(const txNodeSet& aNodes);
+ nsresult addAndTransfer(txNodeSet* aNodes);
+
+ /**
+ * 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
+ * order info operations will be performed.
+ */
+
+ /**
+ * Appends the specified Node to the end of this NodeSet
+ * @param aNode the Node to append to the NodeSet
+ * @return errorcode.
+ */
+ nsresult append(const txXPathNode& aNode);
+
+ /**
+ * Appends the nodes in the specified NodeSet to the end of this NodeSet
+ * @param aNodes the NodeSet to append to the NodeSet
+ * @return errorcode.
+ */
+ nsresult append(const txNodeSet& aNodes);
+
+ /**
+ * API to implement reverse axes in LocationStep.
+ *
+ * Before adding nodes to the nodeset for a reversed axis, call
+ * setReverse(). This will make the append(aNode) and get() methods treat
+ * the nodeset as required. Do only call append(aNode), get(), mark()
+ * and sweep() while the nodeset is reversed.
+ * Afterwards, call unsetReverse(). The nodes are stored in document
+ * order internally.
+ */
+ void setReverse() { mDirection = -1; }
+ void unsetReverse() { mDirection = 1; }
+
+ /**
+ * API to implement predicates in PredicateExpr
+ *
+ * mark(aIndex) marks the specified member of the nodeset.
+ * sweep() clears all members of the nodeset that haven't been
+ * marked before and clear the mMarks array.
+ */
+ nsresult mark(int32_t aIndex);
+ nsresult sweep();
+
+ /**
+ * Removes all nodes from this nodeset
+ */
+ void clear();
+
+ /**
+ * 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
+ * @param aStart index to start searching at
+ * @return index of specified node or -1 if the node does not exist
+ */
+ int32_t indexOf(const txXPathNode& aNode, uint32_t aStart = 0) const;
+
+ /**
+ * Returns true if the specified Node is contained in the set.
+ * @param aNode the Node to search for
+ * @return true if specified Node is contained in the NodeSet
+ */
+ bool contains(const txXPathNode& aNode) const { return indexOf(aNode) >= 0; }
+
+ /**
+ * Returns the Node at the specified node in this NodeSet.
+ * @param aIndex the node of the Node to return
+ * @return Node at specified node
+ */
+ const txXPathNode& get(int32_t aIndex) const;
+
+ /**
+ * Returns true if there are no Nodes in the NodeSet.
+ * @return true if there are no Nodes in the NodeSet.
+ */
+ bool isEmpty() const { return mStart ? mStart == mEnd : true; }
+
+ /**
+ * Returns the number of elements in the NodeSet
+ * @return the number of elements in the NodeSet
+ */
+ int32_t size() const { return mStart ? mEnd - mStart : 0; }
+
+ TX_DECL_EXPRRESULT
+
+ private:
+ /**
+ * Ensure that this nodeset can take another aSize nodes.
+ *
+ * Changes mStart and mEnd as well as mBufferStart and mBufferEnd.
+ */
+ bool ensureGrowSize(int32_t aSize);
+
+ /**
+ * Finds position in the buffer where a node should be inserted
+ * to keep the nodeset in document order. Searches the positions
+ * aFirst-aLast, including aFirst, but not aLast.
+ * @param aNode Node to find insert position for.
+ * @param aFirst First item of the search range, included.
+ * @param aLast Last item of the search range, excluded.
+ * @param aDupe out-param. Will be set to true if the node already
+ * exists in the NodeSet, false if it should be
+ * inserted.
+ * @return pointer where to insert the node. The node should be inserted
+ * before the given node. This value is always set, even if aNode
+ * already exists in the NodeSet
+ */
+ txXPathNode* findPosition(const txXPathNode& aNode, txXPathNode* aFirst,
+ txXPathNode* aLast, bool& aDupe) const;
+
+ static void copyElements(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd);
+ static void transferElements(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd);
+ static void destroyElements(const txXPathNode* aStart,
+ const txXPathNode* aEnd) {
+ while (aStart < aEnd) {
+ aStart->~txXPathNode();
+ ++aStart;
+ }
+ }
+
+ using transferOp = void (*)(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd);
+ using destroyOp = void (*)(const txXPathNode* aStart,
+ const txXPathNode* aEnd);
+ nsresult add(const txNodeSet& aNodes, transferOp aTransfer,
+ destroyOp aDestroy);
+
+ txXPathNode *mStart, *mEnd, *mStartBuffer, *mEndBuffer;
+ int32_t mDirection;
+ // used for mark() and sweep() in predicates
+ bool* mMarks;
+};
+
+#endif
diff --git a/dom/xslt/xpath/txNodeSetContext.cpp b/dom/xslt/xpath/txNodeSetContext.cpp
new file mode 100644
index 0000000000..7565e12545
--- /dev/null
+++ b/dom/xslt/xpath/txNodeSetContext.cpp
@@ -0,0 +1,51 @@
+/* -*- 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 "txNodeSetContext.h"
+#include "txNodeSet.h"
+
+const txXPathNode& txNodeSetContext::getContextNode() {
+ return mContextSet->get(mPosition - 1);
+}
+
+uint32_t txNodeSetContext::size() { return (uint32_t)mContextSet->size(); }
+
+uint32_t txNodeSetContext::position() {
+ NS_ASSERTION(mPosition, "Should have called next() at least once");
+ return mPosition;
+}
+
+nsresult txNodeSetContext::getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getVariable(aNamespace, aLName, aResult);
+}
+
+nsresult txNodeSetContext::isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->isStripSpaceAllowed(aNode, aAllowed);
+}
+
+void* txNodeSetContext::getPrivateContext() {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getPrivateContext();
+}
+
+txResultRecycler* txNodeSetContext::recycler() {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->recycler();
+}
+
+void txNodeSetContext::receiveError(const nsAString& aMsg, nsresult aRes) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+#ifdef DEBUG
+ nsAutoString error(u"forwarded error: "_ns);
+ error.Append(aMsg);
+ mInner->receiveError(error, aRes);
+#else
+ mInner->receiveError(aMsg, aRes);
+#endif
+}
diff --git a/dom/xslt/xpath/txNodeSetContext.h b/dom/xslt/xpath/txNodeSetContext.h
new file mode 100644
index 0000000000..86cb14ab95
--- /dev/null
+++ b/dom/xslt/xpath/txNodeSetContext.h
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+#ifndef __TX_XPATH_SET_CONTEXT
+#define __TX_XPATH_SET_CONTEXT
+
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+
+class txNodeSetContext : public txIEvalContext {
+ public:
+ txNodeSetContext(txNodeSet* aContextNodeSet, txIMatchContext* aContext)
+ : mContextSet(aContextNodeSet), mPosition(0), mInner(aContext) {}
+
+ // Iteration over the given NodeSet
+ bool hasNext() { return mPosition < size(); }
+ void next() {
+ NS_ASSERTION(mPosition < size(), "Out of bounds.");
+ mPosition++;
+ }
+ void setPosition(uint32_t aPosition) {
+ NS_ASSERTION(aPosition > 0 && aPosition <= size(), "Out of bounds.");
+ mPosition = aPosition;
+ }
+
+ TX_DECL_EVAL_CONTEXT;
+
+ protected:
+ RefPtr<txNodeSet> mContextSet;
+ uint32_t mPosition;
+ txIMatchContext* mInner;
+};
+
+#endif // __TX_XPATH_SET_CONTEXT
diff --git a/dom/xslt/xpath/txNodeTypeTest.cpp b/dom/xslt/xpath/txNodeTypeTest.cpp
new file mode 100644
index 0000000000..17d0c60483
--- /dev/null
+++ b/dom/xslt/xpath/txNodeTypeTest.cpp
@@ -0,0 +1,91 @@
+/* -*- 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 "txExpr.h"
+#include "nsAtom.h"
+#include "txIXPathContext.h"
+#include "txXPathTreeWalker.h"
+
+nsresult txNodeTypeTest::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ switch (mNodeType) {
+ case COMMENT_TYPE: {
+ aMatched = txXPathNodeUtils::isComment(aNode);
+ return NS_OK;
+ }
+ case TEXT_TYPE: {
+ aMatched = txXPathNodeUtils::isText(aNode);
+ if (aMatched) {
+ bool allowed;
+ nsresult rv = aContext->isStripSpaceAllowed(aNode, allowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMatched = !allowed;
+ }
+ return NS_OK;
+ }
+ case PI_TYPE: {
+ aMatched =
+ txXPathNodeUtils::isProcessingInstruction(aNode) &&
+ (!mNodeName || txXPathNodeUtils::localNameEquals(aNode, mNodeName));
+ return NS_OK;
+ }
+ case NODE_TYPE: {
+ if (txXPathNodeUtils::isText(aNode)) {
+ bool allowed;
+ nsresult rv = aContext->isStripSpaceAllowed(aNode, allowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMatched = !allowed;
+ } else {
+ aMatched = true;
+ }
+ return NS_OK;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Didn't deal with all values of the NodeType enum!");
+
+ aMatched = false;
+ return NS_OK;
+}
+
+txNodeTest::NodeTestType txNodeTypeTest::getType() { return NODETYPE_TEST; }
+
+/*
+ * Returns the default priority of this txNodeTest
+ */
+double txNodeTypeTest::getDefaultPriority() { return mNodeName ? 0 : -0.5; }
+
+bool txNodeTypeTest::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ return !!(aContext & Expr::NODE_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void txNodeTypeTest::toString(nsAString& aDest) {
+ switch (mNodeType) {
+ case COMMENT_TYPE:
+ aDest.AppendLiteral("comment()");
+ break;
+ case TEXT_TYPE:
+ aDest.AppendLiteral("text()");
+ break;
+ case PI_TYPE:
+ aDest.AppendLiteral("processing-instruction(");
+ if (mNodeName) {
+ nsAutoString str;
+ mNodeName->ToString(str);
+ aDest.Append(char16_t('\''));
+ aDest.Append(str);
+ aDest.Append(char16_t('\''));
+ }
+ aDest.Append(char16_t(')'));
+ break;
+ case NODE_TYPE:
+ aDest.AppendLiteral("node()");
+ break;
+ }
+}
+#endif
diff --git a/dom/xslt/xpath/txNumberExpr.cpp b/dom/xslt/xpath/txNumberExpr.cpp
new file mode 100644
index 0000000000..c3424a91f1
--- /dev/null
+++ b/dom/xslt/xpath/txNumberExpr.cpp
@@ -0,0 +1,108 @@
+/* -*- 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 "mozilla/FloatingPoint.h"
+
+#include "txExpr.h"
+#include <math.h>
+#include "txIXPathContext.h"
+
+nsresult txNumberExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = mRightExpr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double rightDbl = exprRes->numberValue();
+
+ rv = mLeftExpr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double leftDbl = exprRes->numberValue();
+ double result = 0;
+
+ switch (mOp) {
+ case ADD:
+ result = leftDbl + rightDbl;
+ break;
+
+ case SUBTRACT:
+ result = leftDbl - rightDbl;
+ break;
+
+ case DIVIDE:
+ if (rightDbl == 0) {
+#if defined(XP_WIN)
+ /* XXX MSVC miscompiles such that (NaN == 0) */
+ if (std::isnan(rightDbl))
+ result = mozilla::UnspecifiedNaN<double>();
+ else
+#endif
+ if (leftDbl == 0 || std::isnan(leftDbl))
+ result = mozilla::UnspecifiedNaN<double>();
+ else if (mozilla::IsNegative(leftDbl) != mozilla::IsNegative(rightDbl))
+ result = mozilla::NegativeInfinity<double>();
+ else
+ result = mozilla::PositiveInfinity<double>();
+ } else
+ result = leftDbl / rightDbl;
+ break;
+
+ case MODULUS:
+ if (rightDbl == 0) {
+ result = mozilla::UnspecifiedNaN<double>();
+ } else {
+#if defined(XP_WIN)
+ /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */
+ if (!std::isinf(leftDbl) && std::isinf(rightDbl))
+ result = leftDbl;
+ else
+#endif
+ result = fmod(leftDbl, rightDbl);
+ }
+ break;
+
+ case MULTIPLY:
+ result = leftDbl * rightDbl;
+ break;
+ }
+
+ return aContext->recycler()->getNumberResult(result, aResult);
+} //-- evaluate
+
+TX_IMPL_EXPR_STUBS_2(txNumberExpr, NUMBER_RESULT, mLeftExpr, mRightExpr)
+
+bool txNumberExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return mLeftExpr->isSensitiveTo(aContext) ||
+ mRightExpr->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void txNumberExpr::toString(nsAString& str) {
+ mLeftExpr->toString(str);
+
+ switch (mOp) {
+ case ADD:
+ str.AppendLiteral(" + ");
+ break;
+ case SUBTRACT:
+ str.AppendLiteral(" - ");
+ break;
+ case DIVIDE:
+ str.AppendLiteral(" div ");
+ break;
+ case MODULUS:
+ str.AppendLiteral(" mod ");
+ break;
+ case MULTIPLY:
+ str.AppendLiteral(" * ");
+ break;
+ }
+
+ mRightExpr->toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txNumberResult.cpp b/dom/xslt/xpath/txNumberResult.cpp
new file mode 100644
index 0000000000..370a7523d8
--- /dev/null
+++ b/dom/xslt/xpath/txNumberResult.cpp
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+
+/**
+ * NumberResult
+ * Represents the a number as the result of evaluating an Expr
+ **/
+
+#include "mozilla/FloatingPoint.h"
+
+#include "txExprResult.h"
+
+/**
+ * Default Constructor
+ **/
+
+/**
+ * Creates a new NumberResult with the value of the given double parameter
+ * @param dbl the double to use for initialization of this NumberResult's value
+ **/
+NumberResult::NumberResult(double aValue, txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler), value(aValue) {} //-- NumberResult
+
+/*
+ * Virtual Methods from ExprResult
+ */
+
+short NumberResult::getResultType() {
+ return txAExprResult::NUMBER;
+} //-- getResultType
+
+void NumberResult::stringValue(nsString& aResult) {
+ txDouble::toString(value, aResult);
+}
+
+const nsString* NumberResult::stringValuePointer() { return nullptr; }
+
+bool NumberResult::booleanValue() {
+ // OG+
+ // As per the XPath spec, the boolean value of a number is true if and only if
+ // it is neither positive 0 nor negative 0 nor NaN
+ return (bool)(value != 0.0 && !std::isnan(value));
+ // OG-
+} //-- booleanValue
+
+double NumberResult::numberValue() { return this->value; } //-- numberValue
diff --git a/dom/xslt/xpath/txPathExpr.cpp b/dom/xslt/xpath/txPathExpr.cpp
new file mode 100644
index 0000000000..79be4caa08
--- /dev/null
+++ b/dom/xslt/xpath/txPathExpr.cpp
@@ -0,0 +1,229 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txNodeSetContext.h"
+#include "txSingleNodeContext.h"
+#include "txXMLUtils.h"
+#include "txXPathTreeWalker.h"
+
+using mozilla::Unused;
+using mozilla::WrapUnique;
+
+//------------/
+//- PathExpr -/
+//------------/
+
+/**
+ * Adds the Expr to this PathExpr
+ * @param expr the Expr to add to this PathExpr
+ **/
+void PathExpr::addExpr(Expr* aExpr, PathOperator aPathOp) {
+ NS_ASSERTION(!mItems.IsEmpty() || aPathOp == RELATIVE_OP,
+ "First step has to be relative in PathExpr");
+ PathExprItem* pxi = mItems.AppendElement();
+ pxi->expr = WrapUnique(aExpr);
+ pxi->pathOp = aPathOp;
+}
+
+//-----------------------------/
+//- Virtual methods from Expr -/
+//-----------------------------/
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult PathExpr::evaluate(txIEvalContext* aContext, txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ // We need to evaluate the first step with the current context since it
+ // can depend on the context size and position. For example:
+ // key('books', concat('book', position()))
+ RefPtr<txAExprResult> res;
+ nsresult rv = mItems[0].expr->evaluate(aContext, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(res->getResultType() == txAExprResult::NODESET,
+ NS_ERROR_XSLT_NODESET_EXPECTED);
+
+ RefPtr<txNodeSet> nodes =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(res));
+ if (nodes->isEmpty()) {
+ res.forget(aResult);
+
+ return NS_OK;
+ }
+ res = nullptr; // To allow recycling
+
+ // Evaluate remaining steps
+ uint32_t i, len = mItems.Length();
+ for (i = 1; i < len; ++i) {
+ PathExprItem& pxi = mItems[i];
+ RefPtr<txNodeSet> tmpNodes;
+ txNodeSetContext eContext(nodes, aContext);
+ while (eContext.hasNext()) {
+ eContext.next();
+
+ RefPtr<txNodeSet> resNodes;
+ if (pxi.pathOp == DESCENDANT_OP) {
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resNodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = evalDescendants(pxi.expr.get(), eContext.getContextNode(),
+ &eContext, resNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ RefPtr<txAExprResult> res;
+ rv = pxi.expr->evaluate(&eContext, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (res->getResultType() != txAExprResult::NODESET) {
+ // XXX ErrorReport: report nonnodeset error
+ return NS_ERROR_XSLT_NODESET_EXPECTED;
+ }
+ resNodes = static_cast<txNodeSet*>(static_cast<txAExprResult*>(res));
+ }
+
+ if (tmpNodes) {
+ if (!resNodes->isEmpty()) {
+ RefPtr<txNodeSet> oldSet;
+ oldSet.swap(tmpNodes);
+ rv = aContext->recycler()->getNonSharedNodeSet(
+ oldSet, getter_AddRefs(tmpNodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ oldSet.swap(resNodes);
+ rv = aContext->recycler()->getNonSharedNodeSet(
+ oldSet, getter_AddRefs(resNodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ tmpNodes->addAndTransfer(resNodes);
+ }
+ } else {
+ tmpNodes = resNodes;
+ }
+ }
+ nodes = tmpNodes;
+ if (nodes->isEmpty()) {
+ break;
+ }
+ }
+
+ *aResult = nodes;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+} //-- evaluate
+
+/**
+ * Selects from the descendants of the context node
+ * all nodes that match the Expr
+ **/
+nsresult PathExpr::evalDescendants(Expr* aStep, const txXPathNode& aNode,
+ txIMatchContext* aContext,
+ txNodeSet* resNodes) {
+ txSingleNodeContext eContext(aNode, aContext);
+ RefPtr<txAExprResult> res;
+ nsresult rv = aStep->evaluate(&eContext, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (res->getResultType() != txAExprResult::NODESET) {
+ // XXX ErrorReport: report nonnodeset error
+ return NS_ERROR_XSLT_NODESET_EXPECTED;
+ }
+
+ txNodeSet* oldSet = static_cast<txNodeSet*>(static_cast<txAExprResult*>(res));
+ RefPtr<txNodeSet> newSet;
+ rv =
+ aContext->recycler()->getNonSharedNodeSet(oldSet, getter_AddRefs(newSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ resNodes->addAndTransfer(newSet);
+
+ bool filterWS;
+ rv = aContext->isStripSpaceAllowed(aNode, filterWS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathTreeWalker walker(aNode);
+ if (!walker.moveToFirstChild()) {
+ return NS_OK;
+ }
+
+ do {
+ const txXPathNode& node = walker.getCurrentPosition();
+ if (!(filterWS && txXPathNodeUtils::isText(node) &&
+ txXPathNodeUtils::isWhitespace(node))) {
+ rv = evalDescendants(aStep, node, aContext, resNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } while (walker.moveToNextSibling());
+
+ return NS_OK;
+} //-- evalDescendants
+
+Expr::ExprType PathExpr::getType() { return PATH_EXPR; }
+
+TX_IMPL_EXPR_STUBS_BASE(PathExpr, NODESET_RESULT)
+
+Expr* PathExpr::getSubExprAt(uint32_t aPos) {
+ return aPos < mItems.Length() ? mItems[aPos].expr.get() : nullptr;
+}
+void PathExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ NS_ASSERTION(aPos < mItems.Length(), "setting bad subexpression index");
+ Unused << mItems[aPos].expr.release();
+ mItems[aPos].expr = WrapUnique(aExpr);
+}
+
+bool PathExpr::isSensitiveTo(ContextSensitivity aContext) {
+ if (mItems[0].expr->isSensitiveTo(aContext)) {
+ return true;
+ }
+
+ // We're creating a new node/nodeset so we can ignore those bits.
+ Expr::ContextSensitivity context =
+ aContext & ~(Expr::NODE_CONTEXT | Expr::NODESET_CONTEXT);
+ if (context == NO_CONTEXT) {
+ return false;
+ }
+
+ uint32_t i, len = mItems.Length();
+ for (i = 0; i < len; ++i) {
+ NS_ASSERTION(!mItems[i].expr->isSensitiveTo(Expr::NODESET_CONTEXT),
+ "Step cannot depend on nodeset-context");
+ if (mItems[i].expr->isSensitiveTo(context)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void PathExpr::toString(nsAString& dest) {
+ if (!mItems.IsEmpty()) {
+ NS_ASSERTION(mItems[0].pathOp == RELATIVE_OP,
+ "First step should be relative");
+ mItems[0].expr->toString(dest);
+ }
+
+ uint32_t i, len = mItems.Length();
+ for (i = 1; i < len; ++i) {
+ switch (mItems[i].pathOp) {
+ case DESCENDANT_OP:
+ dest.AppendLiteral("//");
+ break;
+ case RELATIVE_OP:
+ dest.Append(char16_t('/'));
+ break;
+ }
+ mItems[i].expr->toString(dest);
+ }
+}
+#endif
diff --git a/dom/xslt/xpath/txPredicateList.cpp b/dom/xslt/xpath/txPredicateList.cpp
new file mode 100644
index 0000000000..ad2804caef
--- /dev/null
+++ b/dom/xslt/xpath/txPredicateList.cpp
@@ -0,0 +1,78 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txNodeSetContext.h"
+
+/*
+ * Represents an ordered list of Predicates,
+ * for use with Step and Filter Expressions
+ */
+
+nsresult PredicateList::evaluatePredicates(txNodeSet* nodes,
+ txIMatchContext* aContext) {
+ NS_ASSERTION(nodes, "called evaluatePredicates with nullptr NodeSet");
+ nsresult rv = NS_OK;
+
+ uint32_t i, len = mPredicates.Length();
+ for (i = 0; i < len && !nodes->isEmpty(); ++i) {
+ txNodeSetContext predContext(nodes, aContext);
+ /*
+ * add nodes to newNodes that match the expression
+ * or, if the result is a number, add the node with the right
+ * position
+ */
+ int32_t index = 0;
+ while (predContext.hasNext()) {
+ predContext.next();
+ RefPtr<txAExprResult> exprResult;
+ rv = mPredicates[i]->evaluate(&predContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // handle default, [position() == numberValue()]
+ if (exprResult->getResultType() == txAExprResult::NUMBER) {
+ if ((double)predContext.position() == exprResult->numberValue()) {
+ nodes->mark(index);
+ }
+ } else if (exprResult->booleanValue()) {
+ nodes->mark(index);
+ }
+ ++index;
+ }
+ // sweep the non-marked nodes
+ nodes->sweep();
+ }
+
+ return NS_OK;
+}
+
+bool PredicateList::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ // We're creating a new node/nodeset so we can ignore those bits.
+ Expr::ContextSensitivity context =
+ aContext & ~(Expr::NODE_CONTEXT | Expr::NODESET_CONTEXT);
+ if (context == Expr::NO_CONTEXT) {
+ return false;
+ }
+
+ uint32_t i, len = mPredicates.Length();
+ for (i = 0; i < len; ++i) {
+ if (mPredicates[i]->isSensitiveTo(context)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void PredicateList::toString(nsAString& dest) {
+ for (uint32_t i = 0; i < mPredicates.Length(); ++i) {
+ dest.Append(char16_t('['));
+ mPredicates[i]->toString(dest);
+ dest.Append(char16_t(']'));
+ }
+}
+#endif
diff --git a/dom/xslt/xpath/txPredicatedNodeTest.cpp b/dom/xslt/xpath/txPredicatedNodeTest.cpp
new file mode 100644
index 0000000000..6c3fd33cf5
--- /dev/null
+++ b/dom/xslt/xpath/txPredicatedNodeTest.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "txExpr.h"
+#include "txExprResult.h"
+#include "txSingleNodeContext.h"
+
+txPredicatedNodeTest::txPredicatedNodeTest(txNodeTest* aNodeTest,
+ Expr* aPredicate)
+ : mNodeTest(aNodeTest), mPredicate(aPredicate) {
+ NS_ASSERTION(!mPredicate->isSensitiveTo(Expr::NODESET_CONTEXT),
+ "predicate must not be context-nodeset-sensitive");
+}
+
+nsresult txPredicatedNodeTest::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext,
+ bool& aMatched) {
+ nsresult rv = mNodeTest->matches(aNode, aContext, aMatched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aMatched) {
+ return NS_OK;
+ }
+
+ txSingleNodeContext context(aNode, aContext);
+ RefPtr<txAExprResult> res;
+ rv = mPredicate->evaluate(&context, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMatched = res->booleanValue();
+ return NS_OK;
+}
+
+double txPredicatedNodeTest::getDefaultPriority() { return 0.5; }
+
+bool txPredicatedNodeTest::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ return mNodeTest->isSensitiveTo(aContext) ||
+ mPredicate->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void txPredicatedNodeTest::toString(nsAString& aDest) {
+ mNodeTest->toString(aDest);
+ aDest.Append(char16_t('['));
+ mPredicate->toString(aDest);
+ aDest.Append(char16_t(']'));
+}
+#endif
diff --git a/dom/xslt/xpath/txRelationalExpr.cpp b/dom/xslt/xpath/txRelationalExpr.cpp
new file mode 100644
index 0000000000..df6da87ca8
--- /dev/null
+++ b/dom/xslt/xpath/txRelationalExpr.cpp
@@ -0,0 +1,184 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txIXPathContext.h"
+#include "txXPathTreeWalker.h"
+
+/**
+ * Compares the two ExprResults based on XPath 1.0 Recommendation (section 3.4)
+ */
+bool RelationalExpr::compareResults(txIEvalContext* aContext,
+ txAExprResult* aLeft,
+ txAExprResult* aRight) {
+ short ltype = aLeft->getResultType();
+ short rtype = aRight->getResultType();
+ nsresult rv = NS_OK;
+
+ // Handle case for just Left NodeSet or Both NodeSets
+ if (ltype == txAExprResult::NODESET) {
+ if (rtype == txAExprResult::BOOLEAN) {
+ BooleanResult leftBool(aLeft->booleanValue());
+ return compareResults(aContext, &leftBool, aRight);
+ }
+
+ txNodeSet* nodeSet = static_cast<txNodeSet*>(aLeft);
+ RefPtr<StringResult> strResult;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strResult));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t i;
+ for (i = 0; i < nodeSet->size(); ++i) {
+ strResult->mValue.Truncate();
+ txXPathNodeUtils::appendNodeValue(nodeSet->get(i), strResult->mValue);
+ if (compareResults(aContext, strResult, aRight)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Handle case for Just Right NodeSet
+ if (rtype == txAExprResult::NODESET) {
+ if (ltype == txAExprResult::BOOLEAN) {
+ BooleanResult rightBool(aRight->booleanValue());
+ return compareResults(aContext, aLeft, &rightBool);
+ }
+
+ txNodeSet* nodeSet = static_cast<txNodeSet*>(aRight);
+ RefPtr<StringResult> strResult;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strResult));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t i;
+ for (i = 0; i < nodeSet->size(); ++i) {
+ strResult->mValue.Truncate();
+ txXPathNodeUtils::appendNodeValue(nodeSet->get(i), strResult->mValue);
+ if (compareResults(aContext, aLeft, strResult)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Neither is a NodeSet
+ if (mOp == EQUAL || mOp == NOT_EQUAL) {
+ bool result;
+ const nsString *lString, *rString;
+
+ // If either is a bool, compare as bools.
+ if (ltype == txAExprResult::BOOLEAN || rtype == txAExprResult::BOOLEAN) {
+ result = aLeft->booleanValue() == aRight->booleanValue();
+ }
+
+ // If either is a number, compare as numbers.
+ else if (ltype == txAExprResult::NUMBER || rtype == txAExprResult::NUMBER) {
+ double lval = aLeft->numberValue();
+ double rval = aRight->numberValue();
+ result = (lval == rval);
+ }
+
+ // Otherwise compare as strings. Try to use the stringobject in
+ // StringResult if possible since that is a common case.
+ else if ((lString = aLeft->stringValuePointer())) {
+ if ((rString = aRight->stringValuePointer())) {
+ result = lString->Equals(*rString);
+ } else {
+ nsAutoString rStr;
+ aRight->stringValue(rStr);
+ result = lString->Equals(rStr);
+ }
+ } else if ((rString = aRight->stringValuePointer())) {
+ nsAutoString lStr;
+ aLeft->stringValue(lStr);
+ result = rString->Equals(lStr);
+ } else {
+ nsAutoString lStr, rStr;
+ aLeft->stringValue(lStr);
+ aRight->stringValue(rStr);
+ result = lStr.Equals(rStr);
+ }
+
+ return mOp == EQUAL ? result : !result;
+ }
+
+ double leftDbl = aLeft->numberValue();
+ double rightDbl = aRight->numberValue();
+ switch (mOp) {
+ case LESS_THAN: {
+ return (leftDbl < rightDbl);
+ }
+ case LESS_OR_EQUAL: {
+ return (leftDbl <= rightDbl);
+ }
+ case GREATER_THAN: {
+ return (leftDbl > rightDbl);
+ }
+ case GREATER_OR_EQUAL: {
+ return (leftDbl >= rightDbl);
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("We should have caught all cases");
+ }
+ }
+
+ return false;
+}
+
+nsresult RelationalExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+ RefPtr<txAExprResult> lResult;
+ nsresult rv = mLeftExpr->evaluate(aContext, getter_AddRefs(lResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txAExprResult> rResult;
+ rv = mRightExpr->evaluate(aContext, getter_AddRefs(rResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContext->recycler()->getBoolResult(
+ compareResults(aContext, lResult, rResult), aResult);
+
+ return NS_OK;
+}
+
+TX_IMPL_EXPR_STUBS_2(RelationalExpr, BOOLEAN_RESULT, mLeftExpr, mRightExpr)
+
+bool RelationalExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return mLeftExpr->isSensitiveTo(aContext) ||
+ mRightExpr->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void RelationalExpr::toString(nsAString& str) {
+ mLeftExpr->toString(str);
+
+ switch (mOp) {
+ case NOT_EQUAL:
+ str.AppendLiteral("!=");
+ break;
+ case LESS_THAN:
+ str.Append(char16_t('<'));
+ break;
+ case LESS_OR_EQUAL:
+ str.AppendLiteral("<=");
+ break;
+ case GREATER_THAN:
+ str.Append(char16_t('>'));
+ break;
+ case GREATER_OR_EQUAL:
+ str.AppendLiteral(">=");
+ break;
+ default:
+ str.Append(char16_t('='));
+ break;
+ }
+
+ mRightExpr->toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txResultRecycler.cpp b/dom/xslt/xpath/txResultRecycler.cpp
new file mode 100644
index 0000000000..b52f91838a
--- /dev/null
+++ b/dom/xslt/xpath/txResultRecycler.cpp
@@ -0,0 +1,171 @@
+/* -*- 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 "txResultRecycler.h"
+#include "txExprResult.h"
+#include "txNodeSet.h"
+
+txResultRecycler::txResultRecycler()
+ : mEmptyStringResult(new StringResult(nullptr)),
+ mTrueResult(new BooleanResult(true)),
+ mFalseResult(new BooleanResult(false)) {}
+
+txResultRecycler::~txResultRecycler() {
+ txStackIterator stringIter(&mStringResults);
+ while (stringIter.hasNext()) {
+ delete static_cast<StringResult*>(stringIter.next());
+ }
+ txStackIterator nodesetIter(&mNodeSetResults);
+ while (nodesetIter.hasNext()) {
+ delete static_cast<txNodeSet*>(nodesetIter.next());
+ }
+ txStackIterator numberIter(&mNumberResults);
+ while (numberIter.hasNext()) {
+ delete static_cast<NumberResult*>(numberIter.next());
+ }
+}
+
+void txResultRecycler::recycle(txAExprResult* aResult) {
+ NS_ASSERTION(aResult->mRefCnt == 0, "In-use txAExprResult recycled");
+ RefPtr<txResultRecycler> kungFuDeathGrip;
+ aResult->mRecycler.swap(kungFuDeathGrip);
+
+ switch (aResult->getResultType()) {
+ case txAExprResult::STRING: {
+ mStringResults.push(static_cast<StringResult*>(aResult));
+ return;
+ }
+ case txAExprResult::NODESET: {
+ static_cast<txNodeSet*>(aResult)->clear();
+ mNodeSetResults.push(static_cast<txNodeSet*>(aResult));
+ return;
+ }
+ case txAExprResult::NUMBER: {
+ mNumberResults.push(static_cast<NumberResult*>(aResult));
+ return;
+ }
+ default: {
+ delete aResult;
+ }
+ }
+}
+
+nsresult txResultRecycler::getStringResult(StringResult** aResult) {
+ if (mStringResults.isEmpty()) {
+ *aResult = new StringResult(this);
+ } else {
+ *aResult = static_cast<StringResult*>(mStringResults.pop());
+ (*aResult)->mValue.Truncate();
+ (*aResult)->mRecycler = this;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsresult txResultRecycler::getStringResult(const nsAString& aValue,
+ txAExprResult** aResult) {
+ if (mStringResults.isEmpty()) {
+ *aResult = new StringResult(aValue, this);
+ } else {
+ StringResult* strRes = static_cast<StringResult*>(mStringResults.pop());
+ strRes->mValue = aValue;
+ strRes->mRecycler = this;
+ *aResult = strRes;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+void txResultRecycler::getEmptyStringResult(txAExprResult** aResult) {
+ *aResult = mEmptyStringResult;
+ NS_ADDREF(*aResult);
+}
+
+nsresult txResultRecycler::getNodeSet(txNodeSet** aResult) {
+ if (mNodeSetResults.isEmpty()) {
+ *aResult = new txNodeSet(this);
+ } else {
+ *aResult = static_cast<txNodeSet*>(mNodeSetResults.pop());
+ (*aResult)->mRecycler = this;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsresult txResultRecycler::getNodeSet(txNodeSet* aNodeSet,
+ txNodeSet** aResult) {
+ if (mNodeSetResults.isEmpty()) {
+ *aResult = new txNodeSet(*aNodeSet, this);
+ } else {
+ *aResult = static_cast<txNodeSet*>(mNodeSetResults.pop());
+ (*aResult)->append(*aNodeSet);
+ (*aResult)->mRecycler = this;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsresult txResultRecycler::getNodeSet(const txXPathNode& aNode,
+ txAExprResult** aResult) {
+ if (mNodeSetResults.isEmpty()) {
+ *aResult = new txNodeSet(aNode, this);
+ } else {
+ txNodeSet* nodes = static_cast<txNodeSet*>(mNodeSetResults.pop());
+ nodes->append(aNode);
+ nodes->mRecycler = this;
+ *aResult = nodes;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsresult txResultRecycler::getNumberResult(double aValue,
+ txAExprResult** aResult) {
+ if (mNumberResults.isEmpty()) {
+ *aResult = new NumberResult(aValue, this);
+ } else {
+ NumberResult* numRes = static_cast<NumberResult*>(mNumberResults.pop());
+ numRes->value = aValue;
+ numRes->mRecycler = this;
+ *aResult = numRes;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+void txResultRecycler::getBoolResult(bool aValue, txAExprResult** aResult) {
+ *aResult = aValue ? mTrueResult : mFalseResult;
+ NS_ADDREF(*aResult);
+}
+
+nsresult txResultRecycler::getNonSharedNodeSet(txNodeSet* aNodeSet,
+ txNodeSet** aResult) {
+ if (aNodeSet->mRefCnt > 1) {
+ return getNodeSet(aNodeSet, aResult);
+ }
+
+ *aResult = aNodeSet;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+void txAExprResult::Release() {
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "txAExprResult");
+ if (mRefCnt == 0) {
+ if (mRecycler) {
+ mRecycler->recycle(this);
+ } else {
+ delete this;
+ }
+ }
+}
diff --git a/dom/xslt/xpath/txResultRecycler.h b/dom/xslt/xpath/txResultRecycler.h
new file mode 100644
index 0000000000..f90687b70b
--- /dev/null
+++ b/dom/xslt/xpath/txResultRecycler.h
@@ -0,0 +1,76 @@
+/* -*- 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/. */
+
+#ifndef txResultRecycler_h__
+#define txResultRecycler_h__
+
+#include "nsCOMPtr.h"
+#include "txStack.h"
+
+class txAExprResult;
+class StringResult;
+class txNodeSet;
+class txXPathNode;
+class NumberResult;
+class BooleanResult;
+
+class txResultRecycler {
+ public:
+ txResultRecycler();
+ ~txResultRecycler();
+
+ void AddRef() {
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "txResultRecycler", sizeof(*this));
+ }
+ void Release() {
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "txResultRecycler");
+ if (mRefCnt == 0) {
+ mRefCnt = 1; // stabilize
+ delete this;
+ }
+ }
+
+ /**
+ * Returns an txAExprResult to this recycler for reuse.
+ * @param aResult result to recycle
+ */
+ void recycle(txAExprResult* aResult);
+
+ /**
+ * Functions to return results that will be fully used by the caller.
+ * Returns nullptr on out-of-memory and an inited result otherwise.
+ */
+ nsresult getStringResult(StringResult** aResult);
+ nsresult getStringResult(const nsAString& aValue, txAExprResult** aResult);
+ nsresult getNodeSet(txNodeSet** aResult);
+ nsresult getNodeSet(txNodeSet* aNodeSet, txNodeSet** aResult);
+ nsresult getNodeSet(const txXPathNode& aNode, txAExprResult** aResult);
+ nsresult getNumberResult(double aValue, txAExprResult** aResult);
+
+ /**
+ * Functions to return a txAExprResult that is shared across several
+ * clients and must not be modified. Never returns nullptr.
+ */
+ void getEmptyStringResult(txAExprResult** aResult);
+ void getBoolResult(bool aValue, txAExprResult** aResult);
+
+ /**
+ * Functions that return non-shared resultsobjects
+ */
+ nsresult getNonSharedNodeSet(txNodeSet* aNodeSet, txNodeSet** aResult);
+
+ private:
+ nsAutoRefCnt mRefCnt;
+ txStack mStringResults;
+ txStack mNodeSetResults;
+ txStack mNumberResults;
+ RefPtr<StringResult> mEmptyStringResult;
+ RefPtr<BooleanResult> mTrueResult;
+ RefPtr<BooleanResult> mFalseResult;
+};
+
+#endif // txResultRecycler_h__
diff --git a/dom/xslt/xpath/txRootExpr.cpp b/dom/xslt/xpath/txRootExpr.cpp
new file mode 100644
index 0000000000..cabc0e1c13
--- /dev/null
+++ b/dom/xslt/xpath/txRootExpr.cpp
@@ -0,0 +1,35 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txIXPathContext.h"
+#include "txXPathTreeWalker.h"
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult RootExpr::evaluate(txIEvalContext* aContext, txAExprResult** aResult) {
+ txXPathTreeWalker walker(aContext->getContextNode());
+ walker.moveToRoot();
+
+ return aContext->recycler()->getNodeSet(walker.getCurrentPosition(), aResult);
+}
+
+TX_IMPL_EXPR_STUBS_0(RootExpr, NODESET_RESULT)
+
+bool RootExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return !!(aContext & NODE_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void RootExpr::toString(nsAString& dest) {
+ if (mSerialize) dest.Append(char16_t('/'));
+}
+#endif
diff --git a/dom/xslt/xpath/txSingleNodeContext.h b/dom/xslt/xpath/txSingleNodeContext.h
new file mode 100644
index 0000000000..80b0680a2d
--- /dev/null
+++ b/dom/xslt/xpath/txSingleNodeContext.h
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+#ifndef __TX_XPATH_SINGLENODE_CONTEXT
+#define __TX_XPATH_SINGLENODE_CONTEXT
+
+#include "mozilla/Attributes.h"
+#include "txIXPathContext.h"
+
+class txSingleNodeContext : public txIEvalContext {
+ public:
+ txSingleNodeContext(const txXPathNode& aContextNode,
+ txIMatchContext* aContext)
+ : mNode(aContextNode), mInner(aContext) {
+ NS_ASSERTION(aContext, "txIMatchContext must be given");
+ }
+
+ nsresult getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getVariable(aNamespace, aLName, aResult);
+ }
+
+ nsresult isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->isStripSpaceAllowed(aNode, aAllowed);
+ }
+
+ void* getPrivateContext() override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getPrivateContext();
+ }
+
+ txResultRecycler* recycler() override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->recycler();
+ }
+
+ void receiveError(const nsAString& aMsg, nsresult aRes) override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+#ifdef DEBUG
+ nsAutoString error(u"forwarded error: "_ns);
+ error.Append(aMsg);
+ mInner->receiveError(error, aRes);
+#else
+ mInner->receiveError(aMsg, aRes);
+#endif
+ }
+
+ const txXPathNode& getContextNode() override { return mNode; }
+
+ uint32_t size() override { return 1; }
+
+ uint32_t position() override { return 1; }
+
+ private:
+ const txXPathNode& mNode;
+ txIMatchContext* mInner;
+};
+
+#endif // __TX_XPATH_SINGLENODE_CONTEXT
diff --git a/dom/xslt/xpath/txStringResult.cpp b/dom/xslt/xpath/txStringResult.cpp
new file mode 100644
index 0000000000..740b6f1b34
--- /dev/null
+++ b/dom/xslt/xpath/txStringResult.cpp
@@ -0,0 +1,43 @@
+/* -*- 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/. */
+
+/**
+ * StringResult
+ * Represents a String as a Result of evaluating an Expr
+ **/
+#include "txExprResult.h"
+
+/**
+ * Default Constructor
+ **/
+StringResult::StringResult(txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler) {}
+
+/**
+ * Creates a new StringResult with the value of the given String parameter
+ * @param str the String to use for initialization of this StringResult's value
+ **/
+StringResult::StringResult(const nsAString& aValue, txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler), mValue(aValue) {}
+
+/*
+ * Virtual Methods from ExprResult
+ */
+
+short StringResult::getResultType() {
+ return txAExprResult::STRING;
+} //-- getResultType
+
+void StringResult::stringValue(nsString& aResult) { aResult.Append(mValue); }
+
+const nsString* StringResult::stringValuePointer() { return &mValue; }
+
+bool StringResult::booleanValue() {
+ return !mValue.IsEmpty();
+} //-- booleanValue
+
+double StringResult::numberValue() {
+ return txDouble::toDouble(mValue);
+} //-- numberValue
diff --git a/dom/xslt/xpath/txUnaryExpr.cpp b/dom/xslt/xpath/txUnaryExpr.cpp
new file mode 100644
index 0000000000..ec31a5fcb3
--- /dev/null
+++ b/dom/xslt/xpath/txUnaryExpr.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "txExpr.h"
+#include "txIXPathContext.h"
+
+/*
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation.
+ * @return the result of the evaluation.
+ */
+nsresult UnaryExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = expr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double value = exprRes->numberValue();
+#ifdef HPUX
+ /*
+ * Negation of a zero doesn't produce a negative
+ * zero on HPUX. Perform the operation by multiplying with
+ * -1.
+ */
+ return aContext->recycler()->getNumberResult(-1 * value, aResult);
+#else
+ return aContext->recycler()->getNumberResult(-value, aResult);
+#endif
+}
+
+TX_IMPL_EXPR_STUBS_1(UnaryExpr, NODESET_RESULT, expr)
+
+bool UnaryExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return expr->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void UnaryExpr::toString(nsAString& str) {
+ if (!expr) return;
+ str.Append(char16_t('-'));
+ expr->toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txUnionExpr.cpp b/dom/xslt/xpath/txUnionExpr.cpp
new file mode 100644
index 0000000000..be8fa47781
--- /dev/null
+++ b/dom/xslt/xpath/txUnionExpr.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "txExpr.h"
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+
+#ifdef TX_TO_STRING
+# include "nsReadableUtils.h"
+#endif
+
+//-------------/
+//- UnionExpr -/
+//-------------/
+
+//-----------------------------/
+//- Virtual methods from Expr -/
+//-----------------------------/
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult UnionExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+ RefPtr<txNodeSet> nodes;
+ nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i, len = mExpressions.Length();
+ for (i = 0; i < len; ++i) {
+ RefPtr<txAExprResult> exprResult;
+ rv = mExpressions[i]->evaluate(aContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exprResult->getResultType() != txAExprResult::NODESET) {
+ // XXX ErrorReport: report nonnodeset error
+ return NS_ERROR_XSLT_NODESET_EXPECTED;
+ }
+
+ RefPtr<txNodeSet> resultSet, ownedSet;
+ resultSet =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult));
+ exprResult = nullptr;
+ rv = aContext->recycler()->getNonSharedNodeSet(resultSet,
+ getter_AddRefs(ownedSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nodes->addAndTransfer(ownedSet);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aResult = nodes;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+} //-- evaluate
+
+Expr::ExprType UnionExpr::getType() { return UNION_EXPR; }
+
+TX_IMPL_EXPR_STUBS_LIST(UnionExpr, NODESET_RESULT, mExpressions)
+
+bool UnionExpr::isSensitiveTo(ContextSensitivity aContext) {
+ uint32_t i, len = mExpressions.Length();
+ for (i = 0; i < len; ++i) {
+ if (mExpressions[i]->isSensitiveTo(aContext)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void UnionExpr::toString(nsAString& aDest) {
+ StringJoinAppend(aDest, u" | "_ns, mExpressions,
+ [](nsAString& dest, Expr* expr) { expr->toString(dest); });
+}
+#endif
diff --git a/dom/xslt/xpath/txUnionNodeTest.cpp b/dom/xslt/xpath/txUnionNodeTest.cpp
new file mode 100644
index 0000000000..4461b537e4
--- /dev/null
+++ b/dom/xslt/xpath/txUnionNodeTest.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/FloatingPoint.h"
+
+#include "txExpr.h"
+#include "txExprResult.h"
+#include "txSingleNodeContext.h"
+
+#ifdef TX_TO_STRING
+# include "nsReadableUtils.h"
+#endif
+
+nsresult txUnionNodeTest::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ uint32_t i, len = mNodeTests.Length();
+ for (i = 0; i < len; ++i) {
+ nsresult rv = mNodeTests[i]->matches(aNode, aContext, aMatched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aMatched) {
+ return NS_OK;
+ }
+ }
+
+ aMatched = false;
+ return NS_OK;
+}
+
+double txUnionNodeTest::getDefaultPriority() {
+ NS_ERROR("Don't call getDefaultPriority on txUnionPattern");
+ return mozilla::UnspecifiedNaN<double>();
+}
+
+bool txUnionNodeTest::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ uint32_t i, len = mNodeTests.Length();
+ for (i = 0; i < len; ++i) {
+ if (mNodeTests[i]->isSensitiveTo(aContext)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void txUnionNodeTest::toString(nsAString& aDest) {
+ aDest.Append('(');
+
+ StringJoinAppend(
+ aDest, u" | "_ns, mNodeTests,
+ [](nsAString& dest, txNodeTest* nodeTest) { nodeTest->toString(dest); });
+
+ aDest.Append(')');
+}
+#endif
diff --git a/dom/xslt/xpath/txVariableRefExpr.cpp b/dom/xslt/xpath/txVariableRefExpr.cpp
new file mode 100644
index 0000000000..4836913135
--- /dev/null
+++ b/dom/xslt/xpath/txVariableRefExpr.cpp
@@ -0,0 +1,62 @@
+/* -*- 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 "txExpr.h"
+#include "nsAtom.h"
+#include "txNodeSet.h"
+#include "nsGkAtoms.h"
+#include "txIXPathContext.h"
+
+//-------------------/
+//- VariableRefExpr -/
+//-------------------/
+
+/**
+ * Creates a VariableRefExpr with the given variable name
+ **/
+VariableRefExpr::VariableRefExpr(nsAtom* aPrefix, nsAtom* aLocalName,
+ int32_t aNSID)
+ : mPrefix(aPrefix), mLocalName(aLocalName), mNamespace(aNSID) {
+ NS_ASSERTION(mLocalName, "VariableRefExpr without local name?");
+ if (mPrefix == nsGkAtoms::_empty) mPrefix = nullptr;
+}
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult VariableRefExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ nsresult rv = aContext->getVariable(mNamespace, mLocalName, *aResult);
+ if (NS_FAILED(rv)) {
+ // XXX report error, undefined variable
+ return rv;
+ }
+ return NS_OK;
+}
+
+TX_IMPL_EXPR_STUBS_0(VariableRefExpr, ANY_RESULT)
+
+bool VariableRefExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return !!(aContext & VARIABLES_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void VariableRefExpr::toString(nsAString& aDest) {
+ aDest.Append(char16_t('$'));
+ if (mPrefix) {
+ nsAutoString prefix;
+ mPrefix->ToString(prefix);
+ aDest.Append(prefix);
+ aDest.Append(char16_t(':'));
+ }
+ nsAutoString lname;
+ mLocalName->ToString(lname);
+ aDest.Append(lname);
+}
+#endif
diff --git a/dom/xslt/xpath/txXPathNode.h b/dom/xslt/xpath/txXPathNode.h
new file mode 100644
index 0000000000..8c5d6f5308
--- /dev/null
+++ b/dom/xslt/xpath/txXPathNode.h
@@ -0,0 +1,91 @@
+/* -*- 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/. */
+
+#ifndef txXPathNode_h__
+#define txXPathNode_h__
+
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsINode.h"
+#include "nsNameSpaceManager.h"
+
+using txXPathNodeType = nsINode;
+
+class txXPathNode {
+ public:
+ bool operator==(const txXPathNode& aNode) const;
+ bool operator!=(const txXPathNode& aNode) const { return !(*this == aNode); }
+ ~txXPathNode();
+
+ private:
+ friend class txNodeSet;
+ friend class txXPathNativeNode;
+ friend class txXPathNodeUtils;
+ friend class txXPathTreeWalker;
+
+ txXPathNode(const txXPathNode& aNode);
+
+ explicit txXPathNode(mozilla::dom::Document* aDocument)
+ : mNode(aDocument), mRefCountRoot(0), mIndex(eDocument) {
+ MOZ_COUNT_CTOR(txXPathNode);
+ }
+ txXPathNode(nsINode* aNode, uint32_t aIndex, nsINode* aRoot)
+ : mNode(aNode), mRefCountRoot(aRoot ? 1 : 0), mIndex(aIndex) {
+ MOZ_COUNT_CTOR(txXPathNode);
+ if (aRoot) {
+ NS_ADDREF(aRoot);
+ }
+ }
+
+ static nsINode* RootOf(nsINode* aNode) { return aNode->SubtreeRoot(); }
+ nsINode* Root() const { return RootOf(mNode); }
+ nsINode* GetRootToAddRef() const { return mRefCountRoot ? Root() : nullptr; }
+
+ bool isDocument() const { return mIndex == eDocument; }
+ bool isContent() const { return mIndex == eContent; }
+ bool isAttribute() const { return mIndex != eDocument && mIndex != eContent; }
+
+ nsIContent* Content() const {
+ NS_ASSERTION(isContent() || isAttribute(), "wrong type");
+ return static_cast<nsIContent*>(mNode);
+ }
+ mozilla::dom::Document* Document() const {
+ NS_ASSERTION(isDocument(), "wrong type");
+ return static_cast<mozilla::dom::Document*>(mNode);
+ }
+
+ enum PositionType { eDocument = (1 << 30), eContent = eDocument - 1 };
+
+ nsINode* mNode;
+ uint32_t mRefCountRoot : 1;
+ uint32_t mIndex : 31;
+};
+
+class txNamespaceManager {
+ public:
+ static int32_t getNamespaceID(const nsAString& aNamespaceURI);
+ static nsresult getNamespaceURI(const int32_t aID, nsAString& aResult);
+};
+
+/* static */
+inline int32_t txNamespaceManager::getNamespaceID(
+ const nsAString& aNamespaceURI) {
+ int32_t namespaceID = kNameSpaceID_Unknown;
+ nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI,
+ namespaceID);
+ return namespaceID;
+}
+
+/* static */
+inline nsresult txNamespaceManager::getNamespaceURI(const int32_t aID,
+ nsAString& aResult) {
+ return nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aID, aResult);
+}
+
+inline bool txXPathNode::operator==(const txXPathNode& aNode) const {
+ return mIndex == aNode.mIndex && mNode == aNode.mNode;
+}
+
+#endif /* txXPathNode_h__ */
diff --git a/dom/xslt/xpath/txXPathOptimizer.cpp b/dom/xslt/xpath/txXPathOptimizer.cpp
new file mode 100644
index 0000000000..40a94f674c
--- /dev/null
+++ b/dom/xslt/xpath/txXPathOptimizer.cpp
@@ -0,0 +1,246 @@
+/* -*- 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 "mozilla/Assertions.h"
+#include "txXPathOptimizer.h"
+#include "txExprResult.h"
+#include "nsAtom.h"
+#include "nsGkAtoms.h"
+#include "txXPathNode.h"
+#include "txExpr.h"
+#include "txIXPathContext.h"
+
+using mozilla::UniquePtr;
+using mozilla::Unused;
+
+class txEarlyEvalContext : public txIEvalContext {
+ public:
+ explicit txEarlyEvalContext(txResultRecycler* aRecycler)
+ : mRecycler(aRecycler) {}
+
+ // txIEvalContext
+ nsresult getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+ nsresult isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+ void* getPrivateContext() override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+ txResultRecycler* recycler() override { return mRecycler; }
+ void receiveError(const nsAString& aMsg, nsresult aRes) override {}
+ const txXPathNode& getContextNode() override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+ uint32_t size() override { MOZ_CRASH("shouldn't depend on this context"); }
+ uint32_t position() override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+
+ private:
+ txResultRecycler* mRecycler;
+};
+
+void txXPathOptimizer::optimize(Expr* aInExpr, Expr** aOutExpr) {
+ *aOutExpr = nullptr;
+
+ // First check if the expression will produce the same result
+ // under any context.
+ Expr::ExprType exprType = aInExpr->getType();
+ if (exprType != Expr::LITERAL_EXPR &&
+ !aInExpr->isSensitiveTo(Expr::ANY_CONTEXT)) {
+ RefPtr<txResultRecycler> recycler = new txResultRecycler;
+ txEarlyEvalContext context(recycler);
+ RefPtr<txAExprResult> exprRes;
+
+ // Don't throw if this fails since it could be that the expression
+ // is or contains an error-expression.
+ nsresult rv = aInExpr->evaluate(&context, getter_AddRefs(exprRes));
+ if (NS_SUCCEEDED(rv)) {
+ *aOutExpr = new txLiteralExpr(exprRes);
+ }
+
+ return;
+ }
+
+ // Then optimize sub expressions
+ uint32_t i = 0;
+ Expr* subExpr;
+ while ((subExpr = aInExpr->getSubExprAt(i))) {
+ Expr* newExpr = nullptr;
+ optimize(subExpr, &newExpr);
+ if (newExpr) {
+ delete subExpr;
+ aInExpr->setSubExprAt(i, newExpr);
+ }
+
+ ++i;
+ }
+
+ // Finally see if current expression can be optimized
+ switch (exprType) {
+ case Expr::LOCATIONSTEP_EXPR:
+ optimizeStep(aInExpr, aOutExpr);
+ return;
+
+ case Expr::PATH_EXPR:
+ optimizePath(aInExpr, aOutExpr);
+ return;
+
+ case Expr::UNION_EXPR:
+ optimizeUnion(aInExpr, aOutExpr);
+ return;
+
+ default:
+ return;
+ }
+}
+
+void txXPathOptimizer::optimizeStep(Expr* aInExpr, Expr** aOutExpr) {
+ LocationStep* step = static_cast<LocationStep*>(aInExpr);
+
+ if (step->getAxisIdentifier() == LocationStep::ATTRIBUTE_AXIS) {
+ // Test for @foo type steps.
+ txNameTest* nameTest = nullptr;
+ if (!step->getSubExprAt(0) &&
+ step->getNodeTest()->getType() == txNameTest::NAME_TEST &&
+ (nameTest = static_cast<txNameTest*>(step->getNodeTest()))
+ ->mLocalName != nsGkAtoms::_asterisk) {
+ *aOutExpr = new txNamedAttributeStep(
+ nameTest->mNamespace, nameTest->mPrefix, nameTest->mLocalName);
+ return; // return since we no longer have a step-object.
+ }
+ }
+
+ // Test for predicates that can be combined into the nodetest
+ Expr* pred;
+ while ((pred = step->getSubExprAt(0)) &&
+ !pred->canReturnType(Expr::NUMBER_RESULT) &&
+ !pred->isSensitiveTo(Expr::NODESET_CONTEXT)) {
+ txNodeTest* predTest = new txPredicatedNodeTest(step->getNodeTest(), pred);
+ step->dropFirst();
+ step->setNodeTest(predTest);
+ }
+}
+
+void txXPathOptimizer::optimizePath(Expr* aInExpr, Expr** aOutExpr) {
+ PathExpr* path = static_cast<PathExpr*>(aInExpr);
+
+ uint32_t i;
+ Expr* subExpr;
+ // look for steps like "//foo" that can be turned into "/descendant::foo"
+ // and "//." that can be turned into "/descendant-or-self::node()"
+ for (i = 0; (subExpr = path->getSubExprAt(i)); ++i) {
+ if (path->getPathOpAt(i) == PathExpr::DESCENDANT_OP &&
+ subExpr->getType() == Expr::LOCATIONSTEP_EXPR &&
+ !subExpr->getSubExprAt(0)) {
+ LocationStep* step = static_cast<LocationStep*>(subExpr);
+ if (step->getAxisIdentifier() == LocationStep::CHILD_AXIS) {
+ step->setAxisIdentifier(LocationStep::DESCENDANT_AXIS);
+ path->setPathOpAt(i, PathExpr::RELATIVE_OP);
+ } else if (step->getAxisIdentifier() == LocationStep::SELF_AXIS) {
+ step->setAxisIdentifier(LocationStep::DESCENDANT_OR_SELF_AXIS);
+ path->setPathOpAt(i, PathExpr::RELATIVE_OP);
+ }
+ }
+ }
+
+ // look for expressions that start with a "./"
+ subExpr = path->getSubExprAt(0);
+ LocationStep* step;
+ if (subExpr->getType() == Expr::LOCATIONSTEP_EXPR && path->getSubExprAt(1) &&
+ path->getPathOpAt(1) != PathExpr::DESCENDANT_OP) {
+ step = static_cast<LocationStep*>(subExpr);
+ if (step->getAxisIdentifier() == LocationStep::SELF_AXIS &&
+ !step->getSubExprAt(0)) {
+ txNodeTest* test = step->getNodeTest();
+ if (test->getType() == txNodeTest::NODETYPE_TEST &&
+ (static_cast<txNodeTypeTest*>(test))->getNodeTestType() ==
+ txNodeTypeTest::NODE_TYPE) {
+ // We have a '.' as first step followed by a single '/'.
+
+ // Check if there are only two steps. If so, return the second
+ // as resulting expression.
+ if (!path->getSubExprAt(2)) {
+ *aOutExpr = path->getSubExprAt(1);
+ path->setSubExprAt(1, nullptr);
+
+ return;
+ }
+
+ // Just delete the '.' step and leave the rest of the PathExpr
+ path->deleteExprAt(0);
+ }
+ }
+ }
+}
+
+void txXPathOptimizer::optimizeUnion(Expr* aInExpr, Expr** aOutExpr) {
+ UnionExpr* uni = static_cast<UnionExpr*>(aInExpr);
+
+ // Check for expressions like "foo | bar" and
+ // "descendant::foo | descendant::bar"
+
+ uint32_t current;
+ Expr* subExpr;
+ for (current = 0; (subExpr = uni->getSubExprAt(current)); ++current) {
+ if (subExpr->getType() != Expr::LOCATIONSTEP_EXPR ||
+ subExpr->getSubExprAt(0)) {
+ continue;
+ }
+
+ LocationStep* currentStep = static_cast<LocationStep*>(subExpr);
+ LocationStep::LocationStepType axis = currentStep->getAxisIdentifier();
+
+ txUnionNodeTest* unionTest = nullptr;
+
+ // Check if there are any other steps with the same axis and merge
+ // them with currentStep
+ uint32_t i;
+ for (i = current + 1; (subExpr = uni->getSubExprAt(i)); ++i) {
+ if (subExpr->getType() != Expr::LOCATIONSTEP_EXPR ||
+ subExpr->getSubExprAt(0)) {
+ continue;
+ }
+
+ LocationStep* step = static_cast<LocationStep*>(subExpr);
+ if (step->getAxisIdentifier() != axis) {
+ continue;
+ }
+
+ // Create a txUnionNodeTest if needed
+ if (!unionTest) {
+ UniquePtr<txNodeTest> owner(unionTest = new txUnionNodeTest);
+ unionTest->addNodeTest(currentStep->getNodeTest());
+
+ currentStep->setNodeTest(unionTest);
+ Unused << owner.release();
+ }
+
+ // Merge the nodetest into the union
+ unionTest->addNodeTest(step->getNodeTest());
+
+ step->setNodeTest(nullptr);
+
+ // Remove the step from the UnionExpr
+ uni->deleteExprAt(i);
+ --i;
+ }
+
+ // Check if all expressions were merged into a single step. If so,
+ // return the step as the new expression.
+ if (unionTest && current == 0 && !uni->getSubExprAt(1)) {
+ // Make sure the step doesn't get deleted when the UnionExpr is
+ uni->setSubExprAt(0, nullptr);
+ *aOutExpr = currentStep;
+
+ // Return right away since we no longer have a union
+ return;
+ }
+ }
+}
diff --git a/dom/xslt/xpath/txXPathOptimizer.h b/dom/xslt/xpath/txXPathOptimizer.h
new file mode 100644
index 0000000000..84f00191e7
--- /dev/null
+++ b/dom/xslt/xpath/txXPathOptimizer.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#ifndef txXPathOptimizer_h__
+#define txXPathOptimizer_h__
+
+#include "txCore.h"
+
+class Expr;
+
+class txXPathOptimizer {
+ public:
+ /**
+ * Optimize the given expression.
+ * @param aInExpr Expression to optimize.
+ * @param aOutExpr Resulting expression, null if optimization didn't
+ * result in a new expression.
+ */
+ void optimize(Expr* aInExpr, Expr** aOutExpr);
+
+ private:
+ // Helper methods for optimizing specific classes
+ void optimizeStep(Expr* aInExpr, Expr** aOutExpr);
+ void optimizePath(Expr* aInExpr, Expr** aOutExpr);
+ void optimizeUnion(Expr* aInExpr, Expr** aOutExpr);
+};
+
+#endif
diff --git a/dom/xslt/xpath/txXPathTreeWalker.h b/dom/xslt/xpath/txXPathTreeWalker.h
new file mode 100644
index 0000000000..6b74b1e18d
--- /dev/null
+++ b/dom/xslt/xpath/txXPathTreeWalker.h
@@ -0,0 +1,201 @@
+/* -*- 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/. */
+
+#ifndef txXPathTreeWalker_h__
+#define txXPathTreeWalker_h__
+
+#include "txCore.h"
+#include "txXPathNode.h"
+#include "nsIContentInlines.h"
+#include "nsTArray.h"
+
+class nsAtom;
+
+class txXPathTreeWalker {
+ public:
+ txXPathTreeWalker(const txXPathTreeWalker& aOther);
+ explicit txXPathTreeWalker(const txXPathNode& aNode);
+
+ bool getAttr(nsAtom* aLocalName, int32_t aNSID, nsAString& aValue) const;
+ int32_t getNamespaceID() const;
+ uint16_t getNodeType() const;
+ void appendNodeValue(nsAString& aResult) const;
+ void getNodeName(nsAString& aName) const;
+
+ void moveTo(const txXPathTreeWalker& aWalker);
+
+ void moveToRoot();
+ bool moveToParent();
+ bool moveToElementById(const nsAString& aID);
+ bool moveToFirstAttribute();
+ bool moveToNextAttribute();
+ bool moveToNamedAttribute(nsAtom* aLocalName, int32_t aNSID);
+ bool moveToFirstChild();
+ bool moveToLastChild();
+ bool moveToNextSibling();
+ bool moveToPreviousSibling();
+
+ bool isOnNode(const txXPathNode& aNode) const;
+
+ const txXPathNode& getCurrentPosition() const;
+
+ private:
+ txXPathNode mPosition;
+
+ bool moveToValidAttribute(uint32_t aStartIndex);
+};
+
+class txXPathNodeUtils {
+ public:
+ static bool getAttr(const txXPathNode& aNode, nsAtom* aLocalName,
+ int32_t aNSID, nsAString& aValue);
+ static already_AddRefed<nsAtom> getLocalName(const txXPathNode& aNode);
+ static nsAtom* getPrefix(const txXPathNode& aNode);
+ static void getLocalName(const txXPathNode& aNode, nsAString& aLocalName);
+ static void getNodeName(const txXPathNode& aNode, nsAString& aName);
+ static int32_t getNamespaceID(const txXPathNode& aNode);
+ static void getNamespaceURI(const txXPathNode& aNode, nsAString& aURI);
+ static uint16_t getNodeType(const txXPathNode& aNode);
+ static void appendNodeValue(const txXPathNode& aNode, nsAString& aResult);
+ static bool isWhitespace(const txXPathNode& aNode);
+ static txXPathNode* getOwnerDocument(const txXPathNode& aNode);
+ static int32_t getUniqueIdentifier(const txXPathNode& aNode);
+ static nsresult getXSLTId(const txXPathNode& aNode, const txXPathNode& aBase,
+ nsAString& aResult);
+ static void release(txXPathNode* aNode);
+ static nsresult getBaseURI(const txXPathNode& aNode, nsAString& aURI);
+ static int comparePosition(const txXPathNode& aNode,
+ const txXPathNode& aOtherNode);
+ static bool localNameEquals(const txXPathNode& aNode, nsAtom* aLocalName);
+ static bool isRoot(const txXPathNode& aNode);
+ static bool isElement(const txXPathNode& aNode);
+ static bool isAttribute(const txXPathNode& aNode);
+ static bool isProcessingInstruction(const txXPathNode& aNode);
+ static bool isComment(const txXPathNode& aNode);
+ static bool isText(const txXPathNode& aNode);
+ static inline bool isHTMLElementInHTMLDocument(const txXPathNode& aNode) {
+ if (!aNode.isContent()) {
+ return false;
+ }
+ nsIContent* content = aNode.Content();
+ return content->IsHTMLElement() && content->IsInHTMLDocument();
+ }
+};
+
+class txXPathNativeNode {
+ public:
+ static txXPathNode* createXPathNode(nsINode* aNode,
+ bool aKeepRootAlive = false);
+ static txXPathNode* createXPathNode(nsIContent* aContent,
+ bool aKeepRootAlive = false);
+ static txXPathNode* createXPathNode(mozilla::dom::Document* aDocument);
+ static nsINode* getNode(const txXPathNode& aNode);
+ static nsIContent* getContent(const txXPathNode& aNode);
+ static mozilla::dom::Document* getDocument(const txXPathNode& aNode);
+ static void addRef(const txXPathNode& aNode) { NS_ADDREF(aNode.mNode); }
+ static void release(const txXPathNode& aNode) {
+ nsINode* node = aNode.mNode;
+ NS_RELEASE(node);
+ }
+};
+
+inline const txXPathNode& txXPathTreeWalker::getCurrentPosition() const {
+ return mPosition;
+}
+
+inline bool txXPathTreeWalker::getAttr(nsAtom* aLocalName, int32_t aNSID,
+ nsAString& aValue) const {
+ return txXPathNodeUtils::getAttr(mPosition, aLocalName, aNSID, aValue);
+}
+
+inline int32_t txXPathTreeWalker::getNamespaceID() const {
+ return txXPathNodeUtils::getNamespaceID(mPosition);
+}
+
+inline void txXPathTreeWalker::appendNodeValue(nsAString& aResult) const {
+ txXPathNodeUtils::appendNodeValue(mPosition, aResult);
+}
+
+inline void txXPathTreeWalker::getNodeName(nsAString& aName) const {
+ txXPathNodeUtils::getNodeName(mPosition, aName);
+}
+
+inline void txXPathTreeWalker::moveTo(const txXPathTreeWalker& aWalker) {
+ nsINode* root = nullptr;
+ if (mPosition.mRefCountRoot) {
+ root = mPosition.Root();
+ }
+ mPosition.mIndex = aWalker.mPosition.mIndex;
+ mPosition.mRefCountRoot = aWalker.mPosition.mRefCountRoot;
+ mPosition.mNode = aWalker.mPosition.mNode;
+ nsINode* newRoot = nullptr;
+ if (mPosition.mRefCountRoot) {
+ newRoot = mPosition.Root();
+ }
+ if (root != newRoot) {
+ NS_IF_ADDREF(newRoot);
+ NS_IF_RELEASE(root);
+ }
+}
+
+inline bool txXPathTreeWalker::isOnNode(const txXPathNode& aNode) const {
+ return (mPosition == aNode);
+}
+
+/* static */
+inline int32_t txXPathNodeUtils::getUniqueIdentifier(const txXPathNode& aNode) {
+ MOZ_ASSERT(!aNode.isAttribute(), "Not implemented for attributes.");
+ return NS_PTR_TO_INT32(aNode.mNode);
+}
+
+/* static */
+inline void txXPathNodeUtils::release(txXPathNode* aNode) {
+ NS_RELEASE(aNode->mNode);
+}
+
+/* static */
+inline bool txXPathNodeUtils::localNameEquals(const txXPathNode& aNode,
+ nsAtom* aLocalName) {
+ if (aNode.isContent() && aNode.Content()->IsElement()) {
+ return aNode.Content()->NodeInfo()->Equals(aLocalName);
+ }
+
+ RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(aNode);
+
+ return localName == aLocalName;
+}
+
+/* static */
+inline bool txXPathNodeUtils::isRoot(const txXPathNode& aNode) {
+ return !aNode.isAttribute() && !aNode.mNode->GetParentNode();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isElement(const txXPathNode& aNode) {
+ return aNode.isContent() && aNode.Content()->IsElement();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isAttribute(const txXPathNode& aNode) {
+ return aNode.isAttribute();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isProcessingInstruction(
+ const txXPathNode& aNode) {
+ return aNode.isContent() && aNode.Content()->IsProcessingInstruction();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isComment(const txXPathNode& aNode) {
+ return aNode.isContent() && aNode.Content()->IsComment();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isText(const txXPathNode& aNode) {
+ return aNode.isContent() && aNode.Content()->IsText();
+}
+
+#endif /* txXPathTreeWalker_h__ */