From 77d609e8e84956abe5b83b07bced2058cbec7fef Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 6 Mar 2024 18:18:29 -0500 Subject: [PATCH] Fix real number support in non-English locales (Issue #311) --- CHANGES.md | 1 + mxml-entity.c | 46 ++++++--------- mxml-file.c | 151 ++++++++++++++++++++++++++++++++++++++++++++----- mxml-private.c | 4 +- mxml-private.h | 21 ++++--- mxml.h | 14 ++--- test.xml | 2 +- 7 files changed, 181 insertions(+), 58 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f8925e5..e9f7877 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ (Issue #51) - Updated the load and save callbacks to include a context pointer (Issue #106) - Fixed some warnings (Issue #301) +- Fixed real number support in non-English locales (Issue #311) # Changes in Mini-XML 3.3.2 diff --git a/mxml-entity.c b/mxml-entity.c index 1d8bab6..727a1d4 100644 --- a/mxml-entity.c +++ b/mxml-entity.c @@ -18,7 +18,8 @@ bool // O - `true` on success, `false` on failure mxmlEntityAddCallback( - mxml_entity_cb_t cb) // I - Callback function to add + mxml_entity_cb_t cb, // I - Callback function to add + void *cbdata) // I - Callback data { _mxml_global_t *global = _mxml_global(); // Global data @@ -26,7 +27,8 @@ mxmlEntityAddCallback( if (global->num_entity_cbs < (sizeof(global->entity_cbs) / sizeof(global->entity_cbs[0]))) { - global->entity_cbs[global->num_entity_cbs] = cb; + global->entity_cbs[global->num_entity_cbs] = cb; + global->entity_cbdatas[global->num_entity_cbs] = cbdata; global->num_entity_cbs ++; return (true); @@ -85,7 +87,7 @@ mxmlEntityGetValue(const char *name) // I - Entity name for (i = 0; i < global->num_entity_cbs; i ++) { - if ((ch = (global->entity_cbs[i])(name)) >= 0) + if ((ch = (global->entity_cbs[i])(global->entity_cbdatas[i], name)) >= 0) return (ch); } @@ -114,8 +116,10 @@ mxmlEntityRemoveCallback( global->num_entity_cbs --; if (i < global->num_entity_cbs) + { memmove(global->entity_cbs + i, global->entity_cbs + i + 1, (global->num_entity_cbs - i) * sizeof(global->entity_cbs[0])); - + memmove(global->entity_cbdatas + i, global->entity_cbdatas + i + 1, (global->num_entity_cbs - i) * sizeof(global->entity_cbdatas[0])); + } return; } } @@ -127,12 +131,11 @@ mxmlEntityRemoveCallback( // int // O - Unicode value or -1 -_mxml_entity_cb(const char *name) // I - Entity name +_mxml_entity_cb(void *cbdata, // I - Callback data (unused) + const char *name) // I - Entity name { - int diff; // Difference between names - size_t current, // Current entity in search - first, // First entity in search - last; // Last entity in search + size_t i; // Looping var + int diff; // Difference static const struct { const char *name; // Entity name @@ -399,27 +402,14 @@ _mxml_entity_cb(const char *name) // I - Entity name }; - // Do a binary search for the named entity... - first = 0; - last = sizeof(entities) / sizeof(entities[0]) - 1; - - while ((last - first) > 1) + // Do a linear search for the named entity... + for (i = 0; i < (sizeof(entities) / sizeof(entities[0])); i ++) { - current = (first + last) / 2; - - if ((diff = strcmp(name, entities[current].name)) == 0) - return (entities[current].val); + if ((diff = strcmp(name, entities[i].name)) == 0) + return (entities[i].val); else if (diff < 0) - last = current; - else - first = current; + break; } - // If we get here, there is a small chance that there is still a match; check first and last... - if (!strcmp(name, entities[first].name)) - return (entities[first].val); - else if (!strcmp(name, entities[last].name)) - return (entities[last].val); - else - return (-1); + return (-1); } diff --git a/mxml-file.c b/mxml-file.c index bbd0aee..e19affa 100644 --- a/mxml-file.c +++ b/mxml-file.c @@ -58,6 +58,8 @@ static int mxml_parse_element(mxml_read_cb_t read_cb, void *read_cbdata, mxml_n static ssize_t mxml_read_cb_fd(int *fd, void *buffer, size_t bytes); static ssize_t mxml_read_cb_file(FILE *fp, void *buffer, size_t bytes); static ssize_t mxml_read_cb_string(_mxml_stringbuf_t *sb, void *buffer, size_t bytes); +static void mxml_set_loc(_mxml_global_t *global); +static double mxml_strtod(_mxml_global_t *global, const char *buffer, char **bufptr); static ssize_t mxml_write_cb_fd(int *fd, const void *buffer, size_t bytes); static ssize_t mxml_write_cb_file(FILE *fp, const void *buffer, size_t bytes); static ssize_t mxml_write_cb_string(_mxml_stringbuf_t *sb, const void *buffer, size_t bytes); @@ -536,16 +538,18 @@ mxmlSaveString( // void -mxmlSetCustomHandlers( - mxml_custom_load_cb_t load, // I - Load function - mxml_custom_save_cb_t save) // I - Save function +mxmlSetCustomCallbacks( + mxml_custom_load_cb_t load_cb, // I - Load callback function + mxml_custom_save_cb_t save_cb, // I - Save callback function + void *cbdata) // I - Callback data { _mxml_global_t *global = _mxml_global(); // Global data - global->custom_load_cb = load; - global->custom_save_cb = save; + global->custom_load_cb = load_cb; + global->custom_save_cb = save_cb; + global->custom_cbdata = cbdata; } @@ -554,13 +558,16 @@ mxmlSetCustomHandlers( // void -mxmlSetErrorCallback(mxml_error_cb_t cb)// I - Error callback function +mxmlSetErrorCallback( + mxml_error_cb_t cb, // I - Error callback function + void *cbdata) // I - Error callback data { _mxml_global_t *global = _mxml_global(); // Global data - global->error_cb = cb; + global->error_cb = cb; + global->error_cbdata = cbdata; } @@ -702,7 +709,8 @@ mxml_get_entity( } else if ((ch = mxmlEntityGetValue(entity)) < 0) { - _mxml_error("Entity name '%s;' not supported under parent <%s> on line %d.", entity, mxmlGetElement(parent), *line); + _mxml_error("Entity '&%s;' not supported under parent <%s> on line %d.", entity, mxmlGetElement(parent), *line); + return (EOF); } if (mxml_bad_char(ch)) @@ -989,7 +997,7 @@ mxml_load_data( break; case MXML_TYPE_REAL : - node = mxmlNewReal(parent, strtod(buffer, &bufptr)); + node = mxmlNewReal(parent, mxml_strtod(global, buffer, &bufptr)); break; case MXML_TYPE_TEXT : @@ -1002,7 +1010,7 @@ mxml_load_data( // Use the callback to fill in the custom data... node = mxmlNewCustom(parent, NULL, NULL); - if (!(*global->custom_load_cb)(node, buffer)) + if (!(*global->custom_load_cb)(global->custom_cbdata, node, buffer)) { _mxml_error("Bad custom value '%s' in parent <%s> on line %d.", buffer, parent ? parent->value.element.name : "null", line); mxmlDelete(node); @@ -1881,6 +1889,106 @@ mxml_read_cb_string( } +// +// 'mxml_set_loc()' - Set the locale values for numbers. +// + +static void +mxml_set_loc(_mxml_global_t *global) // I - Global data +{ + if ((global->loc = localeconv()) != NULL) + { + if (!global->loc->decimal_point || !strcmp(global->loc->decimal_point, ".")) + global->loc = NULL; + else + global->loc_declen = strlen(global->loc->decimal_point); + } + + global->loc_set = true; +} + + +// +// 'mxml_strtod()' - Convert a string to a double without respect to the locale. +// + +static double // O - Real number +mxml_strtod(_mxml_global_t *global, // I - Global data + const char *buffer, // I - String + char **bufend) // O - End of number in string +{ + const char *bufptr; // Pointer into buffer + char temp[64], // Temporary buffer + *tempptr; // Pointer into temporary buffer + + + // See if the locale has a special decimal point string... + if (!global->loc_set) + mxml_set_loc(global); + + if (!global->loc) + return (strtod(buffer, bufend)); + + // Copy leading sign, numbers, period, and then numbers... + tempptr = temp; + temp[sizeof(temp) - 1] = '\0'; + + if (*bufptr == '-' || *bufptr == '+') + *tempptr++ = *bufptr++; + + for (bufptr = buffer; *bufptr && isdigit(*bufptr & 255); bufptr ++) + { + if (tempptr < (temp + sizeof(temp) - 1)) + { + *tempptr++ = *bufptr; + } + else + { + *bufend = (char *)bufptr; + return (0.0); + } + } + + if (*bufptr == '.') + { + // Convert decimal point to locale equivalent... + size_t declen = strlen(global->loc->decimal_point); + // Length of decimal point + bufptr ++; + + if (declen <= (sizeof(temp) - (size_t)(tempptr - temp))) + { + memcpy(tempptr, global->loc->decimal_point, declen); + tempptr += declen; + } + else + { + *bufend = (char *)bufptr; + return (0.0); + } + } + + // Copy any remaining characters... + while (*bufptr && isdigit(*bufptr & 255)) + { + if (tempptr < (temp + sizeof(temp) - 1)) + *tempptr++ = *bufptr++; + else + break; + } + + *bufend = (char *)bufptr; + + if (*bufptr) + return (0.0); + + // Nul-terminate the temporary string and convert the string... + *tempptr = '\0'; + + return (strtod(temp, NULL)); +} + + // // 'mxml_write_cb_fd()' - Write bytes to a file descriptor. // @@ -2079,8 +2187,25 @@ mxml_write_node( } // Write real number... - // TODO: Provide locale-neutral formatting/scanning code for REAL - snprintf(s, sizeof(s), "%f", current->value.real); + snprintf(s, sizeof(s), "%g", current->value.real); + + if (!global->loc_set) + mxml_set_loc(global); + + if (global->loc) + { + char *sptr; // Pointer into string + + if ((sptr = strstr(s, global->loc->decimal_point)) != NULL) + { + // Convert locale decimal point to "." + if (global->loc_declen > 1) + memmove(sptr + 1, sptr + global->loc_declen, strlen(sptr + global->loc_declen) + 1); + + *sptr = '.'; + } + } + col = mxml_write_string(write_cb, write_cbdata, s, /*use_entities*/true, col); break; @@ -2103,7 +2228,7 @@ mxml_write_node( if (!global->custom_save_cb) return (-1); - if ((data = (*global->custom_save_cb)(current)) == NULL) + if ((data = (*global->custom_save_cb)(global->custom_cbdata, current)) == NULL) return (-1); col = mxml_write_string(write_cb, write_cbdata, data, /*use_entities*/true, col); diff --git a/mxml-private.c b/mxml-private.c index cc0838f..a40b80a 100644 --- a/mxml-private.c +++ b/mxml-private.c @@ -45,7 +45,7 @@ void _mxml_error(const char *format, // I - Printf-style format string - ...) // I - Additional arguments as needed + ...) // I - Additional arguments as needed { va_list ap; // Pointer to arguments char s[1024]; // Message string @@ -64,7 +64,7 @@ _mxml_error(const char *format, // I - Printf-style format string // And then display the error message... if (global->error_cb) - (*global->error_cb)(s); + (*global->error_cb)(global->error_cbdata, s); else fprintf(stderr, "%s\n", s); } diff --git a/mxml-private.h b/mxml-private.h index fb27b0b..8163aa7 100644 --- a/mxml-private.h +++ b/mxml-private.h @@ -13,6 +13,7 @@ # define MXML_PRIVATE_H # include "config.h" # include "mxml.h" +# include // @@ -94,12 +95,18 @@ struct _mxml_index_s // An XML node index. typedef struct _mxml_global_s // Global, per-thread data { - void (*error_cb)(const char *); - size_t num_entity_cbs; - int (*entity_cbs[100])(const char *name); - int wrap; - mxml_custom_load_cb_t custom_load_cb; - mxml_custom_save_cb_t custom_save_cb; + mxml_custom_load_cb_t custom_load_cb; // Custom load callback function + mxml_custom_save_cb_t custom_save_cb; // Custom save callback function + void *custom_cbdata; // Custom callback data + mxml_error_cb_t error_cb; // Error callback function + void *error_cbdata; // Error callback data + size_t num_entity_cbs; // Number of entity callbacks + mxml_entity_cb_t entity_cbs[100]; // Entity callback functions + void *entity_cbdatas[100]; // Entity callback data + struct lconv *loc; // Locale data + size_t loc_declen; // Length of decimal point string + bool loc_set; // Locale data set? + int wrap; // Wrap margin } _mxml_global_t; @@ -108,7 +115,7 @@ typedef struct _mxml_global_s // Global, per-thread data // extern _mxml_global_t *_mxml_global(void); -extern int _mxml_entity_cb(const char *name); +extern int _mxml_entity_cb(void *cbdata, const char *name); extern const char *_mxml_entity_string(int ch); extern void _mxml_error(const char *format, ...) MXML_FORMAT(1,2); diff --git a/mxml.h b/mxml.h index 5370f2b..775600f 100644 --- a/mxml.h +++ b/mxml.h @@ -95,7 +95,7 @@ typedef enum mxml_ws_e // Whitespace periods typedef void (*mxml_custom_destroy_cb_t)(void *); // Custom data destructor -typedef void (*mxml_error_cb_t)(const char *); +typedef void (*mxml_error_cb_t)(void *cbdata, const char *message); // Error callback function typedef struct _mxml_node_s mxml_node_t;// An XML node. @@ -103,13 +103,13 @@ typedef struct _mxml_node_s mxml_node_t;// An XML node. typedef struct _mxml_index_s mxml_index_t; // An XML node index. -typedef bool (*mxml_custom_load_cb_t)(mxml_node_t *node, const char *s); +typedef bool (*mxml_custom_load_cb_t)(void *cbdata, mxml_node_t *node, const char *s); // Custom data load callback function -typedef char *(*mxml_custom_save_cb_t)(mxml_node_t *node); +typedef char *(*mxml_custom_save_cb_t)(void *cbdata, mxml_node_t *node); // Custom data save callback function -typedef int (*mxml_entity_cb_t)(const char *name); +typedef int (*mxml_entity_cb_t)(void *cbdata, const char *name); // Entity callback function typedef mxml_type_t (*mxml_load_cb_t)(void *cbdata, mxml_node_t *node); @@ -142,7 +142,7 @@ extern const char *mxmlElementGetAttrByIndex(mxml_node_t *node, int idx, c extern size_t mxmlElementGetAttrCount(mxml_node_t *node); extern void mxmlElementSetAttr(mxml_node_t *node, const char *name, const char *value); extern void mxmlElementSetAttrf(mxml_node_t *node, const char *name, const char *format, ...) MXML_FORMAT(3,4); -extern bool mxmlEntityAddCallback(mxml_entity_cb_t cb); +extern bool mxmlEntityAddCallback(mxml_entity_cb_t cb, void *cbdata); extern int mxmlEntityGetValue(const char *name); extern void mxmlEntityRemoveCallback(mxml_entity_cb_t cb); @@ -218,9 +218,9 @@ extern bool mxmlSetDeclarationf(mxml_node_t *node, const char *format, ...) MXM extern bool mxmlSetDirective(mxml_node_t *node, const char *directive); extern bool mxmlSetDirectivef(mxml_node_t *node, const char *format, ...) MXML_FORMAT(2,3); extern bool mxmlSetCustom(mxml_node_t *node, void *data, mxml_custom_destroy_cb_t destroy); -extern void mxmlSetCustomHandlers(mxml_custom_load_cb_t load, mxml_custom_save_cb_t save); +extern void mxmlSetCustomCallbacks(mxml_custom_load_cb_t load_cb, mxml_custom_save_cb_t save_cb, void *cbdata); extern bool mxmlSetElement(mxml_node_t *node, const char *name); -extern void mxmlSetErrorCallback(mxml_error_cb_t cb); +extern void mxmlSetErrorCallback(mxml_error_cb_t cb, void *cbdata); extern bool mxmlSetInteger(mxml_node_t *node, long integer); extern bool mxmlSetOpaque(mxml_node_t *node, const char *opaque); extern bool mxmlSetOpaquef(mxml_node_t *node, const char *format, ...) MXML_FORMAT(2,3); diff --git a/test.xml b/test.xml index b59599a..3f260a7 100644 --- a/test.xml +++ b/test.xml @@ -4,7 +4,7 @@ InputSlot Auto Media Source - 10.000000 + 10.42 Auto Auto Tray Selection