diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/xslt/xpath | |
parent | Initial commit. (diff) | |
download | firefox-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')
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__ */ |