diff options
Diffstat (limited to 'dom/xslt/xslt/txFormatNumberFunctionCall.cpp')
-rw-r--r-- | dom/xslt/xslt/txFormatNumberFunctionCall.cpp | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/dom/xslt/xslt/txFormatNumberFunctionCall.cpp b/dom/xslt/xslt/txFormatNumberFunctionCall.cpp new file mode 100644 index 0000000000..e851db2d7e --- /dev/null +++ b/dom/xslt/xslt/txFormatNumberFunctionCall.cpp @@ -0,0 +1,406 @@ +/* -*- 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 "txXSLTFunctions.h" +#include "nsGkAtoms.h" +#include "txIXPathContext.h" +#include "txStylesheet.h" +#include <math.h> +#include "txNamespaceMap.h" + +#include "prdtoa.h" + +#define INVALID_PARAM_VALUE u"invalid parameter value for function"_ns + +const char16_t txFormatNumberFunctionCall::FORMAT_QUOTE = '\''; + +/* + * FormatNumberFunctionCall + * A representation of the XSLT additional function: format-number() + */ + +/* + * Creates a new format-number function call + */ +txFormatNumberFunctionCall::txFormatNumberFunctionCall( + txStylesheet* aStylesheet, txNamespaceMap* aMappings) + : mStylesheet(aStylesheet), mMappings(aMappings) {} + +void txFormatNumberFunctionCall::ReportInvalidArg(txIEvalContext* aContext) { + nsAutoString err(INVALID_PARAM_VALUE); +#ifdef TX_TO_STRING + err.AppendLiteral(": "); + toString(err); +#endif + aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG); +} + +/* + * Evaluates this Expr based on the given context node and processor state + * @param context the context node for evaluation of this Expr + * @param cs the ContextState containing the stack information needed + * for evaluation + * @return the result of the evaluation + */ +nsresult txFormatNumberFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + if (!requireParams(2, 3, aContext)) return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + + // Get number and format + double value; + txExpandedName formatName; + + nsresult rv = evaluateToNumber(mParams[0], aContext, &value); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString formatStr; + rv = mParams[1]->evaluateToString(aContext, formatStr); + NS_ENSURE_SUCCESS(rv, rv); + + if (mParams.Length() == 3) { + nsAutoString formatQName; + rv = mParams[2]->evaluateToString(aContext, formatQName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = formatName.init(formatQName, mMappings, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + txDecimalFormat* format = mStylesheet->getDecimalFormat(formatName); + if (!format) { + nsAutoString err(u"unknown decimal format"_ns); +#ifdef TX_TO_STRING + err.AppendLiteral(" for: "); + toString(err); +#endif + aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG); + return NS_ERROR_XPATH_INVALID_ARG; + } + + // Special cases + if (std::isnan(value)) { + return aContext->recycler()->getStringResult(format->mNaN, aResult); + } + + if (value == mozilla::PositiveInfinity<double>()) { + return aContext->recycler()->getStringResult(format->mInfinity, aResult); + } + + if (value == mozilla::NegativeInfinity<double>()) { + nsAutoString res; + res.Append(format->mMinusSign); + res.Append(format->mInfinity); + return aContext->recycler()->getStringResult(res, aResult); + } + + // Value is a normal finite number + nsAutoString prefix; + nsAutoString suffix; + int minIntegerSize = 0; + int minFractionSize = 0; + int maxFractionSize = 0; + int multiplier = 1; + int groupSize = -1; + + uint32_t pos = 0; + uint32_t formatLen = formatStr.Length(); + bool inQuote; + + // Get right subexpression + inQuote = false; + if (mozilla::IsNegative(value)) { + while (pos < formatLen && + (inQuote || formatStr.CharAt(pos) != format->mPatternSeparator)) { + if (formatStr.CharAt(pos) == FORMAT_QUOTE) inQuote = !inQuote; + pos++; + } + + if (pos == formatLen) { + pos = 0; + prefix.Append(format->mMinusSign); + } else + pos++; + } + + // Parse the format string + FormatParseState pState = Prefix; + inQuote = false; + + char16_t c = 0; + while (pos < formatLen && pState != Finished) { + c = formatStr.CharAt(pos++); + + switch (pState) { + case Prefix: + case Suffix: + if (!inQuote) { + if (c == format->mPercent) { + if (multiplier == 1) + multiplier = 100; + else { + ReportInvalidArg(aContext); + return NS_ERROR_XPATH_INVALID_ARG; + } + } else if (c == format->mPerMille) { + if (multiplier == 1) + multiplier = 1000; + else { + ReportInvalidArg(aContext); + return NS_ERROR_XPATH_INVALID_ARG; + } + } else if (c == format->mDecimalSeparator || + c == format->mGroupingSeparator || + c == format->mZeroDigit || c == format->mDigit || + c == format->mPatternSeparator) { + pState = pState == Prefix ? IntDigit : Finished; + pos--; + break; + } + } + + if (c == FORMAT_QUOTE) + inQuote = !inQuote; + else if (pState == Prefix) + prefix.Append(c); + else + suffix.Append(c); + break; + + case IntDigit: + if (c == format->mGroupingSeparator) + groupSize = 0; + else if (c == format->mDigit) { + if (groupSize >= 0) groupSize++; + } else { + pState = IntZero; + pos--; + } + break; + + case IntZero: + if (c == format->mGroupingSeparator) + groupSize = 0; + else if (c == format->mZeroDigit) { + if (groupSize >= 0) groupSize++; + minIntegerSize++; + } else if (c == format->mDecimalSeparator) { + pState = FracZero; + } else { + pState = Suffix; + pos--; + } + break; + + case FracZero: + if (c == format->mZeroDigit) { + maxFractionSize++; + minFractionSize++; + } else { + pState = FracDigit; + pos--; + } + break; + + case FracDigit: + if (c == format->mDigit) + maxFractionSize++; + else { + pState = Suffix; + pos--; + } + break; + + case Finished: + break; + } + } + + // Did we manage to parse the entire formatstring and was it valid + if ((c != format->mPatternSeparator && pos < formatLen) || inQuote || + groupSize == 0) { + ReportInvalidArg(aContext); + return NS_ERROR_XPATH_INVALID_ARG; + } + + /* + * FINALLY we're done with the parsing + * now build the result string + */ + + value = fabs(value) * multiplier; + + // Make sure the multiplier didn't push value to infinity. + if (value == mozilla::PositiveInfinity<double>()) { + return aContext->recycler()->getStringResult(format->mInfinity, aResult); + } + + // Make sure the multiplier didn't push value to infinity. + if (value == mozilla::PositiveInfinity<double>()) { + return aContext->recycler()->getStringResult(format->mInfinity, aResult); + } + + // Prefix + nsAutoString res(prefix); + + int bufsize; + if (value > 1) + bufsize = (int)log10(value) + 30; + else + bufsize = 1 + 30; + + auto buf = mozilla::MakeUnique<char[]>(bufsize); + int bufIntDigits, sign; + char* endp; + PR_dtoa(value, 0, 0, &bufIntDigits, &sign, &endp, buf.get(), bufsize - 1); + + int buflen = endp - buf.get(); + int intDigits; + intDigits = bufIntDigits > minIntegerSize ? bufIntDigits : minIntegerSize; + + if (groupSize < 0) groupSize = intDigits + 10; // to simplify grouping + + // XXX We shouldn't use SetLength. + res.SetLength(res.Length() + intDigits + // integer digits + 1 + // decimal separator + maxFractionSize + // fractions + (intDigits - 1) / groupSize); // group separators + + int32_t i = bufIntDigits + maxFractionSize - 1; + bool carry = (0 <= i + 1) && (i + 1 < buflen) && (buf[i + 1] >= '5'); + bool hasFraction = false; + + // The number of characters in res that we haven't filled in. + mozilla::CheckedUint32 resRemain = mozilla::CheckedUint32(res.Length()); + +#define CHECKED_SET_CHAR(c) \ + --resRemain; \ + if (!resRemain.isValid() || !res.SetCharAt(c, resRemain.value())) { \ + ReportInvalidArg(aContext); \ + return NS_ERROR_XPATH_INVALID_ARG; \ + } + +#define CHECKED_TRUNCATE() \ + --resRemain; \ + if (!resRemain.isValid()) { \ + ReportInvalidArg(aContext); \ + return NS_ERROR_XPATH_INVALID_ARG; \ + } \ + res.Truncate(resRemain.value()); + + // Fractions + for (; i >= bufIntDigits; --i) { + int digit; + if (i >= buflen || i < 0) { + digit = 0; + } else { + digit = buf[i] - '0'; + } + + if (carry) { + digit = (digit + 1) % 10; + carry = digit == 0; + } + + if (hasFraction || digit != 0 || i < bufIntDigits + minFractionSize) { + hasFraction = true; + CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit)); + } else { + CHECKED_TRUNCATE(); + } + } + + // Decimal separator + if (hasFraction) { + CHECKED_SET_CHAR(format->mDecimalSeparator); + } else { + CHECKED_TRUNCATE(); + } + + // Integer digits + for (i = 0; i < intDigits; ++i) { + int digit; + if (bufIntDigits - i - 1 >= buflen || bufIntDigits - i - 1 < 0) { + digit = 0; + } else { + digit = buf[bufIntDigits - i - 1] - '0'; + } + + if (carry) { + digit = (digit + 1) % 10; + carry = digit == 0; + } + + if (i != 0 && i % groupSize == 0) { + CHECKED_SET_CHAR(format->mGroupingSeparator); + } + + CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit)); + } + +#undef CHECKED_SET_CHAR +#undef CHECKED_TRUNCATE + + if (carry) { + if (i % groupSize == 0) { + res.Insert(format->mGroupingSeparator, resRemain.value()); + } + res.Insert((char16_t)(1 + format->mZeroDigit), resRemain.value()); + } + + if (!hasFraction && !intDigits && !carry) { + // If we havn't added any characters we add a '0' + // This can only happen for formats like '##.##' + res.Append(format->mZeroDigit); + } + + // Build suffix + res.Append(suffix); + + return aContext->recycler()->getStringResult(res, aResult); +} //-- evaluate + +Expr::ResultType txFormatNumberFunctionCall::getReturnType() { + return STRING_RESULT; +} + +bool txFormatNumberFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + return argsSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void txFormatNumberFunctionCall::appendName(nsAString& aDest) { + aDest.Append(nsGkAtoms::formatNumber->GetUTF16String()); +} +#endif + +/* + * txDecimalFormat + * A representation of the XSLT element <xsl:decimal-format> + */ + +txDecimalFormat::txDecimalFormat() + : mInfinity(u"Infinity"_ns), mNaN(u"NaN"_ns) { + mDecimalSeparator = '.'; + mGroupingSeparator = ','; + mMinusSign = '-'; + mPercent = '%'; + mPerMille = 0x2030; + mZeroDigit = '0'; + mDigit = '#'; + mPatternSeparator = ';'; +} + +bool txDecimalFormat::isEqual(txDecimalFormat* other) { + return mDecimalSeparator == other->mDecimalSeparator && + mGroupingSeparator == other->mGroupingSeparator && + mInfinity.Equals(other->mInfinity) && + mMinusSign == other->mMinusSign && mNaN.Equals(other->mNaN) && + mPercent == other->mPercent && mPerMille == other->mPerMille && + mZeroDigit == other->mZeroDigit && mDigit == other->mDigit && + mPatternSeparator == other->mPatternSeparator; +} |