diff options
Diffstat (limited to 'dom/xslt/xslt/txXSLTPatterns.cpp')
-rw-r--r-- | dom/xslt/xslt/txXSLTPatterns.cpp | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/dom/xslt/xslt/txXSLTPatterns.cpp b/dom/xslt/xslt/txXSLTPatterns.cpp new file mode 100644 index 0000000000..dc1b81e210 --- /dev/null +++ b/dom/xslt/xslt/txXSLTPatterns.cpp @@ -0,0 +1,530 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/FloatingPoint.h" + +#include "nsReadableUtils.h" +#include "txExecutionState.h" +#include "txXSLTPatterns.h" +#include "txNodeSetContext.h" +#include "txForwardContext.h" +#include "txXMLUtils.h" +#include "txXSLTFunctions.h" +#include "nsWhitespaceTokenizer.h" +#include "nsIContent.h" + +using mozilla::UniquePtr; +using mozilla::Unused; +using mozilla::WrapUnique; + +/* + * Returns the default priority of this Pattern. + * UnionPatterns don't like this. + * This should be called on the simple patterns. + */ +double txUnionPattern::getDefaultPriority() { + NS_ERROR("Don't call getDefaultPriority on txUnionPattern"); + return mozilla::UnspecifiedNaN<double>(); +} + +/* + * Determines whether this Pattern matches the given node within + * the given context + * This should be called on the simple patterns for xsl:template, + * but is fine for xsl:key and xsl:number + */ +nsresult txUnionPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + uint32_t i, len = mLocPathPatterns.Length(); + for (i = 0; i < len; ++i) { + nsresult rv = mLocPathPatterns[i]->matches(aNode, aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (aMatched) { + aMatched = true; + + return NS_OK; + } + } + + aMatched = false; + + return NS_OK; +} + +txPattern::Type txUnionPattern::getType() { return UNION_PATTERN; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txUnionPattern) +txPattern* txUnionPattern::getSubPatternAt(uint32_t aPos) { + return mLocPathPatterns.SafeElementAt(aPos); +} + +void txUnionPattern::setSubPatternAt(uint32_t aPos, txPattern* aPattern) { + NS_ASSERTION(aPos < mLocPathPatterns.Length(), + "setting bad subexpression index"); + mLocPathPatterns[aPos] = aPattern; +} + +#ifdef TX_TO_STRING +void txUnionPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txUnionPattern{"); +# endif + StringJoinAppend( + aDest, u" | "_ns, mLocPathPatterns, + [](nsAString& dest, txPattern* pattern) { pattern->toString(dest); }); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * LocationPathPattern + * + * a list of step patterns, can start with id or key + * (dealt with by the parser) + */ + +void txLocPathPattern::addStep(txPattern* aPattern, bool isChild) { + Step* step = mSteps.AppendElement(); + step->pattern = WrapUnique(aPattern); + step->isChild = isChild; +} + +nsresult txLocPathPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + NS_ASSERTION(mSteps.Length() > 1, "Internal error"); + + /* + * The idea is to split up a path into blocks separated by descendant + * operators. For example "foo/bar//baz/bop//ying/yang" is split up into + * three blocks. The "ying/yang" block is handled by the first while-loop + * and the "foo/bar" and "baz/bop" blocks are handled by the second + * while-loop. + * A block is considered matched when we find a list of ancestors that + * match the block. If there are more than one list of ancestors that + * match a block we only need to find the one furthermost down in the + * tree. + */ + + uint32_t pos = mSteps.Length(); + Step* step = &mSteps[--pos]; + nsresult rv = step->pattern->matches(aNode, aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aMatched) { + return NS_OK; + } + + txXPathTreeWalker walker(aNode); + bool hasParent = walker.moveToParent(); + + while (step->isChild) { + if (!pos) { + aMatched = true; + + return NS_OK; // all steps matched + } + + if (!hasParent) { + // no more ancestors + aMatched = false; + + return NS_OK; + } + + step = &mSteps[--pos]; + rv = + step->pattern->matches(walker.getCurrentPosition(), aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aMatched) { + // no match + return NS_OK; + } + + hasParent = walker.moveToParent(); + } + + // We have at least one // path separator + txXPathTreeWalker blockWalker(walker); + uint32_t blockPos = pos; + + while (pos) { + if (!hasParent) { + aMatched = false; // There are more steps in the current block + // than ancestors of the tested node + return NS_OK; + } + + step = &mSteps[--pos]; + bool matched; + rv = step->pattern->matches(walker.getCurrentPosition(), aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (!matched) { + // Didn't match. We restart at beginning of block using a new + // start node + pos = blockPos; + hasParent = blockWalker.moveToParent(); + walker.moveTo(blockWalker); + } else { + hasParent = walker.moveToParent(); + if (!step->isChild) { + // We've matched an entire block. Set new start pos and start node + blockPos = pos; + blockWalker.moveTo(walker); + } + } + } + + aMatched = true; + + return NS_OK; +} // txLocPathPattern::matches + +double txLocPathPattern::getDefaultPriority() { + NS_ASSERTION(mSteps.Length() > 1, "Internal error"); + + return 0.5; +} + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txLocPathPattern) +txPattern* txLocPathPattern::getSubPatternAt(uint32_t aPos) { + return aPos < mSteps.Length() ? mSteps[aPos].pattern.get() : nullptr; +} + +void txLocPathPattern::setSubPatternAt(uint32_t aPos, txPattern* aPattern) { + NS_ASSERTION(aPos < mSteps.Length(), "setting bad subexpression index"); + Step* step = &mSteps[aPos]; + Unused << step->pattern.release(); + step->pattern = WrapUnique(aPattern); +} + +#ifdef TX_TO_STRING +void txLocPathPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txLocPathPattern{"); +# endif + for (uint32_t i = 0; i < mSteps.Length(); ++i) { + if (i != 0) { + if (mSteps[i].isChild) + aDest.Append(char16_t('/')); + else + aDest.AppendLiteral("//"); + } + mSteps[i].pattern->toString(aDest); + } +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * txRootPattern + * + * a txPattern matching the document node, or '/' + */ + +nsresult txRootPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + aMatched = txXPathNodeUtils::isRoot(aNode); + + return NS_OK; +} + +double txRootPattern::getDefaultPriority() { return 0.5; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txRootPattern) +TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txRootPattern) + +#ifdef TX_TO_STRING +void txRootPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txRootPattern{"); +# endif + if (mSerialize) aDest.Append(char16_t('/')); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * txIdPattern + * + * txIdPattern matches if the given node has a ID attribute with one + * of the space delimited values. + * This looks like the id() function, but may only have LITERALs as + * argument. + */ +txIdPattern::txIdPattern(const nsAString& aString) { + nsWhitespaceTokenizer tokenizer(aString); + while (tokenizer.hasMoreTokens()) { + // this can fail, XXX move to a Init(aString) method + RefPtr<nsAtom> atom = NS_Atomize(tokenizer.nextToken()); + mIds.AppendElement(atom); + } +} + +nsresult txIdPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + if (!txXPathNodeUtils::isElement(aNode)) { + aMatched = false; + + return NS_OK; + } + + // Get a ID attribute, if there is + nsIContent* content = txXPathNativeNode::getContent(aNode); + NS_ASSERTION(content, "a Element without nsIContent"); + + nsAtom* id = content->GetID(); + aMatched = id && mIds.IndexOf(id) != mIds.NoIndex; + + return NS_OK; +} + +double txIdPattern::getDefaultPriority() { return 0.5; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txIdPattern) +TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txIdPattern) + +#ifdef TX_TO_STRING +void txIdPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txIdPattern{"); +# endif + aDest.AppendLiteral("id('"); + uint32_t k, count = mIds.Length() - 1; + for (k = 0; k < count; ++k) { + nsAutoString str; + mIds[k]->ToString(str); + aDest.Append(str); + aDest.Append(char16_t(' ')); + } + nsAutoString str; + mIds[count]->ToString(str); + aDest.Append(str); + aDest.AppendLiteral("')"); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * txKeyPattern + * + * txKeyPattern matches if the given node is in the evalation of + * the key() function + * This resembles the key() function, but may only have LITERALs as + * argument. + */ + +nsresult txKeyPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + txExecutionState* es = (txExecutionState*)aContext->getPrivateContext(); + UniquePtr<txXPathNode> contextDoc(txXPathNodeUtils::getOwnerDocument(aNode)); + NS_ENSURE_TRUE(contextDoc, NS_ERROR_FAILURE); + + RefPtr<txNodeSet> nodes; + nsresult rv = + es->getKeyNodes(mName, *contextDoc, mValue, true, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + aMatched = nodes->contains(aNode); + + return NS_OK; +} + +double txKeyPattern::getDefaultPriority() { return 0.5; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txKeyPattern) +TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txKeyPattern) + +#ifdef TX_TO_STRING +void txKeyPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txKeyPattern{"); +# endif + aDest.AppendLiteral("key('"); + nsAutoString tmp; + if (mPrefix) { + mPrefix->ToString(tmp); + aDest.Append(tmp); + aDest.Append(char16_t(':')); + } + mName.mLocalName->ToString(tmp); + aDest.Append(tmp); + aDest.AppendLiteral(", "); + aDest.Append(mValue); + aDest.AppendLiteral("')"); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * txStepPattern + * + * a txPattern to hold the NodeTest and the Predicates of a StepPattern + */ + +nsresult txStepPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + NS_ASSERTION(mNodeTest, "Internal error"); + + nsresult rv = mNodeTest->matches(aNode, aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aMatched) { + return NS_OK; + } + + txXPathTreeWalker walker(aNode); + if ((!mIsAttr && + txXPathNodeUtils::isAttribute(walker.getCurrentPosition())) || + !walker.moveToParent()) { + aMatched = false; + + return NS_OK; + } + + if (isEmpty()) { + aMatched = true; + + return NS_OK; + } + + /* + * Evaluate Predicates + * + * Copy all siblings/attributes matching mNodeTest to nodes + * Up to the last Predicate do + * Foreach node in nodes + * evaluate Predicate with node as context node + * if the result is a number, check the context position, + * otherwise convert to bool + * if result is true, copy node to newNodes + * if aNode is not member of newNodes, return false + * nodes = newNodes + * + * For the last Predicate, evaluate Predicate with aNode as + * context node, if the result is a number, check the position, + * otherwise return the result converted to boolean + */ + + // Create the context node set for evaluating the predicates + RefPtr<txNodeSet> nodes; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasNext = + mIsAttr ? walker.moveToFirstAttribute() : walker.moveToFirstChild(); + while (hasNext) { + bool matched; + rv = mNodeTest->matches(walker.getCurrentPosition(), aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + nodes->append(walker.getCurrentPosition()); + } + hasNext = + mIsAttr ? walker.moveToNextAttribute() : walker.moveToNextSibling(); + } + + Expr* predicate = mPredicates[0]; + RefPtr<txNodeSet> newNodes; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(newNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t i, predLen = mPredicates.Length(); + for (i = 1; i < predLen; ++i) { + newNodes->clear(); + bool contextIsInPredicate = false; + txNodeSetContext predContext(nodes, aContext); + while (predContext.hasNext()) { + predContext.next(); + RefPtr<txAExprResult> exprResult; + rv = predicate->evaluate(&predContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + switch (exprResult->getResultType()) { + case txAExprResult::NUMBER: + // handle default, [position() == numberValue()] + if ((double)predContext.position() == exprResult->numberValue()) { + const txXPathNode& tmp = predContext.getContextNode(); + if (tmp == aNode) contextIsInPredicate = true; + newNodes->append(tmp); + } + break; + default: + if (exprResult->booleanValue()) { + const txXPathNode& tmp = predContext.getContextNode(); + if (tmp == aNode) contextIsInPredicate = true; + newNodes->append(tmp); + } + break; + } + } + // Move new NodeSet to the current one + nodes->clear(); + nodes->append(*newNodes); + if (!contextIsInPredicate) { + aMatched = false; + + return NS_OK; + } + predicate = mPredicates[i]; + } + txForwardContext evalContext(aContext, aNode, nodes); + RefPtr<txAExprResult> exprResult; + rv = predicate->evaluate(&evalContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + if (exprResult->getResultType() == txAExprResult::NUMBER) { + // handle default, [position() == numberValue()] + aMatched = ((double)evalContext.position() == exprResult->numberValue()); + } else { + aMatched = exprResult->booleanValue(); + } + + return NS_OK; +} // matches + +double txStepPattern::getDefaultPriority() { + if (isEmpty()) return mNodeTest->getDefaultPriority(); + return 0.5; +} + +txPattern::Type txStepPattern::getType() { return STEP_PATTERN; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txStepPattern) +Expr* txStepPattern::getSubExprAt(uint32_t aPos) { + return PredicateList::getSubExprAt(aPos); +} + +void txStepPattern::setSubExprAt(uint32_t aPos, Expr* aExpr) { + PredicateList::setSubExprAt(aPos, aExpr); +} + +#ifdef TX_TO_STRING +void txStepPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txStepPattern{"); +# endif + if (mIsAttr) aDest.Append(char16_t('@')); + if (mNodeTest) mNodeTest->toString(aDest); + + PredicateList::toString(aDest); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif |