/* -*- 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;
}