summaryrefslogtreecommitdiffstats
path: root/dom/xslt/xslt/txFormatNumberFunctionCall.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xslt/xslt/txFormatNumberFunctionCall.cpp')
-rw-r--r--dom/xslt/xslt/txFormatNumberFunctionCall.cpp406
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;
+}