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