Git conversion: Make reactos the root directory, move rosapps, rostests, wallpapers...
[reactos.git] / dll / 3rdparty / libxslt / numbers.c
diff --git a/dll/3rdparty/libxslt/numbers.c b/dll/3rdparty/libxslt/numbers.c
new file mode 100644 (file)
index 0000000..790300d
--- /dev/null
@@ -0,0 +1,1352 @@
+/*
+ * 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 <breese@users.sourceforge.net>
+ */
+
+#include "precomp.h"
+
+#ifndef FALSE
+# define FALSE (0 == 1)
+# define TRUE (1 == 1)
+#endif
+
+#define SYMBOL_QUOTE           ((xmlChar)'\'')
+
+#define DEFAULT_TOKEN          (xmlChar)'0'
+#define DEFAULT_SEPARATOR      "."
+
+#define MAX_TOKENS             1024
+
+typedef struct _xsltFormatToken xsltFormatToken;
+typedef xsltFormatToken *xsltFormatTokenPtr;
+struct _xsltFormatToken {
+    xmlChar    *separator;
+    xmlChar     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 two routines (xsltUTF8Size and xsltUTF8Charcmp)
+ * will be replaced with calls to the corresponding libxml routines
+ * at a later date (when other inter-library dependencies require it)
+ */
+
+/**
+ * xsltUTF8Size:
+ * @utf: pointer to the UTF8 character
+ *
+ * returns the numbers of bytes in the character, -1 on format error
+ */
+static int
+xsltUTF8Size(xmlChar *utf) {
+    xmlChar mask;
+    int len;
+
+    if (utf == NULL)
+        return -1;
+    if (*utf < 0x80)
+        return 1;
+    /* check valid UTF8 character */
+    if (!(*utf & 0x40))
+        return -1;
+    /* determine number of bytes in char */
+    len = 2;
+    for (mask=0x20; mask != 0; mask>>=1) {
+        if (!(*utf & mask))
+            return len;
+        len++;
+    }
+    return -1;
+}
+
+/**
+ * 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) {
+
+    if (utf1 == NULL ) {
+        if (utf2 == NULL)
+            return 0;
+        return -1;
+    }
+    return xmlStrncmp(utf1, utf2, xsltUTF8Size(utf1));
+}
+
+/***** 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((xmlChar)(x)-1)
+
+static int
+xsltIsDigitZero(unsigned int ch)
+{
+    /*
+     * Reference: ftp://ftp.unicode.org/Public/UNIDATA/UnicodeData.txt
+     */
+    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 0x0E60: case 0x0F20: case 0x1040: case 0x17E0:
+    case 0x1810: case 0xFF10:
+       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");
+    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 if ( (val == (xmlChar)'A') ||
+                   (val == (xmlChar)'a') ||
+                   (val == (xmlChar)'I') ||
+                   (val == (xmlChar)'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 = (xmlChar)'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;
+
+    /* select the starting node */
+    switch (node->type) {
+       case XML_ELEMENT_NODE:
+           cur = node;
+           break;
+       case XML_ATTRIBUTE_NODE:
+           cur = ((xmlAttrPtr) node)->parent;
+           break;
+       case XML_TEXT_NODE:
+       case XML_PI_NODE:
+       case XML_COMMENT_NODE:
+           cur = node->parent;
+           break;
+       default:
+           cur = NULL;
+           break;
+    }
+
+    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 */
+
+       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 ancestor;
+    xmlNodePtr preceding;
+    xmlXPathParserContextPtr parser;
+
+    context->xpathCtxt->node = node;
+    parser = xmlXPathNewParserContext(NULL, context->xpathCtxt);
+    if (parser) {
+       /* ancestor-or-self::*[count] */
+       for (ancestor = node;
+            (ancestor != NULL) && (ancestor->type != XML_DOCUMENT_NODE);
+            ancestor = xmlXPathNextAncestor(parser, ancestor)) {
+
+           if ((fromPat != NULL) &&
+               xsltTestCompMatchList(context, ancestor, fromPat))
+               break; /* for */
+
+           if (xsltTestCompMatchCount(context, ancestor, countPat, node)) {
+               /* count(preceding-sibling::*) */
+               cnt = 1;
+               for (preceding =
+                        xmlXPathNextPrecedingSibling(parser, ancestor);
+                    preceding != NULL;
+                    preceding =
+                       xmlXPathNextPrecedingSibling(parser, preceding)) {
+
+                   if (xsltTestCompMatchCount(context, preceding, countPat,
+                                               node))
+                       cnt++;
+               }
+               array[amount++] = (double)cnt;
+               if (amount >= max)
+                   break; /* for */
+           }
+       }
+       xmlXPathFreeParserContext(parser);
+    }
+    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 informations
+ * @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);
+           }
+       }
+    }
+    /* 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)
+{
+    int        count=0;        /* will hold total length of prefix/suffix */
+    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=xsltUTF8Size(*format)) < 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 ouput 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;
+    xmlChar pchar;
+    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);
+           /* no-break on purpose */
+       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=xsltUTF8Size(the_format)) < 1) {
+           found_error = 1;
+           goto OUTPUT_NUMBER;
+       }
+       the_format += len;
+
+    }
+
+    /* We have finished the integer part, now work on fraction */
+    if (xsltUTF8Charcmp(the_format, self->decimalPoint) == 0) {
+        format_info.add_decimal = TRUE;
+       the_format += xsltUTF8Size(the_format); /* 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 = xsltUTF8Size(the_format)) < 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 = xsltUTF8Size(the_format)) < 1) {
+               found_error = 1;
+               goto OUTPUT_NUMBER;
+           }
+           the_format += len;
+           continue; /* while */
+       } else if (xsltUTF8Charcmp(the_format, self->grouping) != 0) {
+           break; /* while */
+       }
+       if ((len = xsltUTF8Size(the_format)) < 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 = xsltUTF8Size(the_format)) < 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, xsltUTF8Size(self->minusSign));
+
+    /* Put the prefix into the buffer */
+    for (j = 0; j < prefix_length; j++) {
+       if ((pchar = *prefix++) == SYMBOL_QUOTE) {
+           len = xsltUTF8Size(prefix);
+           xmlBufferAdd(buffer, prefix, len);
+           prefix += len;
+           j += len - 1;       /* length of symbol less length of quote */
+       } else
+           xmlBufferAdd(buffer, &pchar, 1);
+    }
+
+    /* 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)) {
+
+       len = xmlStrlen(self->grouping);
+       pchar = xsltGetUTF8Char(self->grouping, &len);
+       xsltNumberFormatDecimal(buffer, floor(number), self->zeroDigit[0],
+                               format_info.integer_digits,
+                               format_info.group,
+                               pchar, 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, xsltUTF8Size(self->zeroDigit));
+    }
+
+    /* 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,
+                        xsltUTF8Size(self->decimalPoint));
+    }
+    else {
+      number -= floor(number);
+       if ((number != 0) || (format_info.frac_digits != 0)) {
+           xmlBufferAdd(buffer, self->decimalPoint,
+                        xsltUTF8Size(self->decimalPoint));
+           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; j++) {
+       if ((pchar = *suffix++) == SYMBOL_QUOTE) {
+            len = xsltUTF8Size(suffix);
+           xmlBufferAdd(buffer, suffix, len);
+           suffix += len;
+           j += len - 1;       /* length of symbol less length of escape */
+       } else
+           xmlBufferAdd(buffer, &pchar, 1);
+    }
+
+    *result = xmlStrdup(xmlBufferContent(buffer));
+    xmlBufferFree(buffer);
+    return status;
+}
+