michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "txExpr.h" michael@0: #include "txNodeSet.h" michael@0: #include "txNodeSetContext.h" michael@0: #include "txSingleNodeContext.h" michael@0: #include "txXMLUtils.h" michael@0: #include "txXPathTreeWalker.h" michael@0: michael@0: //------------/ michael@0: //- PathExpr -/ michael@0: //------------/ michael@0: michael@0: /** michael@0: * Adds the Expr to this PathExpr michael@0: * @param expr the Expr to add to this PathExpr michael@0: **/ michael@0: nsresult michael@0: PathExpr::addExpr(Expr* aExpr, PathOperator aPathOp) michael@0: { michael@0: NS_ASSERTION(!mItems.IsEmpty() || aPathOp == RELATIVE_OP, michael@0: "First step has to be relative in PathExpr"); michael@0: PathExprItem* pxi = mItems.AppendElement(); michael@0: if (!pxi) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: pxi->expr = aExpr; michael@0: pxi->pathOp = aPathOp; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //-----------------------------/ michael@0: //- Virtual methods from Expr -/ michael@0: //-----------------------------/ michael@0: michael@0: /** michael@0: * Evaluates this Expr based on the given context node and processor state michael@0: * @param context the context node for evaluation of this Expr michael@0: * @param ps the ContextState containing the stack information needed michael@0: * for evaluation michael@0: * @return the result of the evaluation michael@0: **/ michael@0: nsresult michael@0: PathExpr::evaluate(txIEvalContext* aContext, txAExprResult** aResult) michael@0: { michael@0: *aResult = nullptr; michael@0: michael@0: // We need to evaluate the first step with the current context since it michael@0: // can depend on the context size and position. For example: michael@0: // key('books', concat('book', position())) michael@0: nsRefPtr res; michael@0: nsresult rv = mItems[0].expr->evaluate(aContext, getter_AddRefs(res)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ENSURE_TRUE(res->getResultType() == txAExprResult::NODESET, michael@0: NS_ERROR_XSLT_NODESET_EXPECTED); michael@0: michael@0: nsRefPtr nodes = static_cast michael@0: (static_cast michael@0: (res)); michael@0: if (nodes->isEmpty()) { michael@0: res.swap(*aResult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: res = nullptr; // To allow recycling michael@0: michael@0: // Evaluate remaining steps michael@0: uint32_t i, len = mItems.Length(); michael@0: for (i = 1; i < len; ++i) { michael@0: PathExprItem& pxi = mItems[i]; michael@0: nsRefPtr tmpNodes; michael@0: txNodeSetContext eContext(nodes, aContext); michael@0: while (eContext.hasNext()) { michael@0: eContext.next(); michael@0: michael@0: nsRefPtr resNodes; michael@0: if (pxi.pathOp == DESCENDANT_OP) { michael@0: rv = aContext->recycler()->getNodeSet(getter_AddRefs(resNodes)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = evalDescendants(pxi.expr, eContext.getContextNode(), michael@0: &eContext, resNodes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: nsRefPtr res; michael@0: rv = pxi.expr->evaluate(&eContext, getter_AddRefs(res)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (res->getResultType() != txAExprResult::NODESET) { michael@0: //XXX ErrorReport: report nonnodeset error michael@0: return NS_ERROR_XSLT_NODESET_EXPECTED; michael@0: } michael@0: resNodes = static_cast michael@0: (static_cast michael@0: (res)); michael@0: } michael@0: michael@0: if (tmpNodes) { michael@0: if (!resNodes->isEmpty()) { michael@0: nsRefPtr oldSet; michael@0: oldSet.swap(tmpNodes); michael@0: rv = aContext->recycler()-> michael@0: getNonSharedNodeSet(oldSet, getter_AddRefs(tmpNodes)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: oldSet.swap(resNodes); michael@0: rv = aContext->recycler()-> michael@0: getNonSharedNodeSet(oldSet, getter_AddRefs(resNodes)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: tmpNodes->addAndTransfer(resNodes); michael@0: } michael@0: } michael@0: else { michael@0: tmpNodes = resNodes; michael@0: } michael@0: } michael@0: nodes = tmpNodes; michael@0: if (nodes->isEmpty()) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: *aResult = nodes; michael@0: NS_ADDREF(*aResult); michael@0: michael@0: return NS_OK; michael@0: } //-- evaluate michael@0: michael@0: /** michael@0: * Selects from the descendants of the context node michael@0: * all nodes that match the Expr michael@0: **/ michael@0: nsresult michael@0: PathExpr::evalDescendants(Expr* aStep, const txXPathNode& aNode, michael@0: txIMatchContext* aContext, txNodeSet* resNodes) michael@0: { michael@0: txSingleNodeContext eContext(aNode, aContext); michael@0: nsRefPtr res; michael@0: nsresult rv = aStep->evaluate(&eContext, getter_AddRefs(res)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (res->getResultType() != txAExprResult::NODESET) { michael@0: //XXX ErrorReport: report nonnodeset error michael@0: return NS_ERROR_XSLT_NODESET_EXPECTED; michael@0: } michael@0: michael@0: txNodeSet* oldSet = static_cast michael@0: (static_cast(res)); michael@0: nsRefPtr newSet; michael@0: rv = aContext->recycler()->getNonSharedNodeSet(oldSet, michael@0: getter_AddRefs(newSet)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: resNodes->addAndTransfer(newSet); michael@0: michael@0: bool filterWS = aContext->isStripSpaceAllowed(aNode); michael@0: michael@0: txXPathTreeWalker walker(aNode); michael@0: if (!walker.moveToFirstChild()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: do { michael@0: const txXPathNode& node = walker.getCurrentPosition(); michael@0: if (!(filterWS && txXPathNodeUtils::isText(node) && michael@0: txXPathNodeUtils::isWhitespace(node))) { michael@0: rv = evalDescendants(aStep, node, aContext, resNodes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } while (walker.moveToNextSibling()); michael@0: michael@0: return NS_OK; michael@0: } //-- evalDescendants michael@0: michael@0: Expr::ExprType michael@0: PathExpr::getType() michael@0: { michael@0: return PATH_EXPR; michael@0: } michael@0: michael@0: TX_IMPL_EXPR_STUBS_BASE(PathExpr, NODESET_RESULT) michael@0: michael@0: Expr* michael@0: PathExpr::getSubExprAt(uint32_t aPos) michael@0: { michael@0: return aPos < mItems.Length() ? mItems[aPos].expr.get() : nullptr; michael@0: } michael@0: void michael@0: PathExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) michael@0: { michael@0: NS_ASSERTION(aPos < mItems.Length(), "setting bad subexpression index"); michael@0: mItems[aPos].expr.forget(); michael@0: mItems[aPos].expr = aExpr; michael@0: } michael@0: michael@0: michael@0: bool michael@0: PathExpr::isSensitiveTo(ContextSensitivity aContext) michael@0: { michael@0: if (mItems[0].expr->isSensitiveTo(aContext)) { michael@0: return true; michael@0: } michael@0: michael@0: // We're creating a new node/nodeset so we can ignore those bits. michael@0: Expr::ContextSensitivity context = michael@0: aContext & ~(Expr::NODE_CONTEXT | Expr::NODESET_CONTEXT); michael@0: if (context == NO_CONTEXT) { michael@0: return false; michael@0: } michael@0: michael@0: uint32_t i, len = mItems.Length(); michael@0: for (i = 0; i < len; ++i) { michael@0: NS_ASSERTION(!mItems[i].expr->isSensitiveTo(Expr::NODESET_CONTEXT), michael@0: "Step cannot depend on nodeset-context"); michael@0: if (mItems[i].expr->isSensitiveTo(context)) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: #ifdef TX_TO_STRING michael@0: void michael@0: PathExpr::toString(nsAString& dest) michael@0: { michael@0: if (!mItems.IsEmpty()) { michael@0: NS_ASSERTION(mItems[0].pathOp == RELATIVE_OP, michael@0: "First step should be relative"); michael@0: mItems[0].expr->toString(dest); michael@0: } michael@0: michael@0: uint32_t i, len = mItems.Length(); michael@0: for (i = 1; i < len; ++i) { michael@0: switch (mItems[i].pathOp) { michael@0: case DESCENDANT_OP: michael@0: dest.AppendLiteral("//"); michael@0: break; michael@0: case RELATIVE_OP: michael@0: dest.Append(char16_t('/')); michael@0: break; michael@0: } michael@0: mItems[i].expr->toString(dest); michael@0: } michael@0: } michael@0: #endif