/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ArrayUtils.h" #include "mozilla/FloatingPoint.h" #include "txExpr.h" #include "txNodeSet.h" #include "nsGkAtoms.h" #include "txIXPathContext.h" #include "nsWhitespaceTokenizer.h" #include "txXPathTreeWalker.h" #include #include "txStringUtils.h" #include "txXMLUtils.h" using namespace mozilla; struct txCoreFunctionDescriptor { const int8_t mMinParams; const int8_t mMaxParams; const Expr::ResultType mReturnType; const nsStaticAtom* const mName; }; // This must be ordered in the same order as txCoreFunctionCall::eType. // If you change one, change the other. static const txCoreFunctionDescriptor descriptTable[] = { {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::count}, // COUNT {1, 1, Expr::NODESET_RESULT, nsGkAtoms::id}, // ID {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::last}, // LAST {0, 1, Expr::STRING_RESULT, nsGkAtoms::localName}, // LOCAL_NAME {0, 1, Expr::STRING_RESULT, nsGkAtoms::namespaceUri}, // NAMESPACE_URI {0, 1, Expr::STRING_RESULT, nsGkAtoms::name}, // NAME {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::position}, // POSITION {2, -1, Expr::STRING_RESULT, nsGkAtoms::concat}, // CONCAT {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::contains}, // CONTAINS {0, 1, Expr::STRING_RESULT, nsGkAtoms::normalizeSpace}, // NORMALIZE_SPACE {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::startsWith}, // STARTS_WITH {0, 1, Expr::STRING_RESULT, nsGkAtoms::string}, // STRING {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::stringLength}, // STRING_LENGTH {2, 3, Expr::STRING_RESULT, nsGkAtoms::substring}, // SUBSTRING {2, 2, Expr::STRING_RESULT, nsGkAtoms::substringAfter}, // SUBSTRING_AFTER {2, 2, Expr::STRING_RESULT, nsGkAtoms::substringBefore}, // SUBSTRING_BEFORE {3, 3, Expr::STRING_RESULT, nsGkAtoms::translate}, // TRANSLATE {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::number}, // NUMBER {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::round}, // ROUND {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::floor}, // FLOOR {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::ceiling}, // CEILING {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::sum}, // SUM {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::boolean}, // BOOLEAN {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_false}, // _FALSE {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::lang}, // LANG {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::_not}, // _NOT {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_true} // _TRUE }; /* * Evaluates this Expr based on the given context node and processor state * @param context the context node for evaluation of this Expr * @param ps the ContextState containing the stack information needed * for evaluation * @return the result of the evaluation */ nsresult txCoreFunctionCall::evaluate(txIEvalContext* aContext, txAExprResult** aResult) { *aResult = nullptr; if (!requireParams(descriptTable[mType].mMinParams, descriptTable[mType].mMaxParams, aContext)) { return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; } nsresult rv = NS_OK; switch (mType) { case COUNT: { RefPtr nodes; rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); NS_ENSURE_SUCCESS(rv, rv); return aContext->recycler()->getNumberResult(nodes->size(), aResult); } case ID: { RefPtr exprResult; rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult)); NS_ENSURE_SUCCESS(rv, rv); RefPtr 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(static_cast(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 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 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 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 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 nsAString& result = Substring(arg1, idx + arg2.Length()); return aContext->recycler()->getStringResult(result, aResult); } case SUBSTRING_BEFORE: { nsAutoString arg2; rv = mParams[1]->evaluateToString(aContext, arg2); NS_ENSURE_SUCCESS(rv, rv); if (arg2.IsEmpty()) { aContext->recycler()->getEmptyStringResult(aResult); return NS_OK; } nsAutoString arg1; rv = mParams[0]->evaluateToString(aContext, arg1); NS_ENSURE_SUCCESS(rv, rv); int32_t idx = arg1.Find(arg2); if (idx == kNotFound) { aContext->recycler()->getEmptyStringResult(aResult); return NS_OK; } return aContext->recycler()->getStringResult(StringHead(arg1, idx), aResult); } case TRANSLATE: { nsAutoString src; rv = mParams[0]->evaluateToString(aContext, src); NS_ENSURE_SUCCESS(rv, rv); if (src.IsEmpty()) { aContext->recycler()->getEmptyStringResult(aResult); return NS_OK; } RefPtr 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 nodes; nsresult rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); NS_ENSURE_SUCCESS(rv, rv); double res = 0; int32_t i; for (i = 0; i < nodes->size(); ++i) { nsAutoString resultStr; txXPathNodeUtils::appendNodeValue(nodes->get(i), resultStr); res += txDouble::toDouble(resultStr); } return aContext->recycler()->getNumberResult(res, aResult); } // Boolean functions case BOOLEAN: { bool result; nsresult rv = mParams[0]->evaluateToBool(aContext, result); NS_ENSURE_SUCCESS(rv, rv); aContext->recycler()->getBoolResult(result, aResult); return NS_OK; } case _FALSE: { aContext->recycler()->getBoolResult(false, aResult); return NS_OK; } case LANG: { txXPathTreeWalker walker(aContext->getContextNode()); nsAutoString lang; bool found; do { found = walker.getAttr(nsGkAtoms::lang, kNameSpaceID_XML, lang); } while (!found && walker.moveToParent()); if (!found) { aContext->recycler()->getBoolResult(false, aResult); return NS_OK; } nsAutoString arg; rv = mParams[0]->evaluateToString(aContext, arg); NS_ENSURE_SUCCESS(rv, rv); bool result = StringBeginsWith(lang, arg, nsCaseInsensitiveStringComparator) && (lang.Length() == arg.Length() || lang.CharAt(arg.Length()) == '-'); aContext->recycler()->getBoolResult(result, aResult); return NS_OK; } case _NOT: { bool result; rv = mParams[0]->evaluateToBool(aContext, result); NS_ENSURE_SUCCESS(rv, rv); aContext->recycler()->getBoolResult(!result, aResult); return NS_OK; } case _TRUE: { aContext->recycler()->getBoolResult(true, aResult); return NS_OK; } } aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED); return NS_ERROR_UNEXPECTED; } Expr::ResultType txCoreFunctionCall::getReturnType() { return descriptTable[mType].mReturnType; } bool txCoreFunctionCall::isSensitiveTo(ContextSensitivity aContext) { switch (mType) { case COUNT: case CONCAT: case CONTAINS: case STARTS_WITH: case SUBSTRING: case SUBSTRING_AFTER: case SUBSTRING_BEFORE: case TRANSLATE: case ROUND: case FLOOR: case CEILING: case SUM: case BOOLEAN: case _NOT: case _FALSE: case _TRUE: { return argsSensitiveTo(aContext); } case ID: { return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext); } case LAST: { return !!(aContext & SIZE_CONTEXT); } case LOCAL_NAME: case NAME: case NAMESPACE_URI: case NORMALIZE_SPACE: case STRING: case STRING_LENGTH: case NUMBER: { if (mParams.IsEmpty()) { return !!(aContext & NODE_CONTEXT); } return argsSensitiveTo(aContext); } case POSITION: { return !!(aContext & POSITION_CONTEXT); } case LANG: { return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext); } } MOZ_ASSERT_UNREACHABLE("how'd we get here?"); return true; } // static bool txCoreFunctionCall::getTypeFromAtom(nsAtom* aName, eType& aType) { uint32_t i; for (i = 0; i < ArrayLength(descriptTable); ++i) { if (aName == descriptTable[i].mName) { aType = static_cast(i); return true; } } return false; } #ifdef TX_TO_STRING void txCoreFunctionCall::appendName(nsAString& aDest) { aDest.Append(descriptTable[mType].mName->GetUTF16String()); } #endif