1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "mozilla/FloatingPoint.h"
7
8 #include "txXSLTFunctions.h"
9 #include "nsGkAtoms.h"
10 #include "txIXPathContext.h"
11 #include "txStylesheet.h"
12 #include <math.h>
13 #include "txNamespaceMap.h"
14
15 #include "prdtoa.h"
16
17 #define INVALID_PARAM_VALUE \
18 NS_LITERAL_STRING("invalid parameter value for function")
19
20 const char16_t txFormatNumberFunctionCall::FORMAT_QUOTE = '\'';
21
22 /*
23 * FormatNumberFunctionCall
24 * A representation of the XSLT additional function: format-number()
25 */
26
27 /*
28 * Creates a new format-number function call
29 */
txFormatNumberFunctionCall(txStylesheet * aStylesheet,txNamespaceMap * aMappings)30 txFormatNumberFunctionCall::txFormatNumberFunctionCall(
31 txStylesheet* aStylesheet, txNamespaceMap* aMappings)
32 : mStylesheet(aStylesheet), mMappings(aMappings) {}
33
ReportInvalidArg(txIEvalContext * aContext)34 void txFormatNumberFunctionCall::ReportInvalidArg(txIEvalContext* aContext) {
35 nsAutoString err(INVALID_PARAM_VALUE);
36 #ifdef TX_TO_STRING
37 err.AppendLiteral(": ");
38 toString(err);
39 #endif
40 aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG);
41 }
42
43 /*
44 * Evaluates this Expr based on the given context node and processor state
45 * @param context the context node for evaluation of this Expr
46 * @param cs the ContextState containing the stack information needed
47 * for evaluation
48 * @return the result of the evaluation
49 */
evaluate(txIEvalContext * aContext,txAExprResult ** aResult)50 nsresult txFormatNumberFunctionCall::evaluate(txIEvalContext* aContext,
51 txAExprResult** aResult) {
52 *aResult = nullptr;
53 if (!requireParams(2, 3, aContext)) return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
54
55 // Get number and format
56 double value;
57 txExpandedName formatName;
58
59 nsresult rv = evaluateToNumber(mParams[0], aContext, &value);
60 NS_ENSURE_SUCCESS(rv, rv);
61
62 nsAutoString formatStr;
63 rv = mParams[1]->evaluateToString(aContext, formatStr);
64 NS_ENSURE_SUCCESS(rv, rv);
65
66 if (mParams.Length() == 3) {
67 nsAutoString formatQName;
68 rv = mParams[2]->evaluateToString(aContext, formatQName);
69 NS_ENSURE_SUCCESS(rv, rv);
70
71 rv = formatName.init(formatQName, mMappings, false);
72 NS_ENSURE_SUCCESS(rv, rv);
73 }
74
75 txDecimalFormat* format = mStylesheet->getDecimalFormat(formatName);
76 if (!format) {
77 nsAutoString err(NS_LITERAL_STRING("unknown decimal format"));
78 #ifdef TX_TO_STRING
79 err.AppendLiteral(" for: ");
80 toString(err);
81 #endif
82 aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG);
83 return NS_ERROR_XPATH_INVALID_ARG;
84 }
85
86 // Special cases
87 if (mozilla::IsNaN(value)) {
88 return aContext->recycler()->getStringResult(format->mNaN, aResult);
89 }
90
91 if (value == mozilla::PositiveInfinity<double>()) {
92 return aContext->recycler()->getStringResult(format->mInfinity, aResult);
93 }
94
95 if (value == mozilla::NegativeInfinity<double>()) {
96 nsAutoString res;
97 res.Append(format->mMinusSign);
98 res.Append(format->mInfinity);
99 return aContext->recycler()->getStringResult(res, aResult);
100 }
101
102 // Value is a normal finite number
103 nsAutoString prefix;
104 nsAutoString suffix;
105 int minIntegerSize = 0;
106 int minFractionSize = 0;
107 int maxFractionSize = 0;
108 int multiplier = 1;
109 int groupSize = -1;
110
111 uint32_t pos = 0;
112 uint32_t formatLen = formatStr.Length();
113 bool inQuote;
114
115 // Get right subexpression
116 inQuote = false;
117 if (mozilla::IsNegative(value)) {
118 while (pos < formatLen &&
119 (inQuote || formatStr.CharAt(pos) != format->mPatternSeparator)) {
120 if (formatStr.CharAt(pos) == FORMAT_QUOTE) inQuote = !inQuote;
121 pos++;
122 }
123
124 if (pos == formatLen) {
125 pos = 0;
126 prefix.Append(format->mMinusSign);
127 } else
128 pos++;
129 }
130
131 // Parse the format string
132 FormatParseState pState = Prefix;
133 inQuote = false;
134
135 char16_t c = 0;
136 while (pos < formatLen && pState != Finished) {
137 c = formatStr.CharAt(pos++);
138
139 switch (pState) {
140 case Prefix:
141 case Suffix:
142 if (!inQuote) {
143 if (c == format->mPercent) {
144 if (multiplier == 1)
145 multiplier = 100;
146 else {
147 ReportInvalidArg(aContext);
148 return NS_ERROR_XPATH_INVALID_ARG;
149 }
150 } else if (c == format->mPerMille) {
151 if (multiplier == 1)
152 multiplier = 1000;
153 else {
154 ReportInvalidArg(aContext);
155 return NS_ERROR_XPATH_INVALID_ARG;
156 }
157 } else if (c == format->mDecimalSeparator ||
158 c == format->mGroupingSeparator ||
159 c == format->mZeroDigit || c == format->mDigit ||
160 c == format->mPatternSeparator) {
161 pState = pState == Prefix ? IntDigit : Finished;
162 pos--;
163 break;
164 }
165 }
166
167 if (c == FORMAT_QUOTE)
168 inQuote = !inQuote;
169 else if (pState == Prefix)
170 prefix.Append(c);
171 else
172 suffix.Append(c);
173 break;
174
175 case IntDigit:
176 if (c == format->mGroupingSeparator)
177 groupSize = 0;
178 else if (c == format->mDigit) {
179 if (groupSize >= 0) groupSize++;
180 } else {
181 pState = IntZero;
182 pos--;
183 }
184 break;
185
186 case IntZero:
187 if (c == format->mGroupingSeparator)
188 groupSize = 0;
189 else if (c == format->mZeroDigit) {
190 if (groupSize >= 0) groupSize++;
191 minIntegerSize++;
192 } else if (c == format->mDecimalSeparator) {
193 pState = FracZero;
194 } else {
195 pState = Suffix;
196 pos--;
197 }
198 break;
199
200 case FracZero:
201 if (c == format->mZeroDigit) {
202 maxFractionSize++;
203 minFractionSize++;
204 } else {
205 pState = FracDigit;
206 pos--;
207 }
208 break;
209
210 case FracDigit:
211 if (c == format->mDigit)
212 maxFractionSize++;
213 else {
214 pState = Suffix;
215 pos--;
216 }
217 break;
218
219 case Finished:
220 break;
221 }
222 }
223
224 // Did we manage to parse the entire formatstring and was it valid
225 if ((c != format->mPatternSeparator && pos < formatLen) || inQuote ||
226 groupSize == 0) {
227 ReportInvalidArg(aContext);
228 return NS_ERROR_XPATH_INVALID_ARG;
229 }
230
231 /*
232 * FINALLY we're done with the parsing
233 * now build the result string
234 */
235
236 value = fabs(value) * multiplier;
237
238 // Make sure the multiplier didn't push value to infinity.
239 if (value == mozilla::PositiveInfinity<double>()) {
240 return aContext->recycler()->getStringResult(format->mInfinity, aResult);
241 }
242
243 // Make sure the multiplier didn't push value to infinity.
244 if (value == mozilla::PositiveInfinity<double>()) {
245 return aContext->recycler()->getStringResult(format->mInfinity, aResult);
246 }
247
248 // Prefix
249 nsAutoString res(prefix);
250
251 int bufsize;
252 if (value > 1)
253 bufsize = (int)log10(value) + 30;
254 else
255 bufsize = 1 + 30;
256
257 auto buf = mozilla::MakeUnique<char[]>(bufsize);
258 int bufIntDigits, sign;
259 char* endp;
260 PR_dtoa(value, 0, 0, &bufIntDigits, &sign, &endp, buf.get(), bufsize - 1);
261
262 int buflen = endp - buf.get();
263 int intDigits;
264 intDigits = bufIntDigits > minIntegerSize ? bufIntDigits : minIntegerSize;
265
266 if (groupSize < 0) groupSize = intDigits + 10; // to simplify grouping
267
268 // XXX We shouldn't use SetLength.
269 res.SetLength(res.Length() + intDigits + // integer digits
270 1 + // decimal separator
271 maxFractionSize + // fractions
272 (intDigits - 1) / groupSize); // group separators
273
274 int32_t i = bufIntDigits + maxFractionSize - 1;
275 bool carry = (0 <= i + 1) && (i + 1 < buflen) && (buf[i + 1] >= '5');
276 bool hasFraction = false;
277
278 // The number of characters in res that we haven't filled in.
279 mozilla::CheckedUint32 resRemain = mozilla::CheckedUint32(res.Length());
280
281 #define CHECKED_SET_CHAR(c) \
282 --resRemain; \
283 if (!resRemain.isValid() || !res.SetCharAt(c, resRemain.value())) { \
284 ReportInvalidArg(aContext); \
285 return NS_ERROR_XPATH_INVALID_ARG; \
286 }
287
288 #define CHECKED_TRUNCATE() \
289 --resRemain; \
290 if (!resRemain.isValid()) { \
291 ReportInvalidArg(aContext); \
292 return NS_ERROR_XPATH_INVALID_ARG; \
293 } \
294 res.Truncate(resRemain.value());
295
296 // Fractions
297 for (; i >= bufIntDigits; --i) {
298 int digit;
299 if (i >= buflen || i < 0) {
300 digit = 0;
301 } else {
302 digit = buf[i] - '0';
303 }
304
305 if (carry) {
306 digit = (digit + 1) % 10;
307 carry = digit == 0;
308 }
309
310 if (hasFraction || digit != 0 || i < bufIntDigits + minFractionSize) {
311 hasFraction = true;
312 CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit));
313 } else {
314 CHECKED_TRUNCATE();
315 }
316 }
317
318 // Decimal separator
319 if (hasFraction) {
320 CHECKED_SET_CHAR(format->mDecimalSeparator);
321 } else {
322 CHECKED_TRUNCATE();
323 }
324
325 // Integer digits
326 for (i = 0; i < intDigits; ++i) {
327 int digit;
328 if (bufIntDigits - i - 1 >= buflen || bufIntDigits - i - 1 < 0) {
329 digit = 0;
330 } else {
331 digit = buf[bufIntDigits - i - 1] - '0';
332 }
333
334 if (carry) {
335 digit = (digit + 1) % 10;
336 carry = digit == 0;
337 }
338
339 if (i != 0 && i % groupSize == 0) {
340 CHECKED_SET_CHAR(format->mGroupingSeparator);
341 }
342
343 CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit));
344 }
345
346 #undef CHECKED_SET_CHAR
347 #undef CHECKED_TRUNCATE
348
349 if (carry) {
350 if (i % groupSize == 0) {
351 res.Insert(format->mGroupingSeparator, resRemain.value());
352 }
353 res.Insert((char16_t)(1 + format->mZeroDigit), resRemain.value());
354 }
355
356 if (!hasFraction && !intDigits && !carry) {
357 // If we havn't added any characters we add a '0'
358 // This can only happen for formats like '##.##'
359 res.Append(format->mZeroDigit);
360 }
361
362 // Build suffix
363 res.Append(suffix);
364
365 return aContext->recycler()->getStringResult(res, aResult);
366 } //-- evaluate
367
getReturnType()368 Expr::ResultType txFormatNumberFunctionCall::getReturnType() {
369 return STRING_RESULT;
370 }
371
isSensitiveTo(ContextSensitivity aContext)372 bool txFormatNumberFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
373 return argsSensitiveTo(aContext);
374 }
375
376 #ifdef TX_TO_STRING
appendName(nsAString & aDest)377 void txFormatNumberFunctionCall::appendName(nsAString& aDest) {
378 aDest.Append(nsGkAtoms::formatNumber->GetUTF16String());
379 }
380 #endif
381
382 /*
383 * txDecimalFormat
384 * A representation of the XSLT element <xsl:decimal-format>
385 */
386
txDecimalFormat()387 txDecimalFormat::txDecimalFormat()
388 : mInfinity(NS_LITERAL_STRING("Infinity")), mNaN(NS_LITERAL_STRING("NaN")) {
389 mDecimalSeparator = '.';
390 mGroupingSeparator = ',';
391 mMinusSign = '-';
392 mPercent = '%';
393 mPerMille = 0x2030;
394 mZeroDigit = '0';
395 mDigit = '#';
396 mPatternSeparator = ';';
397 }
398
isEqual(txDecimalFormat * other)399 bool txDecimalFormat::isEqual(txDecimalFormat* other) {
400 return mDecimalSeparator == other->mDecimalSeparator &&
401 mGroupingSeparator == other->mGroupingSeparator &&
402 mInfinity.Equals(other->mInfinity) &&
403 mMinusSign == other->mMinusSign && mNaN.Equals(other->mNaN) &&
404 mPercent == other->mPercent && mPerMille == other->mPerMille &&
405 mZeroDigit == other->mZeroDigit && mDigit == other->mDigit &&
406 mPatternSeparator == other->mPatternSeparator;
407 }
408