/* * numbers.c: Implementation of the XSLT number functions * * Reference: * http://www.w3.org/TR/1999/REC-xslt-19991116 * * See Copyright for the status of this software. * * daniel@veillard.com * Bjorn Reese */ #define IN_LIBXSLT #include "libxslt.h" #include #include #include #include #include #include #include #include #include #include "xsltutils.h" #include "pattern.h" #include "templates.h" #include "transform.h" #include "numbersInternals.h" #ifndef FALSE # define FALSE (0 == 1) # define TRUE (1 == 1) #endif #define SYMBOL_QUOTE ((xmlChar)'\'') #define DEFAULT_TOKEN '0' #define DEFAULT_SEPARATOR "." #define MAX_TOKENS 1024 typedef struct _xsltFormatToken xsltFormatToken; typedef xsltFormatToken *xsltFormatTokenPtr; struct _xsltFormatToken { xmlChar *separator; int token; int width; }; typedef struct _xsltFormat xsltFormat; typedef xsltFormat *xsltFormatPtr; struct _xsltFormat { xmlChar *start; xsltFormatToken tokens[MAX_TOKENS]; int nTokens; xmlChar *end; }; static char alpha_upper_list[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static char alpha_lower_list[] = "abcdefghijklmnopqrstuvwxyz"; static xsltFormatToken default_token; /* * **** Start temp insert **** * * The following routine xsltUTF8Charcmp will be replaced with calls to * the corresponding libxml routine at a later date (when other * inter-library dependencies require it). */ /** * xsltUTF8Charcmp * @utf1: pointer to first UTF8 char * @utf2: pointer to second UTF8 char * * returns result of comparing the two UCS4 values * as with xmlStrncmp */ static int xsltUTF8Charcmp(xmlChar *utf1, xmlChar *utf2) { int len = xmlUTF8Strsize(utf1, 1); if (len < 1) return -1; if (utf1 == NULL ) { if (utf2 == NULL) return 0; return -1; } return xmlStrncmp(utf1, utf2, len); } /***** Stop temp insert *****/ /************************************************************************ * * * Utility functions * * * ************************************************************************/ #define IS_SPECIAL(self,letter) \ ((xsltUTF8Charcmp((letter), (self)->zeroDigit) == 0) || \ (xsltUTF8Charcmp((letter), (self)->digit) == 0) || \ (xsltUTF8Charcmp((letter), (self)->decimalPoint) == 0) || \ (xsltUTF8Charcmp((letter), (self)->grouping) == 0) || \ (xsltUTF8Charcmp((letter), (self)->patternSeparator) == 0)) #define IS_DIGIT_ZERO(x) xsltIsDigitZero(x) #define IS_DIGIT_ONE(x) xsltIsDigitZero((x)-1) static int xsltIsDigitZero(unsigned int ch) { /* * Reference: ftp://ftp.unicode.org/Public/UNIDATA/UnicodeData.txt * * There a many more digit ranges in newer Unicode versions. These * are only the zeros that match Digit in XML 1.0 (IS_DIGIT macro). */ switch (ch) { case 0x0030: case 0x0660: case 0x06F0: case 0x0966: case 0x09E6: case 0x0A66: case 0x0AE6: case 0x0B66: case 0x0C66: case 0x0CE6: case 0x0D66: case 0x0E50: case 0x0ED0: case 0x0F20: return TRUE; default: return FALSE; } } static void xsltNumberFormatDecimal(xmlBufferPtr buffer, double number, int digit_zero, int width, int digitsPerGroup, int groupingCharacter, int groupingCharacterLen) { /* * This used to be * xmlChar temp_string[sizeof(double) * CHAR_BIT * sizeof(xmlChar) + 4]; * which would be length 68 on x86 arch. It was changed to be a longer, * fixed length in order to try to cater for (reasonable) UTF8 * separators and numeric characters. The max UTF8 char size will be * 6 or less, so the value used [500] should be *much* larger than needed */ xmlChar temp_string[500]; xmlChar *pointer; xmlChar temp_char[6]; int i; int val; int len; /* Build buffer from back */ pointer = &temp_string[sizeof(temp_string)] - 1; /* last char */ *pointer = 0; i = 0; while (pointer > temp_string) { if ((i >= width) && (fabs(number) < 1.0)) break; /* for */ if ((i > 0) && (groupingCharacter != 0) && (digitsPerGroup > 0) && ((i % digitsPerGroup) == 0)) { if (pointer - groupingCharacterLen < temp_string) { i = -1; /* flag error */ break; } pointer -= groupingCharacterLen; xmlCopyCharMultiByte(pointer, groupingCharacter); } val = digit_zero + (int)fmod(number, 10.0); if (val < 0x80) { /* shortcut if ASCII */ if (pointer <= temp_string) { /* Check enough room */ i = -1; break; } *(--pointer) = val; } else { /* * Here we have a multibyte character. It's a little messy, * because until we generate the char we don't know how long * it is. So, we generate it into the buffer temp_char, then * copy from there into temp_string. */ len = xmlCopyCharMultiByte(temp_char, val); if ( (pointer - len) < temp_string ) { i = -1; break; } pointer -= len; memcpy(pointer, temp_char, len); } number /= 10.0; ++i; } if (i < 0) xsltGenericError(xsltGenericErrorContext, "xsltNumberFormatDecimal: Internal buffer size exceeded\n"); xmlBufferCat(buffer, pointer); } static void xsltNumberFormatAlpha(xsltNumberDataPtr data, xmlBufferPtr buffer, double number, int is_upper) { char temp_string[sizeof(double) * CHAR_BIT * sizeof(xmlChar) + 1]; char *pointer; int i; char *alpha_list; double alpha_size = (double)(sizeof(alpha_upper_list) - 1); /* * XSLT 1.0 isn't clear on how to handle zero, but XSLT 2.0 says: * * For all format tokens other than the first kind above (one that * consists of decimal digits), there may be implementation-defined * lower and upper bounds on the range of numbers that can be * formatted using this format token; indeed, for some numbering * sequences there may be intrinsic limits. [...] Numbers that fall * outside this range must be formatted using the format token 1. * * The "a" token has an intrinsic lower limit of 1. */ if (number < 1.0) { xsltNumberFormatDecimal(buffer, number, '0', 1, data->digitsPerGroup, data->groupingCharacter, data->groupingCharacterLen); return; } /* Build buffer from back */ pointer = &temp_string[sizeof(temp_string)]; *(--pointer) = 0; alpha_list = (is_upper) ? alpha_upper_list : alpha_lower_list; for (i = 1; i < (int)sizeof(temp_string); i++) { number--; *(--pointer) = alpha_list[((int)fmod(number, alpha_size))]; number /= alpha_size; if (number < 1.0) break; /* for */ } xmlBufferCCat(buffer, pointer); } static void xsltNumberFormatRoman(xsltNumberDataPtr data, xmlBufferPtr buffer, double number, int is_upper) { /* * See discussion in xsltNumberFormatAlpha. Also use a reasonable upper * bound to avoid denial of service. */ if (number < 1.0 || number > 5000.0) { xsltNumberFormatDecimal(buffer, number, '0', 1, data->digitsPerGroup, data->groupingCharacter, data->groupingCharacterLen); return; } /* * Based on an example by Jim Walsh */ while (number >= 1000.0) { xmlBufferCCat(buffer, (is_upper) ? "M" : "m"); number -= 1000.0; } if (number >= 900.0) { xmlBufferCCat(buffer, (is_upper) ? "CM" : "cm"); number -= 900.0; } while (number >= 500.0) { xmlBufferCCat(buffer, (is_upper) ? "D" : "d"); number -= 500.0; } if (number >= 400.0) { xmlBufferCCat(buffer, (is_upper) ? "CD" : "cd"); number -= 400.0; } while (number >= 100.0) { xmlBufferCCat(buffer, (is_upper) ? "C" : "c"); number -= 100.0; } if (number >= 90.0) { xmlBufferCCat(buffer, (is_upper) ? "XC" : "xc"); number -= 90.0; } while (number >= 50.0) { xmlBufferCCat(buffer, (is_upper) ? "L" : "l"); number -= 50.0; } if (number >= 40.0) { xmlBufferCCat(buffer, (is_upper) ? "XL" : "xl"); number -= 40.0; } while (number >= 10.0) { xmlBufferCCat(buffer, (is_upper) ? "X" : "x"); number -= 10.0; } if (number >= 9.0) { xmlBufferCCat(buffer, (is_upper) ? "IX" : "ix"); number -= 9.0; } while (number >= 5.0) { xmlBufferCCat(buffer, (is_upper) ? "V" : "v"); number -= 5.0; } if (number >= 4.0) { xmlBufferCCat(buffer, (is_upper) ? "IV" : "iv"); number -= 4.0; } while (number >= 1.0) { xmlBufferCCat(buffer, (is_upper) ? "I" : "i"); number--; } } static void xsltNumberFormatTokenize(const xmlChar *format, xsltFormatPtr tokens) { int ix = 0; int j; int val; int len; default_token.token = DEFAULT_TOKEN; default_token.width = 1; default_token.separator = BAD_CAST(DEFAULT_SEPARATOR); tokens->start = NULL; tokens->tokens[0].separator = NULL; tokens->end = NULL; /* * Insert initial non-alphanumeric token. * There is always such a token in the list, even if NULL */ while (! (IS_LETTER(val=xmlStringCurrentChar(NULL, format+ix, &len)) || IS_DIGIT(val)) ) { if (format[ix] == 0) /* if end of format string */ break; /* while */ ix += len; } if (ix > 0) tokens->start = xmlStrndup(format, ix); for (tokens->nTokens = 0; tokens->nTokens < MAX_TOKENS; tokens->nTokens++) { if (format[ix] == 0) break; /* for */ /* * separator has already been parsed (except for the first * number) in tokens->end, recover it. */ if (tokens->nTokens > 0) { tokens->tokens[tokens->nTokens].separator = tokens->end; tokens->end = NULL; } val = xmlStringCurrentChar(NULL, format+ix, &len); if (IS_DIGIT_ONE(val) || IS_DIGIT_ZERO(val)) { tokens->tokens[tokens->nTokens].width = 1; while (IS_DIGIT_ZERO(val)) { tokens->tokens[tokens->nTokens].width++; ix += len; val = xmlStringCurrentChar(NULL, format+ix, &len); } if (IS_DIGIT_ONE(val)) { tokens->tokens[tokens->nTokens].token = val - 1; ix += len; val = xmlStringCurrentChar(NULL, format+ix, &len); } else { tokens->tokens[tokens->nTokens].token = '0'; tokens->tokens[tokens->nTokens].width = 1; } } else if ( (val == 'A') || (val == 'a') || (val == 'I') || (val == 'i') ) { tokens->tokens[tokens->nTokens].token = val; ix += len; val = xmlStringCurrentChar(NULL, format+ix, &len); } else { /* XSLT section 7.7 * "Any other format token indicates a numbering sequence * that starts with that token. If an implementation does * not support a numbering sequence that starts with that * token, it must use a format token of 1." */ tokens->tokens[tokens->nTokens].token = '0'; tokens->tokens[tokens->nTokens].width = 1; } /* * Skip over remaining alphanumeric characters from the Nd * (Number, decimal digit), Nl (Number, letter), No (Number, * other), Lu (Letter, uppercase), Ll (Letter, lowercase), Lt * (Letters, titlecase), Lm (Letters, modifiers), and Lo * (Letters, other (uncased)) Unicode categories. This happens * to correspond to the Letter and Digit classes from XML (and * one wonders why XSLT doesn't refer to these instead). */ while (IS_LETTER(val) || IS_DIGIT(val)) { ix += len; val = xmlStringCurrentChar(NULL, format+ix, &len); } /* * Insert temporary non-alphanumeric final tooken. */ j = ix; while (! (IS_LETTER(val) || IS_DIGIT(val))) { if (val == 0) break; /* while */ ix += len; val = xmlStringCurrentChar(NULL, format+ix, &len); } if (ix > j) tokens->end = xmlStrndup(&format[j], ix - j); } } static void xsltNumberFormatInsertNumbers(xsltNumberDataPtr data, double *numbers, int numbers_max, xsltFormatPtr tokens, xmlBufferPtr buffer) { int i = 0; double number; xsltFormatTokenPtr token; /* * Handle initial non-alphanumeric token */ if (tokens->start != NULL) xmlBufferCat(buffer, tokens->start); for (i = 0; i < numbers_max; i++) { /* Insert number */ number = numbers[(numbers_max - 1) - i]; /* Round to nearest like XSLT 2.0 */ number = floor(number + 0.5); /* * XSLT 1.0 isn't clear on how to handle negative numbers, but XSLT * 2.0 says: * * It is a non-recoverable dynamic error if any undiscarded item * in the atomized sequence supplied as the value of the value * attribute of xsl:number cannot be converted to an integer, or * if the resulting integer is less than 0 (zero). */ if (number < 0.0) { xsltTransformError(NULL, NULL, NULL, "xsl-number : negative value\n"); /* Recover by treating negative values as zero. */ number = 0.0; } if (i < tokens->nTokens) { /* * The "n"th format token will be used to format the "n"th * number in the list */ token = &(tokens->tokens[i]); } else if (tokens->nTokens > 0) { /* * If there are more numbers than format tokens, then the * last format token will be used to format the remaining * numbers. */ token = &(tokens->tokens[tokens->nTokens - 1]); } else { /* * If there are no format tokens, then a format token of * 1 is used to format all numbers. */ token = &default_token; } /* Print separator, except for the first number */ if (i > 0) { if (token->separator != NULL) xmlBufferCat(buffer, token->separator); else xmlBufferCCat(buffer, DEFAULT_SEPARATOR); } switch (xmlXPathIsInf(number)) { case -1: xmlBufferCCat(buffer, "-Infinity"); break; case 1: xmlBufferCCat(buffer, "Infinity"); break; default: if (xmlXPathIsNaN(number)) { xmlBufferCCat(buffer, "NaN"); } else { switch (token->token) { case 'A': xsltNumberFormatAlpha(data, buffer, number, TRUE); break; case 'a': xsltNumberFormatAlpha(data, buffer, number, FALSE); break; case 'I': xsltNumberFormatRoman(data, buffer, number, TRUE); break; case 'i': xsltNumberFormatRoman(data, buffer, number, FALSE); break; default: if (IS_DIGIT_ZERO(token->token)) { xsltNumberFormatDecimal(buffer, number, token->token, token->width, data->digitsPerGroup, data->groupingCharacter, data->groupingCharacterLen); } break; } } } } /* * Handle final non-alphanumeric token */ if (tokens->end != NULL) xmlBufferCat(buffer, tokens->end); } static int xsltTestCompMatchCount(xsltTransformContextPtr context, xmlNodePtr node, xsltCompMatchPtr countPat, xmlNodePtr cur) { if (countPat != NULL) { return xsltTestCompMatchList(context, node, countPat); } else { /* * 7.7 Numbering * * If count attribute is not specified, then it defaults to the * pattern that matches any node with the same node type as the * current node and, if the current node has an expanded-name, with * the same expanded-name as the current node. */ if (node->type != cur->type) return 0; if (node->type == XML_NAMESPACE_DECL) /* * Namespace nodes have no preceding siblings and no parents * that are namespace nodes. This means that node == cur. */ return 1; /* TODO: Skip node types without expanded names like text nodes. */ if (!xmlStrEqual(node->name, cur->name)) return 0; if (node->ns == cur->ns) return 1; if ((node->ns == NULL) || (cur->ns == NULL)) return 0; return (xmlStrEqual(node->ns->href, cur->ns->href)); } } static int xsltNumberFormatGetAnyLevel(xsltTransformContextPtr context, xmlNodePtr node, xsltCompMatchPtr countPat, xsltCompMatchPtr fromPat, double *array) { int amount = 0; int cnt = 0; xmlNodePtr cur = node; while (cur != NULL) { /* process current node */ if (xsltTestCompMatchCount(context, cur, countPat, node)) cnt++; if ((fromPat != NULL) && xsltTestCompMatchList(context, cur, fromPat)) { break; /* while */ } /* Skip to next preceding or ancestor */ if ((cur->type == XML_DOCUMENT_NODE) || #ifdef LIBXML_DOCB_ENABLED (cur->type == XML_DOCB_DOCUMENT_NODE) || #endif (cur->type == XML_HTML_DOCUMENT_NODE)) break; /* while */ if (cur->type == XML_NAMESPACE_DECL) { /* * The XPath module stores the parent of a namespace node in * the ns->next field. */ cur = (xmlNodePtr) ((xmlNsPtr) cur)->next; } else if (cur->type == XML_ATTRIBUTE_NODE) { cur = cur->parent; } else { while ((cur->prev != NULL) && ((cur->prev->type == XML_DTD_NODE) || (cur->prev->type == XML_XINCLUDE_START) || (cur->prev->type == XML_XINCLUDE_END))) cur = cur->prev; if (cur->prev != NULL) { for (cur = cur->prev; cur->last != NULL; cur = cur->last); } else { cur = cur->parent; } } } array[amount++] = (double) cnt; return(amount); } static int xsltNumberFormatGetMultipleLevel(xsltTransformContextPtr context, xmlNodePtr node, xsltCompMatchPtr countPat, xsltCompMatchPtr fromPat, double *array, int max) { int amount = 0; int cnt; xmlNodePtr oldCtxtNode; xmlNodePtr ancestor; xmlNodePtr preceding; xmlXPathParserContextPtr parser; oldCtxtNode = context->xpathCtxt->node; parser = xmlXPathNewParserContext(NULL, context->xpathCtxt); if (parser) { /* ancestor-or-self::*[count] */ ancestor = node; while ((ancestor != NULL) && (ancestor->type != XML_DOCUMENT_NODE)) { if ((fromPat != NULL) && xsltTestCompMatchList(context, ancestor, fromPat)) break; /* for */ /* * The xmlXPathNext* iterators require that the context node is * set to the start node. Calls to xsltTestCompMatch* may also * leave the context node in an undefined state, so make sure * that the context node is reset before each iterator invocation. */ if (xsltTestCompMatchCount(context, ancestor, countPat, node)) { /* count(preceding-sibling::*) */ cnt = 1; context->xpathCtxt->node = ancestor; preceding = xmlXPathNextPrecedingSibling(parser, ancestor); while (preceding != NULL) { if (xsltTestCompMatchCount(context, preceding, countPat, node)) cnt++; context->xpathCtxt->node = ancestor; preceding = xmlXPathNextPrecedingSibling(parser, preceding); } array[amount++] = (double)cnt; if (amount >= max) break; /* for */ } context->xpathCtxt->node = node; ancestor = xmlXPathNextAncestor(parser, ancestor); } xmlXPathFreeParserContext(parser); } context->xpathCtxt->node = oldCtxtNode; return amount; } static int xsltNumberFormatGetValue(xmlXPathContextPtr context, xmlNodePtr node, const xmlChar *value, double *number) { int amount = 0; xmlBufferPtr pattern; xmlXPathObjectPtr obj; pattern = xmlBufferCreate(); if (pattern != NULL) { xmlBufferCCat(pattern, "number("); xmlBufferCat(pattern, value); xmlBufferCCat(pattern, ")"); context->node = node; obj = xmlXPathEvalExpression(xmlBufferContent(pattern), context); if (obj != NULL) { *number = obj->floatval; amount++; xmlXPathFreeObject(obj); } xmlBufferFree(pattern); } return amount; } /** * xsltNumberFormat: * @ctxt: the XSLT transformation context * @data: the formatting information * @node: the data to format * * Convert one number. */ void xsltNumberFormat(xsltTransformContextPtr ctxt, xsltNumberDataPtr data, xmlNodePtr node) { xmlBufferPtr output = NULL; int amount, i; double number; xsltFormat tokens; if (data->format != NULL) { xsltNumberFormatTokenize(data->format, &tokens); } else { xmlChar *format; /* The format needs to be recomputed each time */ if (data->has_format == 0) return; format = xsltEvalAttrValueTemplate(ctxt, data->node, (const xmlChar *) "format", XSLT_NAMESPACE); if (format == NULL) return; xsltNumberFormatTokenize(format, &tokens); xmlFree(format); } output = xmlBufferCreate(); if (output == NULL) goto XSLT_NUMBER_FORMAT_END; /* * Evaluate the XPath expression to find the value(s) */ if (data->value) { amount = xsltNumberFormatGetValue(ctxt->xpathCtxt, node, data->value, &number); if (amount == 1) { xsltNumberFormatInsertNumbers(data, &number, 1, &tokens, output); } } else if (data->level) { if (xmlStrEqual(data->level, (const xmlChar *) "single")) { amount = xsltNumberFormatGetMultipleLevel(ctxt, node, data->countPat, data->fromPat, &number, 1); if (amount == 1) { xsltNumberFormatInsertNumbers(data, &number, 1, &tokens, output); } } else if (xmlStrEqual(data->level, (const xmlChar *) "multiple")) { double numarray[1024]; int max = sizeof(numarray)/sizeof(numarray[0]); amount = xsltNumberFormatGetMultipleLevel(ctxt, node, data->countPat, data->fromPat, numarray, max); if (amount > 0) { xsltNumberFormatInsertNumbers(data, numarray, amount, &tokens, output); } } else if (xmlStrEqual(data->level, (const xmlChar *) "any")) { amount = xsltNumberFormatGetAnyLevel(ctxt, node, data->countPat, data->fromPat, &number); if (amount > 0) { xsltNumberFormatInsertNumbers(data, &number, 1, &tokens, output); } } /* * Unlike `match` patterns, `count` and `from` patterns can contain * variable references, so we have to clear the pattern match * cache if the "direct" matching algorithm was used. */ if (data->countPat != NULL) xsltCompMatchClearCache(ctxt, data->countPat); if (data->fromPat != NULL) xsltCompMatchClearCache(ctxt, data->fromPat); } /* Insert number as text node */ xsltCopyTextString(ctxt, ctxt->insert, xmlBufferContent(output), 0); xmlBufferFree(output); XSLT_NUMBER_FORMAT_END: if (tokens.start != NULL) xmlFree(tokens.start); if (tokens.end != NULL) xmlFree(tokens.end); for (i = 0;i < tokens.nTokens;i++) { if (tokens.tokens[i].separator != NULL) xmlFree(tokens.tokens[i].separator); } } static int xsltFormatNumberPreSuffix(xsltDecimalFormatPtr self, xmlChar **format, xsltFormatNumberInfoPtr info) { /* will hold total length of prefix/suffix without quote characters */ int count=0; int len; while (1) { /* * prefix / suffix ends at end of string or at * first 'special' character */ if (**format == 0) return count; /* if next character 'escaped' just count it */ if (**format == SYMBOL_QUOTE) { if (*++(*format) == 0) return -1; } else if (IS_SPECIAL(self, *format)) return count; /* * else treat percent/per-mille as special cases, * depending on whether +ve or -ve */ else { /* * for +ve prefix/suffix, allow only a * single occurence of either */ if (xsltUTF8Charcmp(*format, self->percent) == 0) { if (info->is_multiplier_set) return -1; info->multiplier = 100; info->is_multiplier_set = TRUE; } else if (xsltUTF8Charcmp(*format, self->permille) == 0) { if (info->is_multiplier_set) return -1; info->multiplier = 1000; info->is_multiplier_set = TRUE; } } if ((len=xmlUTF8Strsize(*format, 1)) < 1) return -1; count += len; *format += len; } } /** * xsltFormatNumberConversion: * @self: the decimal format * @format: the format requested * @number: the value to format * @result: the place to output the result * * format-number() uses the JDK 1.1 DecimalFormat class: * * http://java.sun.com/products/jdk/1.1/docs/api/java.text.DecimalFormat.html * * Structure: * * pattern := subpattern{;subpattern} * subpattern := {prefix}integer{.fraction}{suffix} * prefix := '\\u0000'..'\\uFFFD' - specialCharacters * suffix := '\\u0000'..'\\uFFFD' - specialCharacters * integer := '#'* '0'* '0' * fraction := '0'* '#'* * * Notation: * X* 0 or more instances of X * (X | Y) either X or Y. * X..Y any character from X up to Y, inclusive. * S - T characters in S, except those in T * * Special Characters: * * Symbol Meaning * 0 a digit * # a digit, zero shows as absent * . placeholder for decimal separator * , placeholder for grouping separator. * ; separates formats. * - default negative prefix. * % multiply by 100 and show as percentage * ? multiply by 1000 and show as per mille * X any other characters can be used in the prefix or suffix * ' used to quote special characters in a prefix or suffix. * * Returns a possible XPath error */ xmlXPathError xsltFormatNumberConversion(xsltDecimalFormatPtr self, xmlChar *format, double number, xmlChar **result) { xmlXPathError status = XPATH_EXPRESSION_OK; xmlBufferPtr buffer; xmlChar *the_format, *prefix = NULL, *suffix = NULL; xmlChar *nprefix, *nsuffix = NULL; int prefix_length, suffix_length = 0, nprefix_length, nsuffix_length; double scale; int j, len; int self_grouping_len; xsltFormatNumberInfo format_info; /* * delayed_multiplier allows a 'trailing' percent or * permille to be treated as suffix */ int delayed_multiplier = 0; /* flag to show no -ve format present for -ve number */ char default_sign = 0; /* flag to show error found, should use default format */ char found_error = 0; if (xmlStrlen(format) <= 0) { xsltTransformError(NULL, NULL, NULL, "xsltFormatNumberConversion : " "Invalid format (0-length)\n"); } *result = NULL; switch (xmlXPathIsInf(number)) { case -1: if (self->minusSign == NULL) *result = xmlStrdup(BAD_CAST "-"); else *result = xmlStrdup(self->minusSign); /* Intentional fall-through */ case 1: if ((self == NULL) || (self->infinity == NULL)) *result = xmlStrcat(*result, BAD_CAST "Infinity"); else *result = xmlStrcat(*result, self->infinity); return(status); default: if (xmlXPathIsNaN(number)) { if ((self == NULL) || (self->noNumber == NULL)) *result = xmlStrdup(BAD_CAST "NaN"); else *result = xmlStrdup(self->noNumber); return(status); } } buffer = xmlBufferCreate(); if (buffer == NULL) { return XPATH_MEMORY_ERROR; } format_info.integer_hash = 0; format_info.integer_digits = 0; format_info.frac_digits = 0; format_info.frac_hash = 0; format_info.group = -1; format_info.multiplier = 1; format_info.add_decimal = FALSE; format_info.is_multiplier_set = FALSE; format_info.is_negative_pattern = FALSE; the_format = format; /* * First we process the +ve pattern to get percent / permille, * as well as main format */ prefix = the_format; prefix_length = xsltFormatNumberPreSuffix(self, &the_format, &format_info); if (prefix_length < 0) { found_error = 1; goto OUTPUT_NUMBER; } /* * Here we process the "number" part of the format. It gets * a little messy because of the percent/per-mille - if that * appears at the end, it may be part of the suffix instead * of part of the number, so the variable delayed_multiplier * is used to handle it */ self_grouping_len = xmlStrlen(self->grouping); while ((*the_format != 0) && (xsltUTF8Charcmp(the_format, self->decimalPoint) != 0) && (xsltUTF8Charcmp(the_format, self->patternSeparator) != 0)) { if (delayed_multiplier != 0) { format_info.multiplier = delayed_multiplier; format_info.is_multiplier_set = TRUE; delayed_multiplier = 0; } if (xsltUTF8Charcmp(the_format, self->digit) == 0) { if (format_info.integer_digits > 0) { found_error = 1; goto OUTPUT_NUMBER; } format_info.integer_hash++; if (format_info.group >= 0) format_info.group++; } else if (xsltUTF8Charcmp(the_format, self->zeroDigit) == 0) { format_info.integer_digits++; if (format_info.group >= 0) format_info.group++; } else if ((self_grouping_len > 0) && (!xmlStrncmp(the_format, self->grouping, self_grouping_len))) { /* Reset group count */ format_info.group = 0; the_format += self_grouping_len; continue; } else if (xsltUTF8Charcmp(the_format, self->percent) == 0) { if (format_info.is_multiplier_set) { found_error = 1; goto OUTPUT_NUMBER; } delayed_multiplier = 100; } else if (xsltUTF8Charcmp(the_format, self->permille) == 0) { if (format_info.is_multiplier_set) { found_error = 1; goto OUTPUT_NUMBER; } delayed_multiplier = 1000; } else break; /* while */ if ((len=xmlUTF8Strsize(the_format, 1)) < 1) { found_error = 1; goto OUTPUT_NUMBER; } the_format += len; } /* We have finished the integer part, now work on fraction */ if ( (*the_format != 0) && (xsltUTF8Charcmp(the_format, self->decimalPoint) == 0) ) { format_info.add_decimal = TRUE; if ((len = xmlUTF8Strsize(the_format, 1)) < 1) { found_error = 1; goto OUTPUT_NUMBER; } the_format += len; /* Skip over the decimal */ } while (*the_format != 0) { if (xsltUTF8Charcmp(the_format, self->zeroDigit) == 0) { if (format_info.frac_hash != 0) { found_error = 1; goto OUTPUT_NUMBER; } format_info.frac_digits++; } else if (xsltUTF8Charcmp(the_format, self->digit) == 0) { format_info.frac_hash++; } else if (xsltUTF8Charcmp(the_format, self->percent) == 0) { if (format_info.is_multiplier_set) { found_error = 1; goto OUTPUT_NUMBER; } delayed_multiplier = 100; if ((len = xmlUTF8Strsize(the_format, 1)) < 1) { found_error = 1; goto OUTPUT_NUMBER; } the_format += len; continue; /* while */ } else if (xsltUTF8Charcmp(the_format, self->permille) == 0) { if (format_info.is_multiplier_set) { found_error = 1; goto OUTPUT_NUMBER; } delayed_multiplier = 1000; if ((len = xmlUTF8Strsize(the_format, 1)) < 1) { found_error = 1; goto OUTPUT_NUMBER; } the_format += len; continue; /* while */ } else if (xsltUTF8Charcmp(the_format, self->grouping) != 0) { break; /* while */ } if ((len = xmlUTF8Strsize(the_format, 1)) < 1) { found_error = 1; goto OUTPUT_NUMBER; } the_format += len; if (delayed_multiplier != 0) { format_info.multiplier = delayed_multiplier; delayed_multiplier = 0; format_info.is_multiplier_set = TRUE; } } /* * If delayed_multiplier is set after processing the * "number" part, should be in suffix */ if (delayed_multiplier != 0) { the_format -= len; delayed_multiplier = 0; } suffix = the_format; suffix_length = xsltFormatNumberPreSuffix(self, &the_format, &format_info); if ( (suffix_length < 0) || ((*the_format != 0) && (xsltUTF8Charcmp(the_format, self->patternSeparator) != 0)) ) { found_error = 1; goto OUTPUT_NUMBER; } /* * We have processed the +ve prefix, number part and +ve suffix. * If the number is -ve, we must substitute the -ve prefix / suffix */ if (number < 0) { /* * Note that j is the number of UTF8 chars before the separator, * not the number of bytes! (bug 151975) */ j = xmlUTF8Strloc(format, self->patternSeparator); if (j < 0) { /* No -ve pattern present, so use default signing */ default_sign = 1; } else { /* Skip over pattern separator (accounting for UTF8) */ the_format = (xmlChar *)xmlUTF8Strpos(format, j + 1); /* * Flag changes interpretation of percent/permille * in -ve pattern */ format_info.is_negative_pattern = TRUE; format_info.is_multiplier_set = FALSE; /* First do the -ve prefix */ nprefix = the_format; nprefix_length = xsltFormatNumberPreSuffix(self, &the_format, &format_info); if (nprefix_length<0) { found_error = 1; goto OUTPUT_NUMBER; } while (*the_format != 0) { if ( (xsltUTF8Charcmp(the_format, (self)->percent) == 0) || (xsltUTF8Charcmp(the_format, (self)->permille)== 0) ) { if (format_info.is_multiplier_set) { found_error = 1; goto OUTPUT_NUMBER; } format_info.is_multiplier_set = TRUE; delayed_multiplier = 1; } else if (IS_SPECIAL(self, the_format)) delayed_multiplier = 0; else break; /* while */ if ((len = xmlUTF8Strsize(the_format, 1)) < 1) { found_error = 1; goto OUTPUT_NUMBER; } the_format += len; } if (delayed_multiplier != 0) { format_info.is_multiplier_set = FALSE; the_format -= len; } /* Finally do the -ve suffix */ if (*the_format != 0) { nsuffix = the_format; nsuffix_length = xsltFormatNumberPreSuffix(self, &the_format, &format_info); if (nsuffix_length < 0) { found_error = 1; goto OUTPUT_NUMBER; } } else nsuffix_length = 0; if (*the_format != 0) { found_error = 1; goto OUTPUT_NUMBER; } /* * Here's another Java peculiarity: * if -ve prefix/suffix == +ve ones, discard & use default */ if ((nprefix_length != prefix_length) || (nsuffix_length != suffix_length) || ((nprefix_length > 0) && (xmlStrncmp(nprefix, prefix, prefix_length) !=0 )) || ((nsuffix_length > 0) && (xmlStrncmp(nsuffix, suffix, suffix_length) !=0 ))) { prefix = nprefix; prefix_length = nprefix_length; suffix = nsuffix; suffix_length = nsuffix_length; } /* else { default_sign = 1; } */ } } OUTPUT_NUMBER: if (found_error != 0) { xsltTransformError(NULL, NULL, NULL, "xsltFormatNumberConversion : " "error in format string '%s', using default\n", format); default_sign = (number < 0.0) ? 1 : 0; prefix_length = suffix_length = 0; format_info.integer_hash = 0; format_info.integer_digits = 1; format_info.frac_digits = 1; format_info.frac_hash = 4; format_info.group = -1; format_info.multiplier = 1; format_info.add_decimal = TRUE; } /* Ready to output our number. First see if "default sign" is required */ if (default_sign != 0) xmlBufferAdd(buffer, self->minusSign, xmlUTF8Strsize(self->minusSign, 1)); /* Put the prefix into the buffer */ for (j = 0; j < prefix_length; ) { if (*prefix == SYMBOL_QUOTE) prefix++; len = xmlUTF8Strsize(prefix, 1); xmlBufferAdd(buffer, prefix, len); prefix += len; j += len; } /* Next do the integer part of the number */ number = fabs(number) * (double)format_info.multiplier; scale = pow(10.0, (double)(format_info.frac_digits + format_info.frac_hash)); number = floor((scale * number + 0.5)) / scale; if ((self->grouping != NULL) && (self->grouping[0] != 0)) { int gchar; len = xmlStrlen(self->grouping); gchar = xsltGetUTF8Char(self->grouping, &len); xsltNumberFormatDecimal(buffer, floor(number), self->zeroDigit[0], format_info.integer_digits, format_info.group, gchar, len); } else xsltNumberFormatDecimal(buffer, floor(number), self->zeroDigit[0], format_info.integer_digits, format_info.group, ',', 1); /* Special case: java treats '.#' like '.0', '.##' like '.0#', etc. */ if ((format_info.integer_digits + format_info.integer_hash + format_info.frac_digits == 0) && (format_info.frac_hash > 0)) { ++format_info.frac_digits; --format_info.frac_hash; } /* Add leading zero, if required */ if ((floor(number) == 0) && (format_info.integer_digits + format_info.frac_digits == 0)) { xmlBufferAdd(buffer, self->zeroDigit, xmlUTF8Strsize(self->zeroDigit, 1)); } /* Next the fractional part, if required */ if (format_info.frac_digits + format_info.frac_hash == 0) { if (format_info.add_decimal) xmlBufferAdd(buffer, self->decimalPoint, xmlUTF8Strsize(self->decimalPoint, 1)); } else { number -= floor(number); if ((number != 0) || (format_info.frac_digits != 0)) { xmlBufferAdd(buffer, self->decimalPoint, xmlUTF8Strsize(self->decimalPoint, 1)); number = floor(scale * number + 0.5); for (j = format_info.frac_hash; j > 0; j--) { if (fmod(number, 10.0) >= 1.0) break; /* for */ number /= 10.0; } xsltNumberFormatDecimal(buffer, floor(number), self->zeroDigit[0], format_info.frac_digits + j, 0, 0, 0); } } /* Put the suffix into the buffer */ for (j = 0; j < suffix_length; ) { if (*suffix == SYMBOL_QUOTE) suffix++; len = xmlUTF8Strsize(suffix, 1); xmlBufferAdd(buffer, suffix, len); suffix += len; j += len; } *result = xmlStrdup(xmlBufferContent(buffer)); xmlBufferFree(buffer); return status; }