diff options
Diffstat (limited to 'dom/xslt/xpath/txCoreFunctionCall.cpp')
-rw-r--r-- | dom/xslt/xpath/txCoreFunctionCall.cpp | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/dom/xslt/xpath/txCoreFunctionCall.cpp b/dom/xslt/xpath/txCoreFunctionCall.cpp new file mode 100644 index 0000000000..9585944e9e --- /dev/null +++ b/dom/xslt/xpath/txCoreFunctionCall.cpp @@ -0,0 +1,666 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FloatingPoint.h" + +#include "txExpr.h" +#include "txNodeSet.h" +#include "nsGkAtoms.h" +#include "txIXPathContext.h" +#include "nsWhitespaceTokenizer.h" +#include "txXPathTreeWalker.h" +#include <math.h> +#include "txStringUtils.h" +#include "txXMLUtils.h" + +using namespace mozilla; + +struct txCoreFunctionDescriptor { + const int8_t mMinParams; + const int8_t mMaxParams; + const Expr::ResultType mReturnType; + const nsStaticAtom* const mName; +}; + +// This must be ordered in the same order as txCoreFunctionCall::eType. +// If you change one, change the other. +static const txCoreFunctionDescriptor descriptTable[] = { + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::count}, // COUNT + {1, 1, Expr::NODESET_RESULT, nsGkAtoms::id}, // ID + {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::last}, // LAST + {0, 1, Expr::STRING_RESULT, nsGkAtoms::localName}, // LOCAL_NAME + {0, 1, Expr::STRING_RESULT, nsGkAtoms::namespaceUri}, // NAMESPACE_URI + {0, 1, Expr::STRING_RESULT, nsGkAtoms::name}, // NAME + {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::position}, // POSITION + + {2, -1, Expr::STRING_RESULT, nsGkAtoms::concat}, // CONCAT + {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::contains}, // CONTAINS + {0, 1, Expr::STRING_RESULT, nsGkAtoms::normalizeSpace}, // NORMALIZE_SPACE + {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::startsWith}, // STARTS_WITH + {0, 1, Expr::STRING_RESULT, nsGkAtoms::string}, // STRING + {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::stringLength}, // STRING_LENGTH + {2, 3, Expr::STRING_RESULT, nsGkAtoms::substring}, // SUBSTRING + {2, 2, Expr::STRING_RESULT, nsGkAtoms::substringAfter}, // SUBSTRING_AFTER + {2, 2, Expr::STRING_RESULT, + nsGkAtoms::substringBefore}, // SUBSTRING_BEFORE + {3, 3, Expr::STRING_RESULT, nsGkAtoms::translate}, // TRANSLATE + + {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::number}, // NUMBER + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::round}, // ROUND + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::floor}, // FLOOR + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::ceiling}, // CEILING + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::sum}, // SUM + + {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::boolean}, // BOOLEAN + {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_false}, // _FALSE + {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::lang}, // LANG + {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::_not}, // _NOT + {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_true} // _TRUE +}; + +/* + * Evaluates this Expr based on the given context node and processor state + * @param context the context node for evaluation of this Expr + * @param ps the ContextState containing the stack information needed + * for evaluation + * @return the result of the evaluation + */ +nsresult txCoreFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + if (!requireParams(descriptTable[mType].mMinParams, + descriptTable[mType].mMaxParams, aContext)) { + return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + } + + nsresult rv = NS_OK; + switch (mType) { + case COUNT: { + RefPtr<txNodeSet> nodes; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + return aContext->recycler()->getNumberResult(nodes->size(), aResult); + } + case ID: { + RefPtr<txAExprResult> exprResult; + rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txNodeSet> resultSet; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet)); + NS_ENSURE_SUCCESS(rv, rv); + + txXPathTreeWalker walker(aContext->getContextNode()); + + if (exprResult->getResultType() == txAExprResult::NODESET) { + txNodeSet* nodes = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult)); + int32_t i; + for (i = 0; i < nodes->size(); ++i) { + nsAutoString idList; + txXPathNodeUtils::appendNodeValue(nodes->get(i), idList); + nsWhitespaceTokenizer tokenizer(idList); + while (tokenizer.hasMoreTokens()) { + if (walker.moveToElementById(tokenizer.nextToken())) { + resultSet->add(walker.getCurrentPosition()); + } + } + } + } else { + nsAutoString idList; + exprResult->stringValue(idList); + nsWhitespaceTokenizer tokenizer(idList); + while (tokenizer.hasMoreTokens()) { + if (walker.moveToElementById(tokenizer.nextToken())) { + resultSet->add(walker.getCurrentPosition()); + } + } + } + + *aResult = resultSet; + NS_ADDREF(*aResult); + + return NS_OK; + } + case LAST: { + return aContext->recycler()->getNumberResult(aContext->size(), aResult); + } + case LOCAL_NAME: + case NAME: + case NAMESPACE_URI: { + // Check for optional arg + RefPtr<txNodeSet> nodes; + if (!mParams.IsEmpty()) { + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + if (nodes->isEmpty()) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + } + + const txXPathNode& node = + nodes ? nodes->get(0) : aContext->getContextNode(); + switch (mType) { + case LOCAL_NAME: { + StringResult* strRes = nullptr; + rv = aContext->recycler()->getStringResult(&strRes); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = strRes; + txXPathNodeUtils::getLocalName(node, strRes->mValue); + + return NS_OK; + } + case NAMESPACE_URI: { + StringResult* strRes = nullptr; + rv = aContext->recycler()->getStringResult(&strRes); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = strRes; + txXPathNodeUtils::getNamespaceURI(node, strRes->mValue); + + return NS_OK; + } + case NAME: { + // XXX Namespace: namespaces have a name + if (txXPathNodeUtils::isAttribute(node) || + txXPathNodeUtils::isElement(node) || + txXPathNodeUtils::isProcessingInstruction(node)) { + StringResult* strRes = nullptr; + rv = aContext->recycler()->getStringResult(&strRes); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = strRes; + txXPathNodeUtils::getNodeName(node, strRes->mValue); + } else { + aContext->recycler()->getEmptyStringResult(aResult); + } + + return NS_OK; + } + default: { + MOZ_CRASH("Unexpected mType?!"); + } + } + MOZ_CRASH("Inner mType switch should have returned!"); + } + case POSITION: { + return aContext->recycler()->getNumberResult(aContext->position(), + aResult); + } + + // String functions + + case CONCAT: { + RefPtr<StringResult> strRes; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t i, len = mParams.Length(); + for (i = 0; i < len; ++i) { + rv = mParams[i]->evaluateToString(aContext, strRes->mValue); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ADDREF(*aResult = strRes); + + return NS_OK; + } + case CONTAINS: { + nsAutoString arg2; + rv = mParams[1]->evaluateToString(aContext, arg2); + NS_ENSURE_SUCCESS(rv, rv); + + if (arg2.IsEmpty()) { + aContext->recycler()->getBoolResult(true, aResult); + } else { + nsAutoString arg1; + rv = mParams[0]->evaluateToString(aContext, arg1); + NS_ENSURE_SUCCESS(rv, rv); + + aContext->recycler()->getBoolResult(FindInReadable(arg2, arg1), + aResult); + } + + return NS_OK; + } + case NORMALIZE_SPACE: { + nsAutoString resultStr; + if (!mParams.IsEmpty()) { + rv = mParams[0]->evaluateToString(aContext, resultStr); + NS_ENSURE_SUCCESS(rv, rv); + } else { + txXPathNodeUtils::appendNodeValue(aContext->getContextNode(), + resultStr); + } + + RefPtr<StringResult> strRes; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes)); + NS_ENSURE_SUCCESS(rv, rv); + + bool addSpace = false; + bool first = true; + strRes->mValue.SetCapacity(resultStr.Length()); + char16_t c; + uint32_t src; + for (src = 0; src < resultStr.Length(); src++) { + c = resultStr.CharAt(src); + if (XMLUtils::isWhitespace(c)) { + addSpace = true; + } else { + if (addSpace && !first) strRes->mValue.Append(char16_t(' ')); + + strRes->mValue.Append(c); + addSpace = false; + first = false; + } + } + *aResult = strRes; + NS_ADDREF(*aResult); + + return NS_OK; + } + case STARTS_WITH: { + nsAutoString arg2; + rv = mParams[1]->evaluateToString(aContext, arg2); + NS_ENSURE_SUCCESS(rv, rv); + + bool result = false; + if (arg2.IsEmpty()) { + result = true; + } else { + nsAutoString arg1; + rv = mParams[0]->evaluateToString(aContext, arg1); + NS_ENSURE_SUCCESS(rv, rv); + + result = StringBeginsWith(arg1, arg2); + } + + aContext->recycler()->getBoolResult(result, aResult); + + return NS_OK; + } + case STRING: { + RefPtr<StringResult> strRes; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mParams.IsEmpty()) { + rv = mParams[0]->evaluateToString(aContext, strRes->mValue); + NS_ENSURE_SUCCESS(rv, rv); + } else { + txXPathNodeUtils::appendNodeValue(aContext->getContextNode(), + strRes->mValue); + } + + NS_ADDREF(*aResult = strRes); + + return NS_OK; + } + case STRING_LENGTH: { + nsAutoString resultStr; + if (!mParams.IsEmpty()) { + rv = mParams[0]->evaluateToString(aContext, resultStr); + NS_ENSURE_SUCCESS(rv, rv); + } else { + txXPathNodeUtils::appendNodeValue(aContext->getContextNode(), + resultStr); + } + rv = aContext->recycler()->getNumberResult(resultStr.Length(), aResult); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + case SUBSTRING: { + nsAutoString src; + rv = mParams[0]->evaluateToString(aContext, src); + NS_ENSURE_SUCCESS(rv, rv); + + double start; + rv = evaluateToNumber(mParams[1], aContext, &start); + NS_ENSURE_SUCCESS(rv, rv); + + // check for NaN or +/-Inf + if (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<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, nsCaseInsensitiveStringComparator) && + (lang.Length() == arg.Length() || lang.CharAt(arg.Length()) == '-'); + + aContext->recycler()->getBoolResult(result, aResult); + + return NS_OK; + } + case _NOT: { + bool result; + rv = mParams[0]->evaluateToBool(aContext, result); + NS_ENSURE_SUCCESS(rv, rv); + + aContext->recycler()->getBoolResult(!result, aResult); + + return NS_OK; + } + case _TRUE: { + aContext->recycler()->getBoolResult(true, aResult); + + return NS_OK; + } + } + + aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; +} + +Expr::ResultType txCoreFunctionCall::getReturnType() { + return descriptTable[mType].mReturnType; +} + +bool txCoreFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + switch (mType) { + case COUNT: + case CONCAT: + case CONTAINS: + case STARTS_WITH: + case SUBSTRING: + case SUBSTRING_AFTER: + case SUBSTRING_BEFORE: + case TRANSLATE: + case ROUND: + case FLOOR: + case CEILING: + case SUM: + case BOOLEAN: + case _NOT: + case _FALSE: + case _TRUE: { + return argsSensitiveTo(aContext); + } + case ID: { + return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext); + } + case LAST: { + return !!(aContext & SIZE_CONTEXT); + } + case LOCAL_NAME: + case NAME: + case NAMESPACE_URI: + case NORMALIZE_SPACE: + case STRING: + case STRING_LENGTH: + case NUMBER: { + if (mParams.IsEmpty()) { + return !!(aContext & NODE_CONTEXT); + } + return argsSensitiveTo(aContext); + } + case POSITION: { + return !!(aContext & POSITION_CONTEXT); + } + case LANG: { + return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext); + } + } + + MOZ_ASSERT_UNREACHABLE("how'd we get here?"); + return true; +} + +// static +bool txCoreFunctionCall::getTypeFromAtom(nsAtom* aName, eType& aType) { + uint32_t i; + for (i = 0; i < ArrayLength(descriptTable); ++i) { + if (aName == descriptTable[i].mName) { + aType = static_cast<eType>(i); + + return true; + } + } + + return false; +} + +#ifdef TX_TO_STRING +void txCoreFunctionCall::appendName(nsAString& aDest) { + aDest.Append(descriptTable[mType].mName->GetUTF16String()); +} +#endif |