diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/xslt/xpath | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/xslt/xpath')
56 files changed, 10909 insertions, 0 deletions
diff --git a/dom/xslt/xpath/XPathEvaluator.cpp b/dom/xslt/xpath/XPathEvaluator.cpp new file mode 100644 index 000000000..e7170373d --- /dev/null +++ b/dom/xslt/xpath/XPathEvaluator.cpp @@ -0,0 +1,263 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "mozilla/Move.h" +#include "nsCOMPtr.h" +#include "nsIAtom.h" +#include "mozilla/dom/XPathExpression.h" +#include "XPathResult.h" +#include "nsContentCID.h" +#include "txExpr.h" +#include "txExprParser.h" +#include "nsError.h" +#include "txURIUtils.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsDOMString.h" +#include "nsNameSpaceManager.h" +#include "nsContentUtils.h" +#include "txIXPathContext.h" +#include "mozilla/dom/XPathEvaluatorBinding.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/XPathNSResolverBinding.h" + +extern nsresult +TX_ResolveFunctionCallXPCOM(const nsCString &aContractID, int32_t aNamespaceID, + nsIAtom *aName, nsISupports *aState, + FunctionCall **aFunction); + +namespace mozilla { +namespace 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(nsIAtom* aPrefix, int32_t& aID); + nsresult resolveFunctionCall(nsIAtom* aName, int32_t aID, + FunctionCall** aFunction); + bool caseInsensitiveNameTests(); + void SetErrorOffset(uint32_t aOffset); + +private: + XPathNSResolver* mResolver; + nsINode* mResolverNode; + nsresult mLastError; + bool mIsCaseSensitive; +}; + +NS_IMPL_ISUPPORTS(XPathEvaluator, nsIDOMXPathEvaluator) + +XPathEvaluator::XPathEvaluator(nsIDocument* aDocument) + : mDocument(do_GetWeakReference(aDocument)) +{ +} + +XPathEvaluator::~XPathEvaluator() +{ +} + +NS_IMETHODIMP +XPathEvaluator::Evaluate(const nsAString & aExpression, + nsIDOMNode *aContextNode, + nsIDOMNode *aResolver, + uint16_t aType, + nsISupports *aInResult, + nsISupports **aResult) +{ + nsCOMPtr<nsINode> resolver = do_QueryInterface(aResolver); + ErrorResult rv; + nsAutoPtr<XPathExpression> expression(CreateExpression(aExpression, + resolver, rv)); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + nsCOMPtr<nsINode> node = do_QueryInterface(aContextNode); + if (!node) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIXPathResult> inResult = do_QueryInterface(aInResult); + RefPtr<XPathResult> result = + expression->Evaluate(*node, aType, + static_cast<XPathResult*>(inResult.get()), rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + *aResult = ToSupports(result.forget().take()); + + return NS_OK; +} + +XPathExpression* +XPathEvaluator::CreateExpression(const nsAString& aExpression, + XPathNSResolver* aResolver, ErrorResult& aRv) +{ + nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument); + XPathEvaluatorParseContext pContext(aResolver, + !(doc && doc->IsHTMLDocument())); + return CreateExpression(aExpression, &pContext, doc, aRv); +} + +XPathExpression* +XPathEvaluator::CreateExpression(const nsAString& aExpression, + nsINode* aResolver, ErrorResult& aRv) +{ + nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument); + XPathEvaluatorParseContext pContext(aResolver, + !(doc && doc->IsHTMLDocument())); + return CreateExpression(aExpression, &pContext, doc, aRv); +} + +XPathExpression* +XPathEvaluator::CreateExpression(const nsAString & aExpression, + txIParseContext* aContext, + nsIDocument* aDocument, + ErrorResult& aRv) +{ + if (!mRecycler) { + mRecycler = new txResultRecycler; + } + + nsAutoPtr<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.Throw(NS_ERROR_DOM_INVALID_EXPRESSION_ERR); + } + + return nullptr; + } + + return new XPathExpression(Move(expression), mRecycler, aDocument); +} + +bool +XPathEvaluator::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector) +{ + return dom::XPathEvaluatorBinding::Wrap(aCx, this, aGivenProto, aReflector); +} + +/* static */ +already_AddRefed<XPathEvaluator> +XPathEvaluator::Constructor(const GlobalObject& aGlobal, + ErrorResult& rv) +{ + RefPtr<XPathEvaluator> newObj = new XPathEvaluator(nullptr); + return newObj.forget(); +} + +already_AddRefed<XPathResult> +XPathEvaluator::Evaluate(JSContext* aCx, const nsAString& aExpression, + nsINode& aContextNode, XPathNSResolver* aResolver, + uint16_t aType, JS::Handle<JSObject*> aResult, + ErrorResult& rv) +{ + nsAutoPtr<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 + (nsIAtom* aPrefix, int32_t& aID) +{ + aID = kNameSpaceID_Unknown; + + if (!mResolver && !mResolverNode) { + return NS_ERROR_DOM_NAMESPACE_ERR; + } + + nsAutoString prefix; + if (aPrefix) { + aPrefix->ToString(prefix); + } + + nsVoidableString 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 nsContentUtils::NameSpaceManager()->RegisterNameSpace(ns, aID); +} + +nsresult +XPathEvaluatorParseContext::resolveFunctionCall(nsIAtom* aName, + int32_t aID, + FunctionCall** aFn) +{ + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; +} + +bool XPathEvaluatorParseContext::caseInsensitiveNameTests() +{ + return !mIsCaseSensitive; +} + +void +XPathEvaluatorParseContext::SetErrorOffset(uint32_t aOffset) +{ +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/xslt/xpath/XPathEvaluator.h b/dom/xslt/xpath/XPathEvaluator.h new file mode 100644 index 000000000..e2a0ca46b --- /dev/null +++ b/dom/xslt/xpath/XPathEvaluator.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIDOMXPathEvaluator.h" +#include "nsIWeakReference.h" +#include "nsAutoPtr.h" +#include "nsString.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "nsIDocument.h" + +class nsINode; +class txIParseContext; +class txResultRecycler; + +namespace mozilla { +namespace dom { + +class GlobalObject; +class XPathExpression; +class XPathNSResolver; +class XPathResult; + +/** + * A class for evaluating an XPath expression string + */ +class XPathEvaluator final : public nsIDOMXPathEvaluator +{ + ~XPathEvaluator(); + +public: + explicit XPathEvaluator(nsIDocument* aDocument = nullptr); + + NS_DECL_ISUPPORTS + + // nsIDOMXPathEvaluator interface + NS_DECL_NSIDOMXPATHEVALUATOR + + // WebIDL API + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector); + nsIDocument* GetParentObject() + { + nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument); + return doc; + } + static already_AddRefed<XPathEvaluator> + Constructor(const GlobalObject& aGlobal, ErrorResult& rv); + XPathExpression* + CreateExpression(const nsAString& aExpression, + XPathNSResolver* aResolver, + ErrorResult& rv); + XPathExpression* + CreateExpression(const nsAString& aExpression, + nsINode* aResolver, + 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: + XPathExpression* + CreateExpression(const nsAString& aExpression, + txIParseContext* aContext, + nsIDocument* aDocument, + ErrorResult& aRv); + + nsWeakPtr mDocument; + RefPtr<txResultRecycler> mRecycler; +}; + +inline nsISupports* +ToSupports(XPathEvaluator* e) +{ + return static_cast<nsIDOMXPathEvaluator*>(e); +} + +/* d0a75e02-b5e7-11d5-a7f2-df109fb8a1fc */ +#define TRANSFORMIIX_XPATH_EVALUATOR_CID \ +{ 0xd0a75e02, 0xb5e7, 0x11d5, { 0xa7, 0xf2, 0xdf, 0x10, 0x9f, 0xb8, 0xa1, 0xfc } } + +} // 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 000000000..7ef2dba4e --- /dev/null +++ b/dom/xslt/xpath/XPathExpression.cpp @@ -0,0 +1,254 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/Move.h" +#include "XPathExpression.h" +#include "txExpr.h" +#include "txExprResult.h" +#include "txIXPathContext.h" +#include "nsError.h" +#include "nsIDOMCharacterData.h" +#include "nsDOMClassInfoID.h" +#include "nsIDOMDocument.h" +#include "XPathResult.h" +#include "txURIUtils.h" +#include "txXPathTreeWalker.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/XPathResultBinding.h" + +using mozilla::Move; + +namespace mozilla { +namespace 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(nsAutoPtr<Expr>&& aExpression, + txResultRecycler* aRecycler, + nsIDocument *aDocument) + : mExpression(Move(aExpression)), + mRecycler(aRecycler), + mDocument(do_GetWeakReference(aDocument)), + mCheckDocument(aDocument != nullptr) +{ +} + +XPathExpression::~XPathExpression() +{ +} + +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 (!nsContentUtils::LegacyIsCallerNativeCode() && + !nsContentUtils::CanCallerAccess(&aContextNode)) + { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + if (mCheckDocument) { + nsCOMPtr<nsIDocument> 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 == nsIDOMNode::TEXT_NODE || + nodeType == nsIDOMNode::CDATA_SECTION_NODE) { + nsCOMPtr<nsIDOMCharacterData> textNode = + do_QueryInterface(&aContextNode); + if (!textNode) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + uint32_t textLength; + textNode->GetLength(&textLength); + 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 != nsIDOMNode::DOCUMENT_NODE && + nodeType != nsIDOMNode::ELEMENT_NODE && + nodeType != nsIDOMNode::ATTRIBUTE_NODE && + nodeType != nsIDOMNode::COMMENT_NODE && + nodeType != nsIDOMNode::PROCESSING_INSTRUCTION_NODE) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + nsAutoPtr<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); + } + + aRv = xpathResult->SetExprResult(exprResult, resultType, &aContextNode); + + 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, + nsIAtom* aLName, + txAExprResult*& aResult) +{ + aResult = 0; + return NS_ERROR_INVALID_ARG; +} + +bool +EvalContextImpl::isStripSpaceAllowed(const txXPathNode& aNode) +{ + return false; +} + +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 dom +} // namespace mozilla diff --git a/dom/xslt/xpath/XPathExpression.h b/dom/xslt/xpath/XPathExpression.h new file mode 100644 index 000000000..bfe478bdb --- /dev/null +++ b/dom/xslt/xpath/XPathExpression.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIWeakReferenceUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/NonRefcountedDOMObject.h" +#include "mozilla/dom/XPathExpressionBinding.h" + +class Expr; +class nsIDocument; +class nsINode; +class txResultRecycler; + +namespace mozilla { +namespace dom { + +class XPathResult; + +/** + * A class for evaluating an XPath expression string + */ +class XPathExpression final : public NonRefcountedDOMObject +{ +public: + XPathExpression(nsAutoPtr<Expr>&& aExpression, txResultRecycler* aRecycler, + nsIDocument *aDocument); + ~XPathExpression(); + + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector) + { + return XPathExpressionBinding::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: + nsAutoPtr<Expr> mExpression; + RefPtr<txResultRecycler> mRecycler; + nsWeakPtr mDocument; + bool mCheckDocument; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_XPathExpression_h */ diff --git a/dom/xslt/xpath/XPathResult.cpp b/dom/xslt/xpath/XPathResult.cpp new file mode 100644 index 000000000..33315c942 --- /dev/null +++ b/dom/xslt/xpath/XPathResult.cpp @@ -0,0 +1,340 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsDOMClassInfoID.h" +#include "nsIDOMNode.h" +#include "nsIDOMDocument.h" +#include "nsDOMString.h" +#include "txXPathTreeWalker.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/dom/XPathResultBinding.h" + +namespace mozilla { +namespace 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), + mDocument(aResult.mDocument), + mContextNode(aResult.mContextNode), + mCurrentPos(0), + mResultType(aResult.mResultType), + mInvalidIteratorState(aResult.mInvalidIteratorState) +{ + if (mDocument) { + mDocument->AddMutationObserver(this); + } +} + +XPathResult::~XPathResult() +{ + RemoveObserver(); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(XPathResult) + +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(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_SCRIPT_OBJECTS + 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(nsIXPathResult) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPathResult) +NS_INTERFACE_MAP_END + +JSObject* +XPathResult::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return XPathResultBinding::Wrap(aCx, this, aGivenProto); +} + +void +XPathResult::RemoveObserver() +{ + if (mDocument) { + mDocument->RemoveMutationObserver(this); + } +} + +nsINode* +XPathResult::IterateNext(ErrorResult& aRv) +{ + if (!isIterator()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return nullptr; + } + + if (mDocument) { + mDocument->FlushPendingNotifications(Flush_Content); + } + + if (mInvalidIteratorState) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + return mResultNodes.SafeObjectAt(mCurrentPos++); +} + +void +XPathResult::NodeWillBeDestroyed(const nsINode* aNode) +{ + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + // Set to null to avoid unregistring unnecessarily + mDocument = nullptr; + Invalidate(aNode->IsNodeOfType(nsINode::eCONTENT) ? + static_cast<const nsIContent*>(aNode) : nullptr); +} + +void +XPathResult::CharacterDataChanged(nsIDocument* aDocument, + nsIContent *aContent, + CharacterDataChangeInfo* aInfo) +{ + Invalidate(aContent); +} + +void +XPathResult::AttributeChanged(nsIDocument* aDocument, + Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) +{ + Invalidate(aElement); +} + +void +XPathResult::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t aNewIndexInContainer) +{ + Invalidate(aContainer); +} + +void +XPathResult::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer) +{ + Invalidate(aContainer); +} + +void +XPathResult::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + Invalidate(aContainer); +} + +nsresult +XPathResult::SetExprResult(txAExprResult* aExprResult, uint16_t aResultType, + nsINode* aContextNode) +{ + 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. + return NS_ERROR_DOM_TYPE_ERR; + } + + 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.AppendObject(node); + } + + if (count > 0) { + mResult = nullptr; + } + } + + if (!isIterator()) { + return NS_OK; + } + + mInvalidIteratorState = false; + + if (mResultNodes.Count() > 0) { + // 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); + } + } + + return NS_OK; +} + +void +XPathResult::Invalidate(const nsIContent* aChangeRoot) +{ + nsCOMPtr<nsINode> contextNode = do_QueryReferent(mContextNode); + if (contextNode && aChangeRoot && aChangeRoot->GetBindingParent()) { + // If context node is in anonymous content, changes to + // non-anonymous content need to invalidate the XPathResult. If + // the changes are happening in a different anonymous trees, no + // invalidation should happen. + nsIContent* ctxBindingParent = nullptr; + if (contextNode->IsNodeOfType(nsINode::eCONTENT)) { + ctxBindingParent = + static_cast<nsIContent*>(contextNode.get()) + ->GetBindingParent(); + } else if (contextNode->IsNodeOfType(nsINode::eATTRIBUTE)) { + Element* parent = + static_cast<Attr*>(contextNode.get())->GetElement(); + if (parent) { + ctxBindingParent = parent->GetBindingParent(); + } + } + if (ctxBindingParent != aChangeRoot->GetBindingParent()) { + 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.Count() == 0) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + RefPtr<txNodeSet> nodeSet = new txNodeSet(nullptr); + if (!nodeSet) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t i, count = mResultNodes.Count(); + for (i = 0; i < count; ++i) { + nsAutoPtr<txXPathNode> node(txXPathNativeNode::createXPathNode(mResultNodes[i])); + if (!node) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nodeSet->append(*node); + } + + NS_ADDREF(*aExprResult = nodeSet); + + return NS_OK; +} + +nsresult +XPathResult::Clone(nsIXPathResult **aResult) +{ + *aResult = nullptr; + + if (isIterator() && mInvalidIteratorState) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + NS_ADDREF(*aResult = new XPathResult(*this)); + + return NS_OK; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/xslt/xpath/XPathResult.h b/dom/xslt/xpath/XPathResult.h new file mode 100644 index 000000000..9fe8125ba --- /dev/null +++ b/dom/xslt/xpath/XPathResult.h @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsWeakPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "nsString.h" +#include "nsWrapperCache.h" +#include "nsINode.h" + +class nsIDocument; +class txAExprResult; + +// {662f2c9a-c7cd-4cab-9349-e733df5a838c} +#define NS_IXPATHRESULT_IID \ +{ 0x662f2c9a, 0xc7cd, 0x4cab, {0x93, 0x49, 0xe7, 0x33, 0xdf, 0x5a, 0x83, 0x8c }} + +class nsIXPathResult : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPATHRESULT_IID) + virtual nsresult SetExprResult(txAExprResult *aExprResult, + uint16_t aResultType, + nsINode* aContextNode) = 0; + virtual nsresult GetExprResult(txAExprResult **aExprResult) = 0; + virtual nsresult Clone(nsIXPathResult **aResult) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPathResult, NS_IXPATHRESULT_IID) + +namespace mozilla { +namespace dom { + +/** + * A class for evaluating an XPath expression string + */ +class XPathResult final : public nsIXPathResult, + 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_SCRIPT_HOLDER_CLASS_AMBIGUOUS(XPathResult, + nsIXPathResult) + + static XPathResult* FromSupports(nsISupports* aSupports) + { + return static_cast<XPathResult*>(static_cast<nsIXPathResult*>(aSupports)); + } + + 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.Throw(NS_ERROR_DOM_TYPE_ERR); + return 0; + } + + return mNumberResult; + } + void GetStringValue(nsAString &aStringValue, ErrorResult& aRv) const + { + if (mResultType != STRING_TYPE) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return; + } + + aStringValue = mStringResult; + } + bool GetBooleanValue(ErrorResult& aRv) const + { + if (mResultType != BOOLEAN_TYPE) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return false; + } + + return mBooleanResult; + } + nsINode* GetSingleNodeValue(ErrorResult& aRv) const + { + if (!isNode()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return nullptr; + } + + return mResultNodes.SafeObjectAt(0); + } + bool InvalidIteratorState() const + { + return isIterator() && mInvalidIteratorState; + } + uint32_t GetSnapshotLength(ErrorResult& aRv) const + { + if (!isSnapshot()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return 0; + } + + return (uint32_t)mResultNodes.Count(); + } + nsINode* IterateNext(ErrorResult& aRv); + nsINode* SnapshotItem(uint32_t aIndex, ErrorResult& aRv) const + { + if (!isSnapshot()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return nullptr; + } + + return mResultNodes.SafeObjectAt(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 + + nsresult SetExprResult(txAExprResult *aExprResult, uint16_t aResultType, + nsINode* aContextNode) override; + nsresult GetExprResult(txAExprResult **aExprResult) override; + nsresult Clone(nsIXPathResult **aResult) override; + 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; + nsCOMArray<nsINode> mResultNodes; + nsCOMPtr<nsIDocument> mDocument; + nsWeakPtr mContextNode; + uint32_t mCurrentPos; + uint16_t mResultType; + bool mInvalidIteratorState; + bool mBooleanResult; + double mNumberResult; + nsString mStringResult; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_XPathResult_h */ diff --git a/dom/xslt/xpath/moz.build b/dom/xslt/xpath/moz.build new file mode 100644 index 000000000..1a7dbf89f --- /dev/null +++ b/dom/xslt/xpath/moz.build @@ -0,0 +1,62 @@ +# -*- 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 += [ + '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', + 'txNodeSetAdaptor.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', + 'txXPCOMExtensionFunction.cpp', + 'XPathEvaluator.cpp', + 'XPathExpression.cpp', + 'XPathResult.cpp', +] + +LOCAL_INCLUDES += [ + '../base', + '../xml', + '../xslt', +] + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/xslt/xpath/txBooleanExpr.cpp b/dom/xslt/xpath/txBooleanExpr.cpp new file mode 100644 index 000000000..450d2ab10 --- /dev/null +++ b/dom/xslt/xpath/txBooleanExpr.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 000000000..8eb91bb8e --- /dev/null +++ b/dom/xslt/xpath/txBooleanResult.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 000000000..82df8c8ee --- /dev/null +++ b/dom/xslt/xpath/txCoreFunctionCall.cpp @@ -0,0 +1,743 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.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 +{ + int8_t mMinParams; + int8_t mMaxParams; + Expr::ResultType mReturnType; + nsIAtom** 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 (mozilla::IsNaN(start) || + mozilla::IsInfinite(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 (mozilla::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 nsSubstring& 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 (mozilla::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 (mozilla::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 (mozilla::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, + txCaseInsensitiveStringComparator()) && + (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(NS_LITERAL_STRING("Internal error"), + 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); + } + } + + NS_NOTREACHED("how'd we get here?"); + return true; +} + +// static +bool +txCoreFunctionCall::getTypeFromAtom(nsIAtom* 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 +nsresult +txCoreFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + NS_ADDREF(*aAtom = *descriptTable[mType].mName); + return NS_OK; +} +#endif diff --git a/dom/xslt/xpath/txErrorExpr.cpp b/dom/xslt/xpath/txErrorExpr.cpp new file mode 100644 index 000000000..93c45c6a7 --- /dev/null +++ b/dom/xslt/xpath/txErrorExpr.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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(NS_LITERAL_STRING("Invalid expression evaluated")); +#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 000000000..01c1ff6d3 --- /dev/null +++ b/dom/xslt/xpath/txExpr.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 000000000..562fca7a3 --- /dev/null +++ b/dom/xslt/xpath/txExpr.h @@ -0,0 +1,1004 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.h" +#include "txExprResult.h" +#include "txCore.h" +#include "nsString.h" +#include "txOwningArray.h" +#include "nsIAtom.h" + +#ifdef DEBUG +#define TX_TO_STRING +#endif + +/* + XPath class definitions. + Much of this code was ported from XSL:P. +*/ + +class nsIAtom; +class txIMatchContext; +class txIEvalContext; +class txNodeSet; +class txXPathNode; + +/** + * A Base Class for all XSL Expressions +**/ +class Expr +{ +public: + Expr() + { + MOZ_COUNT_CTOR(Expr); + } + virtual ~Expr() + { + MOZ_COUNT_DTOR(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. + */ + typedef uint16_t ResultType; + 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; + } + + typedef uint16_t ContextSensitivity; + 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_GETNAMEATOM \ + nsresult getNameAtom(nsIAtom** aAtom) override; +#else +#define TX_DECL_TOSTRING +#define TX_DECL_GETNAMEATOM +#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_GETNAMEATOM \ + 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) \ +{ \ + NS_NOTREACHED("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; \ + } \ + return nullptr; \ +} \ +void \ +_class::setSubExprAt(uint32_t aPos, Expr* aExpr) \ +{ \ + NS_ASSERTION(aPos < 1, "setting bad subexpression index");\ + _Expr1.forget(); \ + _Expr1 = 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; \ + case 1: \ + return _Expr2; \ + default: \ + break; \ + } \ + return nullptr; \ +} \ +void \ +_class::setSubExprAt(uint32_t aPos, Expr* aExpr) \ +{ \ + NS_ASSERTION(aPos < 2, "setting bad subexpression index");\ + if (aPos == 0) { \ + _Expr1.forget(); \ + _Expr1 = aExpr; \ + } \ + else { \ + _Expr2.forget(); \ + _Expr2 = 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 + * @return nsresult indicating out of memory + */ + nsresult addParam(Expr* aExpr) + { + return mParams.AppendElement(aExpr) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + /** + * 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 + /* + * Returns the name of the function as an atom. + */ + virtual nsresult getNameAtom(nsIAtom** aAtom) = 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(nsIAtom* aName, eType& aType); + +private: + eType mType; +}; + + +/* + * This class represents a NodeTest as defined by the XPath spec + */ +class txNodeTest +{ +public: + txNodeTest() + { + MOZ_COUNT_CTOR(txNodeTest); + } + virtual ~txNodeTest() + { + MOZ_COUNT_DTOR(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 bool matches(const txXPathNode& aNode, + txIMatchContext* aContext) = 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 \ + bool matches(const txXPathNode& aNode, txIMatchContext* aContext) 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(nsIAtom* aPrefix, nsIAtom* aLocalName, int32_t aNSID, + uint16_t aNodeType); + + NodeTestType getType() override; + + TX_DECL_NODE_TEST + + nsCOMPtr<nsIAtom> mPrefix; + nsCOMPtr<nsIAtom> 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; + nsCOMPtr<nsIAtom> 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: + nsAutoPtr<txNodeTest> mNodeTest; + nsAutoPtr<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 + * @return nsresult indicating out of memory + */ + nsresult add(Expr* aExpr) + { + NS_ASSERTION(aExpr, "missing expression"); + return mPredicates.AppendElement(aExpr) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + 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; + } + void setNodeTest(txNodeTest* aNodeTest) + { + mNodeTest.forget(); + mNodeTest = aNodeTest; + } + LocationStepType getAxisIdentifier() + { + return mAxisIdentifier; + } + void setAxisIdentifier(LocationStepType aAxisIdentifier) + { + mAxisIdentifier = aAxisIdentifier; + } + +private: + void fromDescendants(const txXPathNode& aNode, txIMatchContext* aCs, + txNodeSet* aNodes); + void fromDescendantsRev(const txXPathNode& aNode, txIMatchContext* aCs, + txNodeSet* aNodes); + + nsAutoPtr<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: + nsAutoPtr<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: + nsAutoPtr<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: + nsAutoPtr<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: + nsAutoPtr<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); + + nsAutoPtr<Expr> mLeftExpr; + nsAutoPtr<Expr> mRightExpr; + RelationalExprType mOp; +}; + +/** + * VariableRefExpr + * Represents a variable reference ($refname) +**/ +class VariableRefExpr : public Expr { + +public: + + VariableRefExpr(nsIAtom* aPrefix, nsIAtom* aLocalName, int32_t aNSID); + + TX_DECL_EXPR + +private: + nsCOMPtr<nsIAtom> mPrefix; + nsCOMPtr<nsIAtom> 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 + * @return nsresult indicating out of memory + */ + nsresult 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: + nsAutoPtr<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 + * @return nsresult indicating out of memory + */ + nsresult addExpr(Expr* aExpr) + { + return mExpressions.AppendElement(aExpr) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + /** + * 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, nsIAtom* aPrefix, + nsIAtom* aLocalName); + + TX_DECL_EXPR + +private: + int32_t mNamespace; + nsCOMPtr<nsIAtom> mPrefix; + nsCOMPtr<nsIAtom> mLocalName; +}; + +/** + * + */ +class txUnionNodeTest : public txNodeTest +{ +public: + nsresult addNodeTest(txNodeTest* aNodeTest) + { + return mNodeTests.AppendElement(aNodeTest) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + 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 000000000..dc1616af0 --- /dev/null +++ b/dom/xslt/xpath/txExprLexer.cpp @@ -0,0 +1,367 @@ +/* -*- 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() + : 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) { + NS_NOTREACHED("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 nsASingleFragmentString& aPattern) +{ + iterator start, end; + start = 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 + 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)) { + 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 : + 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)) { + 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, NS_ERROR_OUT_OF_MEMORY); + 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 000000000..7497ca505 --- /dev/null +++ b/dom/xslt/xpath/txExprLexer.h @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 + */ + typedef nsASingleFragmentString::const_char_iterator 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 nsASingleFragmentString& aPattern); + + typedef nsASingleFragmentString::const_char_iterator 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 000000000..f8474004b --- /dev/null +++ b/dom/xslt/xpath/txExprParser.cpp @@ -0,0 +1,923 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "mozilla/Move.h" +#include "txExprParser.h" +#include "txExprLexer.h" +#include "txExpr.h" +#include "txStack.h" +#include "nsGkAtoms.h" +#include "nsError.h" +#include "txIXPathContext.h" +#include "txStringUtils.h" +#include "txXPathNode.h" +#include "txXPathOptimizer.h" + +using mozilla::Move; + +/** + * Creates an Attribute Value Template using the given value + * This should move to XSLProcessor class + */ +nsresult +txExprParser::createAVT(const nsSubstring& aAttrValue, + txIParseContext* aContext, + Expr** aResult) +{ + *aResult = nullptr; + nsresult rv = NS_OK; + nsAutoPtr<Expr> expr; + FunctionCall* concat = nullptr; + + nsAutoString literalString; + bool inExpr = false; + nsSubstring::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; + nsAutoPtr<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 = new 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 = Move(newExpr); + } + else { + if (!concat) { + concat = new txCoreFunctionCall(txCoreFunctionCall::CONCAT); + rv = concat->addParam(expr.forget()); + expr = concat; + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = concat->addParam(newExpr.forget()); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (inExpr) { + aContext->SetErrorOffset(iter - avtStart); + return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE; + } + + if (!expr) { + expr = new txLiteralExpr(EmptyString()); + } + + *aResult = expr.forget(); + + return NS_OK; +} + +nsresult +txExprParser::createExprInternal(const nsSubstring& 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)) { + nsASingleFragmentString::const_char_iterator start; + aExpression.BeginReading(start); + aContext->SetErrorOffset(lexer.mPosition - start + aSubStringPos); + return rv; + } + nsAutoPtr<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)) { + nsASingleFragmentString::const_char_iterator start; + aExpression.BeginReading(start); + aContext->SetErrorOffset(lexer.peek()->mStart - start + aSubStringPos); + + return rv; + } + + txXPathOptimizer optimizer; + Expr* newExpr = nullptr; + rv = optimizer.optimize(expr, &newExpr); + NS_ENSURE_SUCCESS(rv, rv); + + *aExpr = newExpr ? newExpr : expr.forget(); + + return NS_OK; +} + +/** + * Private Methods + */ + +/** + * Creates a binary Expr for the given operator + */ +nsresult +txExprParser::createBinaryExpr(nsAutoPtr<Expr>& left, nsAutoPtr<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, right, txNumberExpr::ADD); + break; + case Token::SUBTRACTION_OP: + expr = new txNumberExpr(left, right, txNumberExpr::SUBTRACT); + break; + case Token::DIVIDE_OP : + expr = new txNumberExpr(left, right, txNumberExpr::DIVIDE); + break; + case Token::MODULUS_OP : + expr = new txNumberExpr(left, right, txNumberExpr::MODULUS); + break; + case Token::MULTIPLY_OP : + expr = new txNumberExpr(left, right, txNumberExpr::MULTIPLY); + break; + + //-- case boolean ops + case Token::AND_OP: + expr = new BooleanExpr(left, right, BooleanExpr::AND); + break; + case Token::OR_OP: + expr = new BooleanExpr(left, right, BooleanExpr::OR); + break; + + //-- equality ops + case Token::EQUAL_OP : + expr = new RelationalExpr(left, right, RelationalExpr::EQUAL); + break; + case Token::NOT_EQUAL_OP : + expr = new RelationalExpr(left, right, RelationalExpr::NOT_EQUAL); + break; + + //-- relational ops + case Token::LESS_THAN_OP: + expr = new RelationalExpr(left, right, RelationalExpr::LESS_THAN); + break; + case Token::GREATER_THAN_OP: + expr = new RelationalExpr(left, right, + RelationalExpr::GREATER_THAN); + break; + case Token::LESS_OR_EQUAL_OP: + expr = new RelationalExpr(left, right, + RelationalExpr::LESS_OR_EQUAL); + break; + case Token::GREATER_OR_EQUAL_OP: + expr = new RelationalExpr(left, right, + RelationalExpr::GREATER_OR_EQUAL); + break; + + default: + NS_NOTREACHED("operator tokens should be already checked"); + return NS_ERROR_UNEXPECTED; + } + NS_ENSURE_TRUE(expr, NS_ERROR_OUT_OF_MEMORY); + + left.forget(); + right.forget(); + + *aResult = expr; + return NS_OK; +} + + +nsresult +txExprParser::createExpr(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult) +{ + *aResult = nullptr; + + nsresult rv = NS_OK; + bool done = false; + + nsAutoPtr<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) { + FunctionCall* fcExpr = new txCoreFunctionCall(txCoreFunctionCall::NUMBER); + + rv = fcExpr->addParam(expr); + if (NS_FAILED(rv)) + return rv; + expr.forget(); + expr = fcExpr; + } + else { + expr = new UnaryExpr(expr.forget()); + } + } + + 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 + nsAutoPtr<Expr> left(static_cast<Expr*>(exprs.pop())); + nsAutoPtr<Expr> right(Move(expr)); + rv = createBinaryExpr(left, right, + static_cast<Token*>(ops.pop()), + getter_Transfers(expr)); + if (NS_FAILED(rv)) { + done = true; + break; + } + } + exprs.push(expr.forget()); + ops.push(tok); + } + else { + done = true; + } + } + + while (NS_SUCCEEDED(rv) && !exprs.isEmpty()) { + nsAutoPtr<Expr> left(static_cast<Expr*>(exprs.pop())); + nsAutoPtr<Expr> right(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.forget(); + return NS_OK; +} + +nsresult +txExprParser::createFilterOrStep(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult) +{ + *aResult = nullptr; + + nsresult rv = NS_OK; + Token* tok = lexer.peek(); + + nsAutoPtr<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(); + { + nsCOMPtr<nsIAtom> prefix, lName; + int32_t nspace; + nsresult rv = resolveQName(tok->Value(), getter_AddRefs(prefix), + aContext, getter_AddRefs(lName), + nspace); + NS_ENSURE_SUCCESS(rv, rv); + expr = new 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 = new txLiteralExpr(tok->Value()); + break; + case Token::NUMBER: + { + lexer.nextToken(); + expr = new txLiteralExpr(txDouble::toDouble(tok->Value())); + break; + } + default: + return createLocationStep(lexer, aContext, aResult); + } + + if (lexer.peek()->mType == Token::L_BRACKET) { + nsAutoPtr<FilterExpr> filterExpr(new FilterExpr(expr)); + + expr.forget(); + + //-- handle predicates + rv = parsePredicates(filterExpr, lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + expr = filterExpr.forget(); + } + + *aResult = expr.forget(); + return NS_OK; +} + +nsresult +txExprParser::createFunctionCall(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult) +{ + *aResult = nullptr; + + nsAutoPtr<FunctionCall> fnCall; + + Token* tok = lexer.nextToken(); + NS_ASSERTION(tok->mType == Token::FUNCTION_NAME_AND_PAREN, + "FunctionCall expected"); + + //-- compare function names + nsCOMPtr<nsIAtom> 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 = new 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() + + NS_LITERAL_STRING(" not implemented.")); + + return NS_OK; + } + + NS_ENSURE_SUCCESS(rv, rv); + } + + //-- handle parametes + rv = parseParameters(fnCall, lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = fnCall.forget(); + return NS_OK; +} + +nsresult +txExprParser::createLocationStep(txExprLexer& lexer, txIParseContext* aContext, + Expr** aExpr) +{ + *aExpr = nullptr; + + //-- child axis is default + LocationStep::LocationStepType axisIdentifier = LocationStep::CHILD_AXIS; + nsAutoPtr<txNodeTest> nodeTest; + + //-- get Axis Identifier or AbbreviatedStep, if present + Token* tok = lexer.peek(); + switch (tok->mType) { + case Token::AXIS_IDENTIFIER: + { + //-- eat token + lexer.nextToken(); + nsCOMPtr<nsIAtom> 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 = new txNodeTypeTest(txNodeTypeTest::NODE_TYPE); + break; + case Token::SELF_NODE : + //-- eat token + lexer.nextToken(); + axisIdentifier = LocationStep::SELF_AXIS; + nodeTest = new 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 + nsCOMPtr<nsIAtom> prefix, lName; + int32_t nspace; + rv = resolveQName(tok->Value(), getter_AddRefs(prefix), + aContext, getter_AddRefs(lName), + nspace, true); + NS_ENSURE_SUCCESS(rv, rv); + + nodeTest = + new 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); + } + } + + nsAutoPtr<LocationStep> lstep(new LocationStep(nodeTest, axisIdentifier)); + + nodeTest.forget(); + + //-- handle predicates + rv = parsePredicates(lstep, lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + *aExpr = lstep.forget(); + return NS_OK; +} + +/** + * This method only handles comment(), text(), processing-instructing() + * and node() + */ +nsresult +txExprParser::createNodeTypeTest(txExprLexer& lexer, txNodeTest** aTest) +{ + *aTest = 0; + nsAutoPtr<txNodeTypeTest> nodeTest; + + Token* nodeTok = lexer.peek(); + + switch (nodeTok->mType) { + case Token::COMMENT_AND_PAREN: + lexer.nextToken(); + nodeTest = new txNodeTypeTest(txNodeTypeTest::COMMENT_TYPE); + break; + case Token::NODE_AND_PAREN: + lexer.nextToken(); + nodeTest = new txNodeTypeTest(txNodeTypeTest::NODE_TYPE); + break; + case Token::PROC_INST_AND_PAREN: + lexer.nextToken(); + nodeTest = new txNodeTypeTest(txNodeTypeTest::PI_TYPE); + break; + case Token::TEXT_AND_PAREN: + lexer.nextToken(); + nodeTest = new txNodeTypeTest(txNodeTypeTest::TEXT_TYPE); + break; + default: + return NS_ERROR_XPATH_NO_NODE_TYPE_TEST; + } + + NS_ENSURE_TRUE(nodeTest, NS_ERROR_OUT_OF_MEMORY); + + 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.forget(); + 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; + + nsAutoPtr<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.forget(); + return NS_OK; + } + } + else { + expr = new RootExpr(); + +#ifdef TX_TO_STRING + static_cast<RootExpr*>(expr.get())->setSerialize(false); +#endif + } + + // We have a PathExpr containing several steps + nsAutoPtr<PathExpr> pathExpr(new PathExpr()); + + rv = pathExpr->addExpr(expr, PathExpr::RELATIVE_OP); + NS_ENSURE_SUCCESS(rv, rv); + + expr.forget(); + + // 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.forget(); + return NS_OK; + } + lexer.nextToken(); + + rv = createLocationStep(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pathExpr->addExpr(expr, pathOp); + NS_ENSURE_SUCCESS(rv, rv); + + expr.forget(); + } + NS_NOTREACHED("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; + + nsAutoPtr<Expr> expr; + nsresult rv = createPathExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + if (lexer.peek()->mType != Token::UNION_OP) { + *aResult = expr.forget(); + return NS_OK; + } + + nsAutoPtr<UnionExpr> unionExpr(new UnionExpr()); + + rv = unionExpr->addExpr(expr); + NS_ENSURE_SUCCESS(rv, rv); + + expr.forget(); + + while (lexer.peek()->mType == Token::UNION_OP) { + lexer.nextToken(); //-- eat token + + rv = createPathExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = unionExpr->addExpr(expr.forget()); + NS_ENSURE_SUCCESS(rv, rv); + } + + *aResult = unionExpr.forget(); + 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) +{ + nsAutoPtr<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); + + rv = aPredicateList->add(expr); + NS_ENSURE_SUCCESS(rv, rv); + + expr.forget(); + + 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; + } + + nsAutoPtr<Expr> expr; + nsresult rv = NS_OK; + while (1) { + rv = createExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aFnCall) { + rv = aFnCall->addParam(expr.forget()); + NS_ENSURE_SUCCESS(rv, rv); + } + + 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; + } + } + + NS_NOTREACHED("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, + nsIAtom** aPrefix, txIParseContext* aContext, + nsIAtom** 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 000000000..238e03399 --- /dev/null +++ b/dom/xslt/xpath/txExprParser.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.h" +#include "nsString.h" + +class Expr; +class txExprLexer; +class FunctionCall; +class LocationStep; +class nsIAtom; +class PredicateList; +class Token; +class txIParseContext; +class txNodeTest; + +class txExprParser +{ +public: + + static nsresult createExpr(const nsSubstring& aExpression, + txIParseContext* aContext, Expr** aExpr) + { + return createExprInternal(aExpression, 0, aContext, aExpr); + } + + /** + * Creates an Attribute Value Template using the given value + */ + static nsresult createAVT(const nsSubstring& aAttrValue, + txIParseContext* aContext, + Expr** aResult); + + +protected: + static nsresult createExprInternal(const nsSubstring& aExpression, + uint32_t aSubStringPos, + txIParseContext* aContext, + Expr** aExpr); + /** + * Using nsAutoPtr& to optimize passing the ownership to the + * created binary expression objects. + */ + static nsresult createBinaryExpr(nsAutoPtr<Expr>& left, + nsAutoPtr<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, nsIAtom** aPrefix, + txIParseContext* aContext, + nsIAtom** 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 000000000..73e2f0772 --- /dev/null +++ b/dom/xslt/xpath/txExprResult.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.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() + { + } + + 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(); \ + virtual void stringValue(nsString& aString); \ + virtual const nsString* stringValuePointer(); \ + virtual bool booleanValue(); \ + virtual double numberValue(); \ + + +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 000000000..155210557 --- /dev/null +++ b/dom/xslt/xpath/txFilterExpr.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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" + +//-- 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; + } + return PredicateList::getSubExprAt(aPos - 1); +} + +void +FilterExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) +{ + if (aPos == 0) { + expr.forget(); + expr = 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 000000000..e2367a3ae --- /dev/null +++ b/dom/xslt/xpath/txForwardContext.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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, nsIAtom* aLName, + txAExprResult*& aResult) +{ + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getVariable(aNamespace, aLName, aResult); +} + +bool txForwardContext::isStripSpaceAllowed(const txXPathNode& aNode) +{ + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->isStripSpaceAllowed(aNode); +} + +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(NS_LITERAL_STRING("forwarded error: ")); + 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 000000000..19e06f8d1 --- /dev/null +++ b/dom/xslt/xpath/txForwardContext.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.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 000000000..aa8f51fc7 --- /dev/null +++ b/dom/xslt/xpath/txFunctionCall.cpp @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIAtom.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" + +/** + * 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(NS_LITERAL_STRING("NodeSet expected as argument"), 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(NS_LITERAL_STRING("invalid number of parameters for function")); +#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) +{ + nsCOMPtr<nsIAtom> functionNameAtom; + if (NS_FAILED(getNameAtom(getter_AddRefs(functionNameAtom)))) { + NS_ERROR("Can't get function name."); + return; + } + + + + aDest.Append(nsDependentAtomString(functionNameAtom) + + NS_LITERAL_STRING("(")); + for (uint32_t i = 0; i < mParams.Length(); ++i) { + if (i != 0) { + aDest.Append(char16_t(',')); + } + mParams[i]->toString(aDest); + } + aDest.Append(char16_t(')')); +} +#endif diff --git a/dom/xslt/xpath/txIXPathContext.h b/dom/xslt/xpath/txIXPathContext.h new file mode 100644 index 000000000..ed5f81a53 --- /dev/null +++ b/dom/xslt/xpath/txIXPathContext.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "txCore.h" + +class FunctionCall; +class nsIAtom; +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() + { + } + + /* + * Return a namespaceID for a given prefix. + */ + virtual nsresult resolveNamespacePrefix(nsIAtom* 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(nsIAtom* 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() + { + } + + /* + * Return the ExprResult associated with the variable with the + * given namespace and local name. + */ + virtual nsresult getVariable(int32_t aNamespace, nsIAtom* aLName, + txAExprResult*& aResult) = 0; + + /* + * Is whitespace stripping allowed for the given node? + * See http://www.w3.org/TR/xslt#strip + */ + virtual bool isStripSpaceAllowed(const txXPathNode& aNode) = 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, nsIAtom* aLName, \ + txAExprResult*& aResult); \ + bool isStripSpaceAllowed(const txXPathNode& aNode); \ + void* getPrivateContext(); \ + txResultRecycler* recycler(); \ + void receiveError(const nsAString& aMsg, nsresult aRes) + +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(); \ + uint32_t size(); \ + uint32_t position() + +#endif // __TX_I_XPATH_CONTEXT diff --git a/dom/xslt/xpath/txLiteralExpr.cpp b/dom/xslt/xpath/txLiteralExpr.cpp new file mode 100644 index 000000000..8c984dcd1 --- /dev/null +++ b/dom/xslt/xpath/txLiteralExpr.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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) +{ + NS_NOTREACHED("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 000000000..f945b750f --- /dev/null +++ b/dom/xslt/xpath/txLocationStep.cpp @@ -0,0 +1,325 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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; + } + MOZ_FALLTHROUGH; + } + case ANCESTOR_OR_SELF_AXIS: + { + nodes->setReverse(); + + do { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } while (walker.moveToParent()); + + break; + } + case ATTRIBUTE_AXIS: + { + if (!walker.moveToFirstAttribute()) { + break; + } + + do { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } while (walker.moveToNextAttribute()); + break; + } + case DESCENDANT_OR_SELF_AXIS: + { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + MOZ_FALLTHROUGH; + } + case DESCENDANT_AXIS: + { + fromDescendants(walker.getCurrentPosition(), aContext, nodes); + break; + } + case FOLLOWING_AXIS: + { + if (txXPathNodeUtils::isAttribute(walker.getCurrentPosition())) { + walker.moveToParent(); + fromDescendants(walker.getCurrentPosition(), aContext, nodes); + } + bool cont = true; + while (!walker.moveToNextSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + while (cont) { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + + fromDescendants(walker.getCurrentPosition(), aContext, nodes); + + while (!walker.moveToNextSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + } + break; + } + case FOLLOWING_SIBLING_AXIS: + { + while (walker.moveToNextSibling()) { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } + 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() && + mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + break; + } + case PRECEDING_AXIS: + { + nodes->setReverse(); + + bool cont = true; + while (!walker.moveToPreviousSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + while (cont) { + fromDescendantsRev(walker.getCurrentPosition(), aContext, nodes); + + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + + while (!walker.moveToPreviousSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + } + break; + } + case PRECEDING_SIBLING_AXIS: + { + nodes->setReverse(); + + while (walker.moveToPreviousSibling()) { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } + break; + } + case SELF_AXIS: + { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + break; + } + default: // Children Axis + { + if (!walker.moveToFirstChild()) { + break; + } + + do { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } 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; +} + +void LocationStep::fromDescendants(const txXPathNode& aNode, + txIMatchContext* aCs, + txNodeSet* aNodes) +{ + txXPathTreeWalker walker(aNode); + if (!walker.moveToFirstChild()) { + return; + } + + do { + const txXPathNode& child = walker.getCurrentPosition(); + if (mNodeTest->matches(child, aCs)) { + aNodes->append(child); + } + fromDescendants(child, aCs, aNodes); + } while (walker.moveToNextSibling()); +} + +void LocationStep::fromDescendantsRev(const txXPathNode& aNode, + txIMatchContext* aCs, + txNodeSet* aNodes) +{ + txXPathTreeWalker walker(aNode); + if (!walker.moveToLastChild()) { + return; + } + + do { + const txXPathNode& child = walker.getCurrentPosition(); + fromDescendantsRev(child, aCs, aNodes); + + if (mNodeTest->matches(child, aCs)) { + aNodes->append(child); + } + + } while (walker.moveToPreviousSibling()); +} + +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 000000000..1387097e1 --- /dev/null +++ b/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp @@ -0,0 +1,776 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIAtom.h" +#include "nsIAttribute.h" +#include "nsIDOMAttr.h" +#include "nsIDOMDocument.h" +#include "nsIDOMNode.h" +#include "nsIDOMElement.h" +#include "nsIDOMProcessingInstruction.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 "nsTArray.h" +#include "mozilla/dom/Attr.h" +#include "mozilla/dom/Element.h" +#include <stdint.h> +#include <algorithm> + +using namespace mozilla::dom; + +const uint32_t kUnknownIndex = uint32_t(-1); + +txXPathTreeWalker::txXPathTreeWalker(const txXPathTreeWalker& aOther) + : mPosition(aOther.mPosition), + mCurrentIndex(aOther.mCurrentIndex) +{ +} + +txXPathTreeWalker::txXPathTreeWalker(const txXPathNode& aNode) + : mPosition(aNode), + mCurrentIndex(kUnknownIndex) +{ +} + +void +txXPathTreeWalker::moveToRoot() +{ + if (mPosition.isDocument()) { + return; + } + + nsIDocument* root = mPosition.mNode->GetUncomposedDoc(); + if (root) { + mPosition.mIndex = txXPathNode::eDocument; + mPosition.mNode = root; + } + else { + nsINode *rootNode = mPosition.Root(); + + NS_ASSERTION(rootNode->IsNodeOfType(nsINode::eCONTENT), + "root of subtree wasn't an nsIContent"); + + mPosition.mIndex = txXPathNode::eContent; + mPosition.mNode = rootNode; + } + + mCurrentIndex = kUnknownIndex; + mDescendants.Clear(); +} + +bool +txXPathTreeWalker::moveToElementById(const nsAString& aID) +{ + if (aID.IsEmpty()) { + return false; + } + + nsIDocument* 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->IsNodeOfType(nsINode::eCONTENT), + "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; + mCurrentIndex = kUnknownIndex; + mDescendants.Clear(); + + 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"); + + uint32_t total = mPosition.Content()->GetAttrCount(); + if (aStartIndex >= total) { + return false; + } + + uint32_t index; + for (index = aStartIndex; index < total; ++index) { + const nsAttrName* name = mPosition.Content()->GetAttrNameAt(index); + + // We need to ignore XMLNS attributes. + if (name->NamespaceID() != kNameSpaceID_XMLNS) { + mPosition.mIndex = index; + + return true; + } + } + return false; +} + +bool +txXPathTreeWalker::moveToNamedAttribute(nsIAtom* aLocalName, int32_t aNSID) +{ + if (!mPosition.isContent()) { + return false; + } + + const nsAttrName* name; + uint32_t i; + for (i = 0; (name = mPosition.Content()->GetAttrNameAt(i)); ++i) { + if (name->Equals(aLocalName, aNSID)) { + mPosition.mIndex = i; + + return true; + } + } + return false; +} + +bool +txXPathTreeWalker::moveToFirstChild() +{ + if (mPosition.isAttribute()) { + return false; + } + + NS_ASSERTION(!mPosition.isDocument() || + (mCurrentIndex == kUnknownIndex && mDescendants.IsEmpty()), + "we shouldn't have any position info at the document"); + NS_ASSERTION(mCurrentIndex != kUnknownIndex || mDescendants.IsEmpty(), + "Index should be known if parents index are"); + + nsIContent* child = mPosition.mNode->GetFirstChild(); + if (!child) { + return false; + } + mPosition.mIndex = txXPathNode::eContent; + mPosition.mNode = child; + + if (mCurrentIndex != kUnknownIndex && + !mDescendants.AppendValue(mCurrentIndex)) { + mDescendants.Clear(); + } + mCurrentIndex = 0; + + return true; +} + +bool +txXPathTreeWalker::moveToLastChild() +{ + if (mPosition.isAttribute()) { + return false; + } + + NS_ASSERTION(!mPosition.isDocument() || + (mCurrentIndex == kUnknownIndex && mDescendants.IsEmpty()), + "we shouldn't have any position info at the document"); + NS_ASSERTION(mCurrentIndex != kUnknownIndex || mDescendants.IsEmpty(), + "Index should be known if parents index are"); + + uint32_t total = mPosition.mNode->GetChildCount(); + if (!total) { + return false; + } + mPosition.mNode = mPosition.mNode->GetLastChild(); + + if (mCurrentIndex != kUnknownIndex && + !mDescendants.AppendValue(mCurrentIndex)) { + mDescendants.Clear(); + } + mCurrentIndex = total - 1; + + return true; +} + +bool +txXPathTreeWalker::moveToNextSibling() +{ + if (!mPosition.isContent()) { + return false; + } + + return moveToSibling(1); +} + +bool +txXPathTreeWalker::moveToPreviousSibling() +{ + if (!mPosition.isContent()) { + return false; + } + + return moveToSibling(-1); +} + +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; + } + + uint32_t count = mDescendants.Length(); + if (count) { + mCurrentIndex = mDescendants.ValueAt(--count); + mDescendants.RemoveValueAt(count); + } + else { + mCurrentIndex = kUnknownIndex; + } + + mPosition.mIndex = mPosition.mNode->GetParent() ? + txXPathNode::eContent : txXPathNode::eDocument; + mPosition.mNode = parent; + + return true; +} + +bool +txXPathTreeWalker::moveToSibling(int32_t aDir) +{ + NS_ASSERTION(mPosition.isContent(), + "moveToSibling should only be called for content"); + + nsINode* parent = mPosition.mNode->GetParentNode(); + if (!parent) { + return false; + } + if (mCurrentIndex == kUnknownIndex) { + mCurrentIndex = parent->IndexOf(mPosition.mNode); + } + + // if mCurrentIndex is 0 we rely on GetChildAt returning null for an + // index of uint32_t(-1). + uint32_t newIndex = mCurrentIndex + aDir; + nsIContent* newChild = parent->GetChildAt(newIndex); + if (!newChild) { + return false; + } + + mPosition.mNode = newChild; + mCurrentIndex = newIndex; + + 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, nsIAtom* aLocalName, + int32_t aNSID, nsAString& aValue) +{ + if (aNode.isDocument() || aNode.isAttribute()) { + return false; + } + + return aNode.Content()->GetAttr(aNSID, aLocalName, aValue); +} + +/* static */ +already_AddRefed<nsIAtom> +txXPathNodeUtils::getLocalName(const txXPathNode& aNode) +{ + if (aNode.isDocument()) { + return nullptr; + } + + if (aNode.isContent()) { + if (aNode.mNode->IsElement()) { + nsCOMPtr<nsIAtom> localName = + aNode.Content()->NodeInfo()->NameAtom(); + return localName.forget(); + } + + if (aNode.mNode->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) { + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode.mNode); + nsAutoString target; + node->GetNodeName(target); + + return NS_Atomize(target); + } + + return nullptr; + } + + nsCOMPtr<nsIAtom> localName = aNode.Content()-> + GetAttrNameAt(aNode.mIndex)->LocalName(); + + return localName.forget(); +} + +nsIAtom* +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()->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->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) { + // PIs don't have a nodeinfo but do have a name + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode.mNode); + node->GetNodeName(aLocalName); + + return; + } + + aLocalName.Truncate(); + + return; + } + + aNode.Content()->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() == + nsIDOMNode::PROCESSING_INSTRUCTION_NODE) { + aName = aNode.Content()->NodeName(); + return; + } + + aName.Truncate(); + + return; + } + + aNode.Content()->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()->GetAttrNameAt(aNode.mIndex)->NamespaceID(); +} + +/* static */ +void +txXPathNodeUtils::getNamespaceURI(const txXPathNode& aNode, nsAString& aURI) +{ + nsContentUtils::NameSpaceManager()->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()->GetAttrNameAt(aNode.mIndex); + + if (aResult.IsEmpty()) { + aNode.Content()->GetAttr(name->NamespaceID(), name->LocalName(), + aResult); + } + else { + nsAutoString result; + aNode.Content()->GetAttr(name->NamespaceID(), name->LocalName(), + result); + aResult.Append(result); + } + + return; + } + + if (aNode.isDocument() || + aNode.mNode->IsElement() || + aNode.mNode->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT)) { + nsContentUtils::AppendNodeTextContent(aNode.mNode, true, aResult, + mozilla::fallible); + + return; + } + + 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%p"; +const char gPrintfFmtAttr[] = "id0x%p-%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. + nsIDocument* document = aNode.mNode->GetUncomposedDoc(); + nsIDocument* 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; + } + + return parent->IndexOf(node) < parent->IndexOf(otherNode) ? + -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; + } + + int32_t index = parent->IndexOf(node); + int32_t otherIndex = parent->IndexOf(otherNode); + NS_ASSERTION(index != otherIndex && index >= 0 && otherIndex >= 0, + "invalid index in compareTreePosition"); + + return index < otherIndex ? -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 == nsIDOMNode::ATTRIBUTE_NODE) { + nsCOMPtr<nsIAttribute> attr = do_QueryInterface(aNode); + NS_ASSERTION(attr, "doesn't implement nsIAttribute"); + + mozilla::dom::NodeInfo *nodeInfo = attr->NodeInfo(); + mozilla::dom::Element* parent = + static_cast<Attr*>(attr.get())->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 == nsIDOMNode::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(nsIDOMDocument* aDocument) +{ + nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument); + return new txXPathNode(document); +} + +/* static */ +nsINode* +txXPathNativeNode::getNode(const txXPathNode& aNode) +{ + if (!aNode.isAttribute()) { + return aNode.mNode; + } + + const nsAttrName* name = aNode.Content()->GetAttrNameAt(aNode.mIndex); + + nsAutoString namespaceURI; + nsContentUtils::NameSpaceManager()->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 */ +nsIDocument* +txXPathNativeNode::getDocument(const txXPathNode& aNode) +{ + NS_ASSERTION(aNode.isDocument(), + "Only call getDocument on nsIDocument wrappers!"); + return aNode.Document(); +} diff --git a/dom/xslt/xpath/txNameTest.cpp b/dom/xslt/xpath/txNameTest.cpp new file mode 100644 index 000000000..bad043282 --- /dev/null +++ b/dom/xslt/xpath/txNameTest.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIAtom.h" +#include "nsGkAtoms.h" +#include "txXPathTreeWalker.h" +#include "txIXPathContext.h" + +txNameTest::txNameTest(nsIAtom* aPrefix, nsIAtom* 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"); +} + +bool txNameTest::matches(const txXPathNode& aNode, txIMatchContext* aContext) +{ + if ((mNodeType == txXPathNodeType::ELEMENT_NODE && + !txXPathNodeUtils::isElement(aNode)) || + (mNodeType == txXPathNodeType::ATTRIBUTE_NODE && + !txXPathNodeUtils::isAttribute(aNode)) || + (mNodeType == txXPathNodeType::DOCUMENT_NODE && + !txXPathNodeUtils::isRoot(aNode))) { + return false; + } + + // Totally wild? + if (mLocalName == nsGkAtoms::_asterisk && !mPrefix) + return true; + + // Compare namespaces + if (mNamespace != txXPathNodeUtils::getNamespaceID(aNode) + && !(mNamespace == kNameSpaceID_None && + txXPathNodeUtils::isHTMLElementInHTMLDocument(aNode)) + ) + return false; + + // Name wild? + if (mLocalName == nsGkAtoms::_asterisk) + return true; + + // Compare local-names + return txXPathNodeUtils::localNameEquals(aNode, mLocalName); +} + +/* + * 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 000000000..e042b37af --- /dev/null +++ b/dom/xslt/xpath/txNamedAttributeStep.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIAtom.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" +#include "txExpr.h" +#include "txXPathTreeWalker.h" + +txNamedAttributeStep::txNamedAttributeStep(int32_t aNsID, + nsIAtom* aPrefix, + nsIAtom* 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 000000000..a55317e33 --- /dev/null +++ b/dom/xslt/xpath/txNodeSet.cpp @@ -0,0 +1,622 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsMemory.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))); + if (!newArr) { + return false; + } + + 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 000000000..bd33be628 --- /dev/null +++ b/dom/xslt/xpath/txNodeSet.h @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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; + } + } + + typedef void (*transferOp) (txXPathNode* aDest, const txXPathNode* aStart, + const txXPathNode* aEnd); + typedef void (*destroyOp) (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/txNodeSetAdaptor.cpp b/dom/xslt/xpath/txNodeSetAdaptor.cpp new file mode 100644 index 000000000..2656c8dde --- /dev/null +++ b/dom/xslt/xpath/txNodeSetAdaptor.cpp @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "txNodeSetAdaptor.h" +#include "txXPathTreeWalker.h" + +txNodeSetAdaptor::txNodeSetAdaptor() + : txXPathObjectAdaptor(), + mWritable(true) +{ +} + +txNodeSetAdaptor::txNodeSetAdaptor(txNodeSet *aNodeSet) + : txXPathObjectAdaptor(aNodeSet), + mWritable(false) +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(txNodeSetAdaptor, txXPathObjectAdaptor, txINodeSet) + +nsresult +txNodeSetAdaptor::Init() +{ + if (!mValue) { + mValue = new txNodeSet(nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP +txNodeSetAdaptor::Item(uint32_t aIndex, nsIDOMNode **aResult) +{ + *aResult = nullptr; + + if (aIndex > (uint32_t)NodeSet()->size()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + return txXPathNativeNode::getNode(NodeSet()->get(aIndex), aResult); +} + +NS_IMETHODIMP +txNodeSetAdaptor::ItemAsNumber(uint32_t aIndex, double *aResult) +{ + if (aIndex > (uint32_t)NodeSet()->size()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsAutoString result; + txXPathNodeUtils::appendNodeValue(NodeSet()->get(aIndex), result); + + *aResult = txDouble::toDouble(result); + + return NS_OK; +} + +NS_IMETHODIMP +txNodeSetAdaptor::ItemAsString(uint32_t aIndex, nsAString &aResult) +{ + if (aIndex > (uint32_t)NodeSet()->size()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + txXPathNodeUtils::appendNodeValue(NodeSet()->get(aIndex), aResult); + + return NS_OK; +} + +NS_IMETHODIMP +txNodeSetAdaptor::GetLength(uint32_t *aLength) +{ + *aLength = (uint32_t)NodeSet()->size(); + + return NS_OK; +} + +NS_IMETHODIMP +txNodeSetAdaptor::Add(nsIDOMNode *aNode) +{ + NS_ENSURE_TRUE(mWritable, NS_ERROR_FAILURE); + + nsAutoPtr<txXPathNode> node(txXPathNativeNode::createXPathNode(aNode, + true)); + + return node ? NodeSet()->add(*node) : NS_ERROR_OUT_OF_MEMORY; +} diff --git a/dom/xslt/xpath/txNodeSetAdaptor.h b/dom/xslt/xpath/txNodeSetAdaptor.h new file mode 100644 index 000000000..4648dca5c --- /dev/null +++ b/dom/xslt/xpath/txNodeSetAdaptor.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 txNodeSetAdaptor_h__ +#define txNodeSetAdaptor_h__ + +#include "txINodeSet.h" +#include "txNodeSet.h" +#include "txXPathObjectAdaptor.h" + +/** + * Implements an XPCOM wrapper around an XPath NodeSet. + */ + +class txNodeSetAdaptor : public txXPathObjectAdaptor, + public txINodeSet +{ +public: + txNodeSetAdaptor(); + explicit txNodeSetAdaptor(txNodeSet* aNodeSet); + + nsresult Init(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_TXINODESET + +protected: + ~txNodeSetAdaptor() {} + +private: + txNodeSet* NodeSet() + { + return static_cast<txNodeSet*>(mValue.get()); + } + + bool mWritable; +}; + +#endif // txNodeSetAdaptor_h__ diff --git a/dom/xslt/xpath/txNodeSetContext.cpp b/dom/xslt/xpath/txNodeSetContext.cpp new file mode 100644 index 000000000..7d98391fd --- /dev/null +++ b/dom/xslt/xpath/txNodeSetContext.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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, nsIAtom* aLName, + txAExprResult*& aResult) +{ + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getVariable(aNamespace, aLName, aResult); +} + +bool txNodeSetContext::isStripSpaceAllowed(const txXPathNode& aNode) +{ + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->isStripSpaceAllowed(aNode); +} + +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(NS_LITERAL_STRING("forwarded error: ")); + 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 000000000..2c133cf33 --- /dev/null +++ b/dom/xslt/xpath/txNodeSetContext.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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" +#include "nsAutoPtr.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 000000000..650a1ae5f --- /dev/null +++ b/dom/xslt/xpath/txNodeTypeTest.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIAtom.h" +#include "txIXPathContext.h" +#include "txXPathTreeWalker.h" + +bool txNodeTypeTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext) +{ + switch (mNodeType) { + case COMMENT_TYPE: + { + return txXPathNodeUtils::isComment(aNode); + } + case TEXT_TYPE: + { + return txXPathNodeUtils::isText(aNode) && + !aContext->isStripSpaceAllowed(aNode); + } + case PI_TYPE: + { + return txXPathNodeUtils::isProcessingInstruction(aNode) && + (!mNodeName || + txXPathNodeUtils::localNameEquals(aNode, mNodeName)); + } + case NODE_TYPE: + { + return !txXPathNodeUtils::isText(aNode) || + !aContext->isStripSpaceAllowed(aNode); + } + } + return true; +} + +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 000000000..1defe905b --- /dev/null +++ b/dom/xslt/xpath/txNumberExpr.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 (mozilla::IsNaN(rightDbl)) + result = mozilla::UnspecifiedNaN<double>(); + else +#endif + if (leftDbl == 0 || mozilla::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 (!mozilla::IsInfinite(leftDbl) && mozilla::IsInfinite(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 000000000..0bcbecdc5 --- /dev/null +++ b/dom/xslt/xpath/txNumberResult.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 && !mozilla::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 000000000..bcff47e9f --- /dev/null +++ b/dom/xslt/xpath/txPathExpr.cpp @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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" + + //------------/ + //- PathExpr -/ +//------------/ + +/** + * Adds the Expr to this PathExpr + * @param expr the Expr to add to this PathExpr +**/ +nsresult +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(); + if (!pxi) { + return NS_ERROR_OUT_OF_MEMORY; + } + pxi->expr = aExpr; + pxi->pathOp = aPathOp; + + return NS_OK; +} + + //-----------------------------/ + //- 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, 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 = aContext->isStripSpaceAllowed(aNode); + + 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"); + mItems[aPos].expr.forget(); + mItems[aPos].expr = 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 000000000..937d4148f --- /dev/null +++ b/dom/xslt/xpath/txPredicateList.cpp @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 000000000..4726f48f2 --- /dev/null +++ b/dom/xslt/xpath/txPredicatedNodeTest.cpp @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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"); +} + +bool +txPredicatedNodeTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext) +{ + if (!mNodeTest->matches(aNode, aContext)) { + return false; + } + + txSingleNodeContext context(aNode, aContext); + RefPtr<txAExprResult> res; + nsresult rv = mPredicate->evaluate(&context, getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv, false); + + return res->booleanValue(); +} + +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 000000000..5621ede7b --- /dev/null +++ b/dom/xslt/xpath/txRelationalExpr.cpp @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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: + { + NS_NOTREACHED("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 000000000..774a0345c --- /dev/null +++ b/dom/xslt/xpath/txResultRecycler.cpp @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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); + + nsresult rv = NS_OK; + switch (aResult->getResultType()) { + case txAExprResult::STRING: + { + rv = mStringResults.push(static_cast<StringResult*>(aResult)); + if (NS_FAILED(rv)) { + delete aResult; + } + return; + } + case txAExprResult::NODESET: + { + static_cast<txNodeSet*>(aResult)->clear(); + rv = mNodeSetResults.push(static_cast<txNodeSet*>(aResult)); + if (NS_FAILED(rv)) { + delete aResult; + } + return; + } + case txAExprResult::NUMBER: + { + rv = mNumberResults.push(static_cast<NumberResult*>(aResult)); + if (NS_FAILED(rv)) { + delete 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 000000000..eec7d75e8 --- /dev/null +++ b/dom/xslt/xpath/txResultRecycler.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.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 000000000..f6ef6abcb --- /dev/null +++ b/dom/xslt/xpath/txRootExpr.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 000000000..e66083d80 --- /dev/null +++ b/dom/xslt/xpath/txSingleNodeContext.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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, nsIAtom* aLName, + txAExprResult*& aResult) override + { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getVariable(aNamespace, aLName, aResult); + } + + bool isStripSpaceAllowed(const txXPathNode& aNode) override + { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->isStripSpaceAllowed(aNode); + } + + 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(NS_LITERAL_STRING("forwarded error: ")); + 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 000000000..f3fbe4eaf --- /dev/null +++ b/dom/xslt/xpath/txStringResult.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 000000000..95682b5b2 --- /dev/null +++ b/dom/xslt/xpath/txUnaryExpr.cpp @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 000000000..0bde2c38a --- /dev/null +++ b/dom/xslt/xpath/txUnionExpr.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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" + + //-------------/ + //- 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& dest) +{ + uint32_t i; + for (i = 0; i < mExpressions.Length(); ++i) { + if (i > 0) + dest.AppendLiteral(" | "); + mExpressions[i]->toString(dest); + } +} +#endif diff --git a/dom/xslt/xpath/txUnionNodeTest.cpp b/dom/xslt/xpath/txUnionNodeTest.cpp new file mode 100644 index 000000000..421ea680c --- /dev/null +++ b/dom/xslt/xpath/txUnionNodeTest.cpp @@ -0,0 +1,60 @@ +/* -*- 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" + +bool +txUnionNodeTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext) +{ + uint32_t i, len = mNodeTests.Length(); + for (i = 0; i < len; ++i) { + if (mNodeTests[i]->matches(aNode, aContext)) { + return true; + } + } + + return false; +} + +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('('); + for (uint32_t i = 0; i < mNodeTests.Length(); ++i) { + if (i != 0) { + aDest.AppendLiteral(" | "); + } + mNodeTests[i]->toString(aDest); + } + aDest.Append(')'); +} +#endif diff --git a/dom/xslt/xpath/txVariableRefExpr.cpp b/dom/xslt/xpath/txVariableRefExpr.cpp new file mode 100644 index 000000000..813e17b8f --- /dev/null +++ b/dom/xslt/xpath/txVariableRefExpr.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIAtom.h" +#include "txNodeSet.h" +#include "nsGkAtoms.h" +#include "txIXPathContext.h" + + //-------------------/ + //- VariableRefExpr -/ +//-------------------/ + +/** + * Creates a VariableRefExpr with the given variable name +**/ +VariableRefExpr::VariableRefExpr(nsIAtom* aPrefix, nsIAtom* 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/txXPCOMExtensionFunction.cpp b/dom/xslt/xpath/txXPCOMExtensionFunction.cpp new file mode 100644 index 000000000..4913702aa --- /dev/null +++ b/dom/xslt/xpath/txXPCOMExtensionFunction.cpp @@ -0,0 +1,617 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsDependentString.h" +#include "nsIAtom.h" +#include "nsIInterfaceInfoManager.h" +#include "nsServiceManagerUtils.h" +#include "txExpr.h" +#include "txIFunctionEvaluationContext.h" +#include "txIXPathContext.h" +#include "txNodeSetAdaptor.h" +#include "txXPathTreeWalker.h" +#include "xptcall.h" +#include "txXPathObjectAdaptor.h" +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsIClassInfo.h" +#include "nsIInterfaceInfo.h" +#include "js/RootingAPI.h" + +NS_IMPL_ISUPPORTS(txXPathObjectAdaptor, txIXPathObject) + +class txFunctionEvaluationContext final : public txIFunctionEvaluationContext +{ +public: + txFunctionEvaluationContext(txIEvalContext *aContext, nsISupports *aState); + + NS_DECL_ISUPPORTS + NS_DECL_TXIFUNCTIONEVALUATIONCONTEXT + + void ClearContext() + { + mContext = nullptr; + } + +private: + ~txFunctionEvaluationContext() {} + + txIEvalContext *mContext; + nsCOMPtr<nsISupports> mState; +}; + +txFunctionEvaluationContext::txFunctionEvaluationContext(txIEvalContext *aContext, + nsISupports *aState) + : mContext(aContext), + mState(aState) +{ +} + +NS_IMPL_ISUPPORTS(txFunctionEvaluationContext, txIFunctionEvaluationContext) + +NS_IMETHODIMP +txFunctionEvaluationContext::GetPosition(uint32_t *aPosition) +{ + NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); + + *aPosition = mContext->position(); + + return NS_OK; +} + +NS_IMETHODIMP +txFunctionEvaluationContext::GetSize(uint32_t *aSize) +{ + NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); + + *aSize = mContext->size(); + + return NS_OK; +} + +NS_IMETHODIMP +txFunctionEvaluationContext::GetContextNode(nsIDOMNode **aNode) +{ + NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); + + return txXPathNativeNode::getNode(mContext->getContextNode(), aNode); +} + +NS_IMETHODIMP +txFunctionEvaluationContext::GetState(nsISupports **aState) +{ + NS_IF_ADDREF(*aState = mState); + + return NS_OK; +} + +enum txArgumentType { + eBOOLEAN = nsXPTType::T_BOOL, + eNUMBER = nsXPTType::T_DOUBLE, + eSTRING = nsXPTType::T_DOMSTRING, + eNODESET, + eCONTEXT, + eOBJECT, + eUNKNOWN +}; + +class txXPCOMExtensionFunctionCall : public FunctionCall +{ +public: + txXPCOMExtensionFunctionCall(nsISupports *aHelper, const nsIID &aIID, + uint16_t aMethodIndex, +#ifdef TX_TO_STRING + nsIAtom *aName, +#endif + nsISupports *aState); + + TX_DECL_FUNCTION + +private: + txArgumentType GetParamType(const nsXPTParamInfo &aParam, + nsIInterfaceInfo *aInfo); + + nsCOMPtr<nsISupports> mHelper; + nsIID mIID; + uint16_t mMethodIndex; +#ifdef TX_TO_STRING + nsCOMPtr<nsIAtom> mName; +#endif + nsCOMPtr<nsISupports> mState; +}; + +txXPCOMExtensionFunctionCall::txXPCOMExtensionFunctionCall(nsISupports *aHelper, + const nsIID &aIID, + uint16_t aMethodIndex, +#ifdef TX_TO_STRING + nsIAtom *aName, +#endif + nsISupports *aState) + : mHelper(aHelper), + mIID(aIID), + mMethodIndex(aMethodIndex), +#ifdef TX_TO_STRING + mName(aName), +#endif + mState(aState) +{ +} + +class txInterfacesArrayHolder +{ +public: + txInterfacesArrayHolder(nsIID **aArray, uint32_t aCount) : mArray(aArray), + mCount(aCount) + { + } + ~txInterfacesArrayHolder() + { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mArray); + } + +private: + nsIID **mArray; + uint32_t mCount; +}; + +static nsresult +LookupFunction(const char *aContractID, nsIAtom* aName, nsIID &aIID, + uint16_t &aMethodIndex, nsISupports **aHelper) +{ + nsresult rv; + nsCOMPtr<nsISupports> helper = do_GetService(aContractID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(helper, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInterfaceInfoManager> iim = + do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(iim, NS_ERROR_FAILURE); + + nsIID** iidArray = nullptr; + uint32_t iidCount = 0; + rv = classInfo->GetInterfaces(&iidCount, &iidArray); + NS_ENSURE_SUCCESS(rv, rv); + + txInterfacesArrayHolder holder(iidArray, iidCount); + + // Remove any minus signs and uppercase the following letter (so + // foo-bar becomes fooBar). Note that if there are any names that already + // have uppercase letters they might cause false matches (both fooBar and + // foo-bar matching fooBar). + const char16_t *name = aName->GetUTF16String(); + nsAutoCString methodName; + char16_t letter; + bool upperNext = false; + while ((letter = *name)) { + if (letter == '-') { + upperNext = true; + } + else { + MOZ_ASSERT(nsCRT::IsAscii(letter), + "invalid static_cast coming up"); + methodName.Append(upperNext ? + nsCRT::ToUpper(static_cast<char>(letter)) : + letter); + upperNext = false; + } + ++name; + } + + uint32_t i; + for (i = 0; i < iidCount; ++i) { + nsIID *iid = iidArray[i]; + + nsCOMPtr<nsIInterfaceInfo> info; + rv = iim->GetInfoForIID(iid, getter_AddRefs(info)); + NS_ENSURE_SUCCESS(rv, rv); + + uint16_t methodIndex; + const nsXPTMethodInfo *methodInfo; + rv = info->GetMethodInfoForName(methodName.get(), &methodIndex, + &methodInfo); + if (NS_SUCCEEDED(rv)) { + // Exclude notxpcom and hidden. Also check that we have at least a + // return value (the xpidl compiler ensures that that return value + // is the last argument). + uint8_t paramCount = methodInfo->GetParamCount(); + if (methodInfo->IsNotXPCOM() || methodInfo->IsHidden() || + paramCount == 0 || + !methodInfo->GetParam(paramCount - 1).IsRetval()) { + return NS_ERROR_FAILURE; + } + + aIID = *iid; + aMethodIndex = methodIndex; + return helper->QueryInterface(aIID, (void**)aHelper); + } + } + + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; +} + +/* static */ +nsresult +TX_ResolveFunctionCallXPCOM(const nsCString &aContractID, int32_t aNamespaceID, + nsIAtom* aName, nsISupports *aState, + FunctionCall **aFunction) +{ + nsIID iid; + uint16_t methodIndex = 0; + nsCOMPtr<nsISupports> helper; + + nsresult rv = LookupFunction(aContractID.get(), aName, iid, methodIndex, + getter_AddRefs(helper)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aFunction) { + return NS_OK; + } + + *aFunction = new txXPCOMExtensionFunctionCall(helper, iid, methodIndex, +#ifdef TX_TO_STRING + aName, +#endif + aState); + return NS_OK; +} + +txArgumentType +txXPCOMExtensionFunctionCall::GetParamType(const nsXPTParamInfo &aParam, + nsIInterfaceInfo *aInfo) +{ + uint8_t tag = aParam.GetType().TagPart(); + switch (tag) { + case nsXPTType::T_BOOL: + case nsXPTType::T_DOUBLE: + case nsXPTType::T_DOMSTRING: + { + return txArgumentType(tag); + } + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: + { + nsIID iid; + aInfo->GetIIDForParamNoAlloc(mMethodIndex, &aParam, &iid); + if (iid.Equals(NS_GET_IID(txINodeSet))) { + return eNODESET; + } + if (iid.Equals(NS_GET_IID(txIFunctionEvaluationContext))) { + return eCONTEXT; + } + if (iid.Equals(NS_GET_IID(txIXPathObject))) { + return eOBJECT; + } + return eUNKNOWN; + } + default: + { + // XXX Error! + return eUNKNOWN; + } + } +} + +class txParamArrayHolder +{ +public: + txParamArrayHolder() + : mCount(0) + { + } + txParamArrayHolder(txParamArrayHolder&& rhs) + : mArray(mozilla::Move(rhs.mArray)) + , mCount(rhs.mCount) + { + rhs.mCount = 0; + } + ~txParamArrayHolder(); + + bool Init(uint8_t aCount); + operator nsXPTCVariant*() const + { + return mArray.get(); + } + + void trace(JSTracer* trc) { + for (uint8_t i = 0; i < mCount; ++i) { + if (mArray[i].type == nsXPTType::T_JSVAL) { + JS::UnsafeTraceRoot(trc, &mArray[i].val.j, "txParam value"); + } + } + } + +private: + mozilla::UniquePtr<nsXPTCVariant[]> mArray; + uint8_t mCount; +}; + +txParamArrayHolder::~txParamArrayHolder() +{ + uint8_t i; + for (i = 0; i < mCount; ++i) { + nsXPTCVariant &variant = mArray[i]; + if (variant.DoesValNeedCleanup()) { + if (variant.type.TagPart() == nsXPTType::T_DOMSTRING) + delete (nsAString*)variant.val.p; + else { + MOZ_ASSERT(variant.type.TagPart() == nsXPTType::T_INTERFACE || + variant.type.TagPart() == nsXPTType::T_INTERFACE_IS, + "We only support cleanup of strings and interfaces " + "here, and this looks like neither!"); + static_cast<nsISupports*>(variant.val.p)->Release(); + } + } + } +} + +bool +txParamArrayHolder::Init(uint8_t aCount) +{ + mCount = aCount; + mArray = mozilla::MakeUnique<nsXPTCVariant[]>(mCount); + if (!mArray) { + return false; + } + + memset(mArray.get(), 0, mCount * sizeof(nsXPTCVariant)); + + return true; +} + +nsresult +txXPCOMExtensionFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) +{ + nsCOMPtr<nsIInterfaceInfoManager> iim = + do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(iim, NS_ERROR_FAILURE); + + nsCOMPtr<nsIInterfaceInfo> info; + nsresult rv = iim->GetInfoForIID(&mIID, getter_AddRefs(info)); + NS_ENSURE_SUCCESS(rv, rv); + + const nsXPTMethodInfo *methodInfo; + rv = info->GetMethodInfo(mMethodIndex, &methodInfo); + NS_ENSURE_SUCCESS(rv, rv); + + uint8_t paramCount = methodInfo->GetParamCount(); + uint8_t inArgs = paramCount - 1; + + JS::Rooted<txParamArrayHolder> invokeParams(mozilla::dom::RootingCx()); + if (!invokeParams.get().Init(paramCount)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(0); + txArgumentType type = GetParamType(paramInfo, info); + if (type == eUNKNOWN) { + return NS_ERROR_FAILURE; + } + + txFunctionEvaluationContext *context; + uint32_t paramStart = 0; + if (type == eCONTEXT) { + if (paramInfo.IsOut()) { + // We don't support out values. + return NS_ERROR_FAILURE; + } + + // Create context wrapper. + context = new txFunctionEvaluationContext(aContext, mState); + + nsXPTCVariant &invokeParam = invokeParams.get()[0]; + invokeParam.type = paramInfo.GetType(); + invokeParam.SetValNeedsCleanup(); + NS_ADDREF((txIFunctionEvaluationContext*&)invokeParam.val.p = context); + + // Skip first argument, since it's the context. + paramStart = 1; + } + else { + context = nullptr; + } + + // XXX varargs + if (!requireParams(inArgs - paramStart, inArgs - paramStart, aContext)) { + return NS_ERROR_FAILURE; + } + + uint32_t i; + for (i = paramStart; i < inArgs; ++i) { + Expr* expr = mParams[i - paramStart]; + + const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(i); + txArgumentType type = GetParamType(paramInfo, info); + if (type == eUNKNOWN) { + return NS_ERROR_FAILURE; + } + + nsXPTCVariant &invokeParam = invokeParams.get()[i]; + if (paramInfo.IsOut()) { + // We don't support out values. + return NS_ERROR_FAILURE; + } + + invokeParam.type = paramInfo.GetType(); + switch (type) { + case eNODESET: + { + RefPtr<txNodeSet> nodes; + rv = evaluateToNodeSet(expr, aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + txNodeSetAdaptor *adaptor = new txNodeSetAdaptor(nodes); + if (!adaptor) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<txINodeSet> nodeSet = adaptor; + rv = adaptor->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + invokeParam.SetValNeedsCleanup(); + nodeSet.swap((txINodeSet*&)invokeParam.val.p); + break; + } + case eBOOLEAN: + { + rv = expr->evaluateToBool(aContext, invokeParam.val.b); + NS_ENSURE_SUCCESS(rv, rv); + + break; + } + case eNUMBER: + { + double dbl; + rv = evaluateToNumber(mParams[0], aContext, &dbl); + NS_ENSURE_SUCCESS(rv, rv); + + invokeParam.val.d = dbl; + break; + } + case eSTRING: + { + nsString *value = new nsString(); + if (!value) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = expr->evaluateToString(aContext, *value); + NS_ENSURE_SUCCESS(rv, rv); + + invokeParam.SetValNeedsCleanup(); + invokeParam.val.p = value; + break; + } + case eOBJECT: + { + RefPtr<txAExprResult> exprRes; + rv = expr->evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<txIXPathObject> adaptor = + new txXPathObjectAdaptor(exprRes); + if (!adaptor) { + return NS_ERROR_OUT_OF_MEMORY; + } + + invokeParam.SetValNeedsCleanup(); + adaptor.swap((txIXPathObject*&)invokeParam.val.p); + break; + } + case eCONTEXT: + case eUNKNOWN: + { + // We only support passing the context as the *first* argument. + return NS_ERROR_FAILURE; + } + } + } + + const nsXPTParamInfo &returnInfo = methodInfo->GetParam(inArgs); + txArgumentType returnType = GetParamType(returnInfo, info); + if (returnType == eUNKNOWN) { + return NS_ERROR_FAILURE; + } + + nsXPTCVariant &returnParam = invokeParams.get()[inArgs]; + returnParam.type = returnInfo.GetType(); + if (returnType == eSTRING) { + nsString *value = new nsString(); + returnParam.SetValNeedsCleanup(); + returnParam.val.p = value; + } + else { + returnParam.SetIndirect(); + if (returnType == eNODESET || returnType == eOBJECT) { + returnParam.SetValNeedsCleanup(); + } + } + + rv = NS_InvokeByIndex(mHelper, mMethodIndex, paramCount, invokeParams.get()); + + // In case someone is holding on to the txFunctionEvaluationContext which + // could thus stay alive longer than this function. + if (context) { + context->ClearContext(); + } + + NS_ENSURE_SUCCESS(rv, rv); + + switch (returnType) { + case eNODESET: + { + txINodeSet* nodeSet = static_cast<txINodeSet*>(returnParam.val.p); + nsCOMPtr<txIXPathObject> object = do_QueryInterface(nodeSet, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*aResult = object->GetResult()); + + return NS_OK; + } + case eBOOLEAN: + { + aContext->recycler()->getBoolResult(returnParam.val.b, aResult); + + return NS_OK; + } + case eNUMBER: + { + return aContext->recycler()->getNumberResult(returnParam.val.d, + aResult); + } + case eSTRING: + { + nsString *returned = static_cast<nsString*> + (returnParam.val.p); + return aContext->recycler()->getStringResult(*returned, aResult); + } + case eOBJECT: + { + txIXPathObject *object = + static_cast<txIXPathObject*>(returnParam.val.p); + + NS_ADDREF(*aResult = object->GetResult()); + + return NS_OK; + } + default: + { + // Huh? + return NS_ERROR_FAILURE; + } + } +} + +Expr::ResultType +txXPCOMExtensionFunctionCall::getReturnType() +{ + // 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 ANY_RESULT; +} + +bool +txXPCOMExtensionFunctionCall::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 +nsresult +txXPCOMExtensionFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + NS_ADDREF(*aAtom = mName); + + return NS_OK; +} +#endif diff --git a/dom/xslt/xpath/txXPathNode.h b/dom/xslt/xpath/txXPathNode.h new file mode 100644 index 000000000..53b6b6d84 --- /dev/null +++ b/dom/xslt/xpath/txXPathNode.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAutoPtr.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMNode.h" +#include "nsNameSpaceManager.h" +#include "nsContentUtils.h" // For NameSpaceManager(). + +typedef nsIDOMNode txXPathNodeType; + +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(nsIDocument* 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) + { + nsINode *ancestor, *root = aNode; + while ((ancestor = root->GetParentNode())) { + root = ancestor; + } + return root; + } + 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); + } + nsIDocument* Document() const + { + NS_ASSERTION(isDocument(), "wrong type"); + return static_cast<nsIDocument*>(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; + nsContentUtils::NameSpaceManager()-> + RegisterNameSpace(aNamespaceURI, namespaceID); + return namespaceID; +} + +/* static */ +inline nsresult +txNamespaceManager::getNamespaceURI(const int32_t aID, nsAString& aResult) +{ + return nsContentUtils::NameSpaceManager()-> + 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/txXPathObjectAdaptor.h b/dom/xslt/xpath/txXPathObjectAdaptor.h new file mode 100644 index 000000000..1df5abb78 --- /dev/null +++ b/dom/xslt/xpath/txXPathObjectAdaptor.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 txXPathObjectAdaptor_h__ +#define txXPathObjectAdaptor_h__ + +#include "txExprResult.h" +#include "txINodeSet.h" +#include "txIXPathObject.h" + +/** + * Implements an XPCOM wrapper around XPath data types boolean, number, string, + * or nodeset. + */ + +class txXPathObjectAdaptor : public txIXPathObject +{ +public: + explicit txXPathObjectAdaptor(txAExprResult* aValue) : mValue(aValue) + { + NS_ASSERTION(aValue, + "Don't create a txXPathObjectAdaptor if you don't have a " + "txAExprResult"); + } + + NS_DECL_ISUPPORTS + + NS_IMETHOD_(txAExprResult*) GetResult() override + { + return mValue; + } + +protected: + txXPathObjectAdaptor() : mValue(nullptr) + { + } + + virtual ~txXPathObjectAdaptor() {} + + RefPtr<txAExprResult> mValue; +}; + +#endif // txXPathObjectAdaptor_h__ diff --git a/dom/xslt/xpath/txXPathOptimizer.cpp b/dom/xslt/xpath/txXPathOptimizer.cpp new file mode 100644 index 000000000..756dd253d --- /dev/null +++ b/dom/xslt/xpath/txXPathOptimizer.cpp @@ -0,0 +1,282 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIAtom.h" +#include "nsGkAtoms.h" +#include "txXPathNode.h" +#include "txExpr.h" +#include "txIXPathContext.h" + +class txEarlyEvalContext : public txIEvalContext +{ +public: + explicit txEarlyEvalContext(txResultRecycler* aRecycler) + : mRecycler(aRecycler) + { + } + + // txIEvalContext + nsresult getVariable(int32_t aNamespace, nsIAtom* aLName, + txAExprResult*& aResult) + { + MOZ_CRASH("shouldn't depend on this context"); + } + bool isStripSpaceAllowed(const txXPathNode& aNode) + { + MOZ_CRASH("shouldn't depend on this context"); + } + void* getPrivateContext() + { + MOZ_CRASH("shouldn't depend on this context"); + } + txResultRecycler* recycler() + { + return mRecycler; + } + void receiveError(const nsAString& aMsg, nsresult aRes) + { + } + const txXPathNode& getContextNode() + { + MOZ_CRASH("shouldn't depend on this context"); + } + uint32_t size() + { + MOZ_CRASH("shouldn't depend on this context"); + } + uint32_t position() + { + MOZ_CRASH("shouldn't depend on this context"); + } + +private: + txResultRecycler* mRecycler; +}; + + +nsresult +txXPathOptimizer::optimize(Expr* aInExpr, Expr** aOutExpr) +{ + *aOutExpr = nullptr; + nsresult rv = NS_OK; + + // 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. + rv = aInExpr->evaluate(&context, getter_AddRefs(exprRes)); + if (NS_SUCCEEDED(rv)) { + *aOutExpr = new txLiteralExpr(exprRes); + } + + return NS_OK; + } + + // Then optimize sub expressions + uint32_t i = 0; + Expr* subExpr; + while ((subExpr = aInExpr->getSubExprAt(i))) { + Expr* newExpr = nullptr; + rv = optimize(subExpr, &newExpr); + NS_ENSURE_SUCCESS(rv, rv); + if (newExpr) { + delete subExpr; + aInExpr->setSubExprAt(i, newExpr); + } + + ++i; + } + + // Finally see if current expression can be optimized + switch (exprType) { + case Expr::LOCATIONSTEP_EXPR: + return optimizeStep(aInExpr, aOutExpr); + + case Expr::PATH_EXPR: + return optimizePath(aInExpr, aOutExpr); + + case Expr::UNION_EXPR: + return optimizeUnion(aInExpr, aOutExpr); + + default: + break; + } + + return NS_OK; +} + +nsresult +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 NS_OK; // 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); + } + + return NS_OK; +} + +nsresult +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(); + txNodeTypeTest* typeTest; + if (test->getType() == txNodeTest::NODETYPE_TEST && + (typeTest = 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 NS_OK; + } + + // Just delete the '.' step and leave the rest of the PathExpr + path->deleteExprAt(0); + } + } + } + + return NS_OK; +} + +nsresult +txXPathOptimizer::optimizeUnion(Expr* aInExpr, Expr** aOutExpr) +{ + UnionExpr* uni = static_cast<UnionExpr*>(aInExpr); + + // Check for expressions like "foo | bar" and + // "descendant::foo | descendant::bar" + + nsresult rv; + 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) { + nsAutoPtr<txNodeTest> owner(unionTest = new txUnionNodeTest); + rv = unionTest->addNodeTest(currentStep->getNodeTest()); + NS_ENSURE_SUCCESS(rv, rv); + + currentStep->setNodeTest(unionTest); + owner.forget(); + } + + // Merge the nodetest into the union + rv = unionTest->addNodeTest(step->getNodeTest()); + NS_ENSURE_SUCCESS(rv, rv); + + 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 NS_OK; + } + } + + return NS_OK; +} diff --git a/dom/xslt/xpath/txXPathOptimizer.h b/dom/xslt/xpath/txXPathOptimizer.h new file mode 100644 index 000000000..a933ac3ad --- /dev/null +++ b/dom/xslt/xpath/txXPathOptimizer.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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. + */ + nsresult optimize(Expr* aInExpr, Expr** aOutExpr); + +private: + // Helper methods for optimizing specific classes + nsresult optimizeStep(Expr* aInExpr, Expr** aOutExpr); + nsresult optimizePath(Expr* aInExpr, Expr** aOutExpr); + nsresult 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 000000000..99a07ffe7 --- /dev/null +++ b/dom/xslt/xpath/txXPathTreeWalker.h @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 nsIAtom; +class nsIDOMDocument; + +class txUint32Array : public nsTArray<uint32_t> +{ +public: + bool AppendValue(uint32_t aValue) + { + return AppendElement(aValue) != nullptr; + } + bool RemoveValueAt(uint32_t aIndex) + { + if (aIndex < Length()) { + RemoveElementAt(aIndex); + } + return true; + } + uint32_t ValueAt(uint32_t aIndex) const + { + return (aIndex < Length()) ? ElementAt(aIndex) : 0; + } +}; + +class txXPathTreeWalker +{ +public: + txXPathTreeWalker(const txXPathTreeWalker& aOther); + explicit txXPathTreeWalker(const txXPathNode& aNode); + + bool getAttr(nsIAtom* 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(nsIAtom* 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); + bool moveToSibling(int32_t aDir); + + uint32_t mCurrentIndex; + txUint32Array mDescendants; +}; + +class txXPathNodeUtils +{ +public: + static bool getAttr(const txXPathNode& aNode, nsIAtom* aLocalName, + int32_t aNSID, nsAString& aValue); + static already_AddRefed<nsIAtom> getLocalName(const txXPathNode& aNode); + static nsIAtom* 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, + nsIAtom* 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(nsIDOMNode* aNode, + bool aKeepRootAlive = false) + { + nsCOMPtr<nsINode> node = do_QueryInterface(aNode); + return createXPathNode(node, aKeepRootAlive); + } + static txXPathNode* createXPathNode(nsIContent* aContent, + bool aKeepRootAlive = false); + static txXPathNode* createXPathNode(nsIDOMDocument* aDocument); + static nsINode* getNode(const txXPathNode& aNode); + static nsresult getNode(const txXPathNode& aNode, nsIDOMNode** aResult) + { + return CallQueryInterface(getNode(aNode), aResult); + } + static nsIContent* getContent(const txXPathNode& aNode); + static nsIDocument* 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(nsIAtom* 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); + } + + mCurrentIndex = aWalker.mCurrentIndex; + mDescendants.Clear(); +} + +inline bool +txXPathTreeWalker::isOnNode(const txXPathNode& aNode) const +{ + return (mPosition == aNode); +} + +/* static */ +inline int32_t +txXPathNodeUtils::getUniqueIdentifier(const txXPathNode& aNode) +{ + NS_PRECONDITION(!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, + nsIAtom* aLocalName) +{ + if (aNode.isContent() && + aNode.Content()->IsElement()) { + return aNode.Content()->NodeInfo()->Equals(aLocalName); + } + + nsCOMPtr<nsIAtom> 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()->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION); +} + +/* static */ +inline bool +txXPathNodeUtils::isComment(const txXPathNode& aNode) +{ + return aNode.isContent() && + aNode.Content()->IsNodeOfType(nsINode::eCOMMENT); +} + +/* static */ +inline bool +txXPathNodeUtils::isText(const txXPathNode& aNode) +{ + return aNode.isContent() && + aNode.Content()->IsNodeOfType(nsINode::eTEXT); +} + +#endif /* txXPathTreeWalker_h__ */ |