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