diff --git a/.gitignore b/.gitignore index 4866813e..9abeeb43 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ testlogs-*/ CMakeFiles/ ThirdParty/winflexbison/ doc/ +CMakeSettings.json +.vs/ +out/ +enc_temp_folder/ diff --git a/CMakeLists.txt b/CMakeLists.txt index f7372bf2..6fb6362a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ option(FMILIB_LINK_TEST_TO_SHAREDLIB "Link the tests to fmilib_shared (if built) option(FMILIB_GENERATE_BUILD_STAMP "Generate a build time stamp and include in into the library" OFF) option(FMILIB_ENABLE_LOG_LEVEL_DEBUG "Enable log level 'debug'. If the option is of then the debug level is not compiled in." OFF) option(FMILIB_PRINT_DEBUG_MESSAGES "Enable printing of status messages from the build script. Intended for debugging." OFF) +option(FMILIB_TEST_LOCALE "Perform testing related to setting locales (requires certain language packs)" OFF) mark_as_advanced(FMILIB_PRINT_DEBUG_MESSAGES FMILIB_DEBUG_TRACE) if(NOT FMILIB_BUILD_SHARED_LIB AND NOT FMILIB_BUILD_STATIC_LIB) diff --git a/Config.cmake/runtime_test.cmake b/Config.cmake/runtime_test.cmake index 6212483b..d61137fb 100644 --- a/Config.cmake/runtime_test.cmake +++ b/Config.cmake/runtime_test.cmake @@ -13,16 +13,25 @@ set(RTTESTDIR ${FMILIBRARYHOME}/Test) +# Test: jm_vector add_executable (jm_vector_test ${RTTESTDIR}/jm_vector_test.c) target_link_libraries (jm_vector_test ${JMUTIL_LIBRARIES}) +# Test: jm locale +add_executable (jm_locale_test ${RTTESTDIR}/jm_locale_test.c) +target_link_libraries (jm_locale_test ${JMUTIL_LIBRARIES}) +if(FMILIB_TEST_LOCALE) + target_compile_definitions(jm_locale_test PRIVATE -DFMILIB_TEST_LOCALE) +endif() + #Create function that zipz the dummy FMUs add_executable (compress_test_fmu_zip ${RTTESTDIR}/compress_test_fmu_zip.c) target_link_libraries (compress_test_fmu_zip ${FMIZIP_LIBRARIES}) set_target_properties( - jm_vector_test compress_test_fmu_zip + jm_vector_test jm_locale_test compress_test_fmu_zip PROPERTIES FOLDER "Test") + #Path to the executable get_property(COMPRESS_EXECUTABLE TARGET compress_test_fmu_zip PROPERTY LOCATION) @@ -153,6 +162,8 @@ if(FMILIB_BUILD_BEFORE_TESTS) COMMAND "${CMAKE_COMMAND}" --build ${FMILIBRARYBUILD} --config $) endif() +add_test(ctest_jm_locale_test jm_locale_test) + ADD_TEST(ctest_fmi_zip_unzip_test fmi_zip_unzip_test) ADD_TEST(ctest_fmi_zip_zip_test fmi_zip_zip_test) @@ -177,4 +188,4 @@ if(FMILIB_BUILD_BEFORE_TESTS) ctest_fmi_zip_zip_test PROPERTIES DEPENDS ctest_build_all) endif() -SET_TESTS_PROPERTIES ( ctest_fmi_import_test_no_xml PROPERTIES DEPENDS ctest_fmi_zip_unzip_test) \ No newline at end of file +SET_TESTS_PROPERTIES ( ctest_fmi_import_test_no_xml PROPERTIES DEPENDS ctest_fmi_zip_unzip_test) diff --git a/Config.cmake/test_fmi2.cmake b/Config.cmake/test_fmi2.cmake index 768f292c..fb20cd44 100644 --- a/Config.cmake/test_fmi2.cmake +++ b/Config.cmake/test_fmi2.cmake @@ -77,6 +77,14 @@ to_native_c_path("${TEST_OUTPUT_FOLDER}/${FMU2_DUMMY_CS_MODEL_IDENTIFIER}_mf.fmu add_executable (fmi2_xml_parsing_test ${RTTESTDIR}/FMI2/fmi2_xml_parsing_test.c) target_link_libraries (fmi2_xml_parsing_test ${FMILIBFORTEST}) +if(FMILIB_TEST_LOCALE) + set(FMI2_XML_PARSING_TEST_DEFINITIONS -DFMILIB_TEST_LOCALE) + if(UNIX) + list(APPEND FMI2_XML_PARSING_TEST_DEFINITIONS -D_GNU_SOURCE) + endif() + target_compile_definitions(fmi2_xml_parsing_test PRIVATE ${FMI2_XML_PARSING_TEST_DEFINITIONS}) +endif() + add_executable (fmi2_import_xml_test ${RTTESTDIR}/FMI2/fmi2_import_xml_test.cc) target_link_libraries (fmi2_import_xml_test ${FMILIBFORTEST}) add_executable (fmi2_import_me_test ${RTTESTDIR}/FMI2/fmi2_import_me_test.c) diff --git a/Makefile b/Makefile index 5b13bbc0..8fba731d 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ generate: -DFMILIB_GENERATE_DOXYGEN_DOC=$(GENERATE_DOXYGEN_DOC) \ -DFMILIB_BUILD_WITH_STATIC_RTLIB=$(BUILD_WITH_STATIC_RTLIB) \ -DFMILIB_BUILD_TESTS=$(BUILD_TESTS) \ + -DFMILIB_TEST_LOCALE=$(TEST_LOCALE) \ -G $(GENERATOR) \ ../$(SRC_DIR) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f278e044..7039d54a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,12 @@ The release notes are typically a highlighting subset of all changes made. For f Note that version 2.1 is the first version with release notes. Please see the commit history for older versions. +## 2.2.1 + +### Minor +- Bug fix: Correctly parse doubles when locale is not using decimal point +- Check `variability != continuous` for non-Real variables + ## 2.2 ### Bug fixes diff --git a/Test/FMI2/fmi2_xml_parsing_test.c b/Test/FMI2/fmi2_xml_parsing_test.c index aded3d12..0cd58334 100644 --- a/Test/FMI2/fmi2_xml_parsing_test.c +++ b/Test/FMI2/fmi2_xml_parsing_test.c @@ -2,6 +2,7 @@ #include #include #include "config_test.h" +#include static const int SHOULD_NOT_LOG_EXPECTED_MSG = 0; static const int SHOULD_LOG_EXPECTED_MSG = 1; @@ -10,6 +11,17 @@ static int did_not_log_expected_msg; static char *expected_message = "Invalid structured ScalarVariable name"; static char *name_check_test_directory; +static void fail(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + printf("Test failure: "); + vprintf(fmt, args); + printf("\n"); + va_end(args); + + exit(CTEST_RETURN_FAIL); +} + char *concat(char *s1, char *s2) { size_t len1 = strlen(s1); @@ -35,20 +47,19 @@ void importlogger(jm_callbacks* c, jm_string module, void test_parser(char *xml_dir, int should_not_log_expected_msg, int configuration) { - jm_callbacks *callbacks; + jm_callbacks cb; fmi_import_context_t *context; fmi2_import_t *fmu; char *full_path; - callbacks = (jm_callbacks *) malloc(sizeof(jm_callbacks)); - callbacks->malloc = malloc; - callbacks->calloc = calloc; - callbacks->realloc = realloc; - callbacks->free = free; - callbacks->logger = importlogger; - callbacks->log_level = jm_log_level_all; - callbacks->context = 0; - context = fmi_import_allocate_context(callbacks); + cb.malloc = malloc; + cb.calloc = calloc; + cb.realloc = realloc; + cb.free = free; + cb.logger = importlogger; + cb.log_level = jm_log_level_all; + cb.context = NULL; + context = fmi_import_allocate_context(&cb); if (configuration != 0) { fmi_import_set_configuration(context, configuration); } @@ -62,8 +73,7 @@ void test_parser(char *xml_dir, int should_not_log_expected_msg, int configurati if (fmu == NULL) { exit(CTEST_RETURN_FAIL); } - if (!should_not_log_expected_msg && did_not_log_expected_msg || - did_not_log_expected_msg && !should_not_log_expected_msg) { + if (should_not_log_expected_msg != did_not_log_expected_msg) { /* XOR */ exit(CTEST_RETURN_FAIL); } } @@ -160,16 +170,146 @@ void test_variable_naming_conventions(void) pass_name_check("naming_conventions_xmls/flat/q-char-nonescaped"); } +static fmi2_import_t* parse_xml(jm_callbacks* cb, const char* xmldir) { + fmi_import_context_t* context; + fmi2_import_t* xml; + + context = fmi_import_allocate_context(cb); + + xml = fmi2_import_parse_xml(context, xmldir, NULL); + fmi_import_free_context(context); + + return xml; +} + +/** + * Tests that parsing works as expected, and that the previous locale and + * thread settings are reset. + * + * Test is by default disabled, because it requires the target machine to have + * specific language packs. + */ +static void test_locale_lc_numeric() { + + jm_callbacks* cb = jm_get_default_callbacks(); + char* loc_old = NULL; + char* tmp = NULL; + + /* Any locale that uses decimal coma instead of decimal point. */ +#ifdef WIN32 + char* locale_bad = "Swedish_Sweden.1252"; +#else + char* locale_bad = "sv_SE.utf8"; +#endif + + /* Set/get thread-specific settings (and later check that they are + * restored). */ +#ifdef WIN32 + int thread_setting = _DISABLE_PER_THREAD_LOCALE; + _configthreadlocale(thread_setting); +#else + /* Do nothing for Linux since I don't think it's possible to check equality + * of locale_t. */ +#endif + + /* NOT MT-SAFE: But it's the only way to test it for Linux. There are + * currently no other tests that modify the locale globally, so should be + * OK. + * Worst case we can run with the '--force-new-ctest-process' ctest flag. + */ + tmp = setlocale(LC_NUMERIC, locale_bad); + if (!tmp) { + /* If this errors, it's possible that your machine doesn't have + * the locale installed. + * + * Windows: It seemed like I had at least Danish, French, Swedish + * installed by default. + * + * Linux (Ubuntu 18): I had to install a language pack to get this. + */ + fail("failed to set locale"); + } + + /* + * Value of 'tmp' returned from 'setlocale' may be changed by further calls + * to setlocale, and it's also possible that the returned value is not + * "string equal" to the argument (i.e. alias values for the same locale). + * To be able to later compare the restored value, we therefore must copy + * 'tmp'. + */ + loc_old = (char*)malloc(strlen(tmp) + 1); + if (!loc_old) { + fail("failed to alloc memory"); + } + strcpy(loc_old, tmp); + tmp = NULL; + + { + /* Perform parsing and verify that the bad global locale did not affect + * the result. */ + + int failed = 0; + char* xmldir = concat(name_check_test_directory, "env/locale"); + fmi2_import_t* xml = parse_xml(cb, xmldir); + free(xmldir); + + if (xml == NULL) { + fail("failed to parse FMU"); + } + + if (fmi2_import_get_default_experiment_start(xml) != 2.3 || + fmi2_import_get_default_experiment_stop(xml) != 3.55 || + fmi2_import_get_default_experiment_tolerance(xml) != 1e-6 || + fmi2_import_get_default_experiment_step(xml) != 2e-3) + { + /* If the decimal delimiter is comma, sscanf will only parse + * until the dot. */ + printf("Test failure: incorrect default experiment value\n"); + failed = 1; + } + + fmi2_import_free(xml); + + if (failed) { + fail("... see above printed messages"); + } + } + + /* Cleanup and verify that locale is properly restored. + * + * Getting locale should be MT-safe if all setting of locale is done in + * per_thread context. + */ + tmp = setlocale(LC_NUMERIC, loc_old); + free(loc_old); + if (!tmp) { + fail("failed to restore locale"); + } else if (strcmp(tmp, locale_bad)) { + fail("unexpected locale"); + } + +#ifdef WIN32 + if (_configthreadlocale(0) != thread_setting) { + /* This was set at the beginning of the test, and should now have been restored. */ + fail("unexpected Windows thread setting"); + } +#endif +} + int main(int argc, char *argv[]) { if (argc == 2) { name_check_test_directory = argv[1]; } else { - printf("Usage: %s \n", argv[0]); + printf("Usage: %s \n", argv[0]); exit(CTEST_RETURN_FAIL); } test_variable_naming_conventions(); + #ifdef FMILIB_TEST_LOCALE + test_locale_lc_numeric(); + #endif + return 0; } diff --git a/Test/FMI2/parser_test_xmls/env/locale/modelDescription.xml b/Test/FMI2/parser_test_xmls/env/locale/modelDescription.xml new file mode 100644 index 00000000..15f8a949 --- /dev/null +++ b/Test/FMI2/parser_test_xmls/env/locale/modelDescription.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/Test/jm_locale_test.c b/Test/jm_locale_test.c new file mode 100644 index 00000000..0380c3dc --- /dev/null +++ b/Test/jm_locale_test.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include + +#include "config_test.h" +#include + +static void fail(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + printf("Test failure: "); + vprintf(fmt, args); + printf("\n"); + va_end(args); + + exit(CTEST_RETURN_FAIL); +} + +/* Very coarse comparison, enough for this test though. */ +static int dblAlmostEq(double lhs, double rhs) { + return fabs(lhs - rhs) < 0.0001; +} + +#include +static void sscanf_double(const char* str, double expected) { + double val = 0; + printf("Debug: setlocale: %s\n", setlocale(LC_NUMERIC, NULL)); + if (sscanf(str, "%lf", &val) != 1) { + fail("call failed: sscanf"); + } + + if (!dblAlmostEq(expected, val)) { + fail("parsed double did not equal expected, str: %s, exp: %f, act: %f", str, expected, val); + } +} + +static void test_parse_with_locale() { + jm_callbacks* cb = jm_get_default_callbacks(); + jm_locale_t* jmloc1 = NULL; + double dval = 0; + const char* str_comma = "2,5"; + const char* str_point = "2.5"; + +#ifdef WIN32 + char* locale_bad = "French_France.1252"; /* 'sv-SE' does not exist on Jenkins nodes */ +#else + /* Any locale that uses decimal coma instead of decimal point. */ + char* locale_bad = "sv_SE.utf8"; +#endif + + jmloc1 = jm_mtsafe_setlocale_numeric(cb, locale_bad); + if (!jmloc1) { + fail("call failed: jm_mtsafe_setlocale_numeric"); + } + + printf("Debug: parsing with str_comma expected\n"); + /* Check that decimal comma works */ + sscanf_double(str_comma, 2.5); + sscanf_double(str_point, 2.0); + + /* Check that decimal point works (nested setlocale_numeric call) */ + { + jm_locale_t* jmloc2 = jm_mtsafe_setlocale_numeric(cb, "C"); + if (!jmloc2) { + fail("call failed: jm_mtsafe_setlocale_numeric"); + } + + printf("Debug: parsing with str_point expected\n"); + /* Check that decimal point works */ + sscanf_double(str_comma, 2.0); + sscanf_double(str_point, 2.5); + + if (jm_mtsafe_resetlocale_numeric(cb, jmloc2)) { + fail("call failed: jm_mtsafe_setlocale_numeric (nested)"); + } + jmloc2 = NULL; + } + + printf("Debug: parsing with str_comman expected (after restore)\n"); + /* Check that decimal comma works, after restoring */ + sscanf_double(str_comma, 2.5); + sscanf_double(str_point, 2.0); + + if (jm_mtsafe_resetlocale_numeric(cb, jmloc1)) { + fail("call failed: jm_mtsafe_setlocale_numeric (nested)"); + } + jmloc1 = NULL; +} + +int main() { + + #ifdef FMILIB_TEST_LOCALE + test_parse_with_locale(); + #endif + + return CTEST_RETURN_SUCCESS; +} diff --git a/build/config/documentation b/build/config/documentation index a37057de..f6fd8a20 100644 --- a/build/config/documentation +++ b/build/config/documentation @@ -10,6 +10,7 @@ GENERATOR="Unix Makefiles" GENERATE_DOXYGEN_DOC=1 BUILD_WITH_STATIC_RTLIB=0 BUILD_TESTS=0 +TEST_LOCALE=0 # Docker DOCKER_IMAGE_BASE=fmil_base:latest diff --git a/build/config/linux64 b/build/config/linux64 index 05ce6cfb..1d5bd97b 100644 --- a/build/config/linux64 +++ b/build/config/linux64 @@ -10,6 +10,7 @@ GENERATOR="Unix Makefiles" GENERATE_DOXYGEN_DOC=0 BUILD_WITH_STATIC_RTLIB=0 BUILD_TESTS=1 +TEST_LOCALE=1 # Docker DOCKER_IMAGE_BASE=fmil_base:latest diff --git a/build/config/win64 b/build/config/win64 index 3aaf9e23..a76c6bb1 100644 --- a/build/config/win64 +++ b/build/config/win64 @@ -10,6 +10,7 @@ GENERATOR="Visual Studio 10" GENERATE_DOXYGEN_DOC=0 BUILD_WITH_STATIC_RTLIB=0 BUILD_TESTS=1 +TEST_LOCALE=1 # Docker DOCKER_IMAGE_BASE= # N/A diff --git a/build/config/win64_static_runtime b/build/config/win64_static_runtime index 63de6c21..a84d2888 100644 --- a/build/config/win64_static_runtime +++ b/build/config/win64_static_runtime @@ -10,6 +10,7 @@ GENERATOR="Visual Studio 10" GENERATE_DOXYGEN_DOC=0 BUILD_WITH_STATIC_RTLIB=1 BUILD_TESTS=1 +TEST_LOCALE=1 # Docker DOCKER_IMAGE_BASE= # N/A diff --git a/build/docker/Dockerfile_base b/build/docker/Dockerfile_base index 82eb6d4c..e3048c58 100644 --- a/build/docker/Dockerfile_base +++ b/build/docker/Dockerfile_base @@ -1,7 +1,9 @@ FROM ubuntu:16.04 +# 'language-pack-sv' is needed for locale tests RUN apt-get update && apt-get install -y \ build-essential \ cmake \ - doxygen - + doxygen \ + vim \ + language-pack-sv diff --git a/src/Util/include/JM/jm_portability.h b/src/Util/include/JM/jm_portability.h index 82e472a4..b814dcd5 100644 --- a/src/Util/include/JM/jm_portability.h +++ b/src/Util/include/JM/jm_portability.h @@ -139,6 +139,37 @@ int jm_snprintf(char * str, size_t size, const char * fmt, ...); #elif defined(WIN32) #define JM_VA_COPY(dest,src) dest=src #endif + +/** + \brief Sets the LC_NUMERIC locale for this thread only. A follow up call to + 'jm_mtsafe_resetlocale_numeric' is needed to free the returned 'jm_locale_t' + object. + + \param jmloc: + \param value: + Value to set for LC_NUMERIC. + \return: + Pointer to object for reseting thread settings (locale, and + _configthreadlocale on Windows). NULL on failure. + */ +jm_locale_t* jm_mtsafe_setlocale_numeric(jm_callbacks* cb, const char* value); + +/** + \brief Restores thread settings and locale. + + This function is only allowed to be called when the current locale is set by + 'jm_mtsafe_setlocale_numeric', and the 'jmloc' argument must be what is + returned from that call. On Linux, the locale must in no way be modified since that + call. + + \param jmloc: + Return value from previous call to 'jm_mtsafe_setlocale_numeric'. Current + locale must be set with that function. This call will free 'jmloc', so it's + not allowed to be used after. + \return: + 0 on success. + */ +int jm_mtsafe_resetlocale_numeric(jm_callbacks* cb, jm_locale_t* jmloc); /*@}*/ #endif /* End of header file JM_PORTABILITY_H_ */ diff --git a/src/Util/include/JM/jm_types.h b/src/Util/include/JM/jm_types.h index 2dc7293c..4f01afa0 100644 --- a/src/Util/include/JM/jm_types.h +++ b/src/Util/include/JM/jm_types.h @@ -63,6 +63,9 @@ typedef enum { FMILIB_EXPORT const char* jm_log_level_to_string(jm_log_level_enu_t level); +/** \brief Struct for restoring LC_NUMERIC locale. */ +typedef struct jm_locale_t jm_locale_t; + /** @} */ #ifdef __cplusplus } diff --git a/src/Util/src/JM/jm_portability.c b/src/Util/src/JM/jm_portability.c index 8268b029..70018829 100644 --- a/src/Util/src/JM/jm_portability.c +++ b/src/Util/src/JM/jm_portability.c @@ -355,3 +355,110 @@ int jm_snprintf(char * str, size_t size, const char * fmt, ...) { va_end (args); return ret; } + +struct jm_locale_t { +#ifdef WIN32 + char* locale_old; + int per_thread_locale_type_old; +#else + locale_t locale_old; +#endif +}; + +jm_locale_t* jm_mtsafe_setlocale_numeric(jm_callbacks* cb, const char* value) { + + jm_locale_t* jmloc = (jm_locale_t*)malloc(sizeof(jm_locale_t)); + if (!jmloc) { + jm_log_error(cb, module, "failed to allocate memory"); + return NULL; + } + +#ifdef WIN32 + { + char* tmp; + + /* Save current thread settings. */ + jmloc->per_thread_locale_type_old = _configthreadlocale(0); + + /* Create a copy of locale, since any further calls to setlocale (e.g. + * from 3rd party code) will override the returned pointer. */ + tmp = setlocale(LC_NUMERIC, NULL); + if (!tmp) { + jm_log_error(cb, module, "Failed to get current locale with 'setlocale'"); + free(jmloc); + return NULL; + } + jmloc->locale_old = (char*)cb->malloc(strlen(tmp) + 1); /* + 1 for \0 */ + strcpy(jmloc->locale_old, tmp); + + /* Set LC_NUMERIC for this thread. */ + _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + if (setlocale(LC_NUMERIC, value) == NULL) { + jm_log_error(cb, module, "Failed to call 'setlocale' for LC_NUMERIC with value: '%s'", value); + free(jmloc->locale_old); + free(jmloc); + return NULL; + } + + return jmloc; + } +#else /* _GNU_SOURCE */ + { + locale_t nloc = NULL; + + /* Retrieve old locale */ + jmloc->locale_old = uselocale((locale_t)0); + if (jmloc->locale_old == (locale_t)0) { + jm_log_error(cb, module, "'uselocale' failed to get current locale"); + goto err1; + } + + /* Create new locale. */ + nloc = newlocale(LC_NUMERIC_MASK, value, (locale_t)0); + if (nloc == (locale_t)0) { + jm_log_error(cb, module, "call failed: 'newlocale'"); + goto err1; + } + + /* Set new locale */ + uselocale(nloc); + + return jmloc; + + /* Error handling */ +err1: + free(jmloc); + return NULL; + } +#endif +} + +int jm_mtsafe_resetlocale_numeric(jm_callbacks* cb, jm_locale_t* jmloc) { + if (jmloc == NULL) { + return 1; /* impl. error */ + } + +#ifdef WIN32 + setlocale(LC_NUMERIC, jmloc->locale_old); + cb->free(jmloc->locale_old); + jmloc->locale_old = NULL; + + _configthreadlocale(jmloc->per_thread_locale_type_old); +#else + { + /* Get current locale, which is expected to have been set with a previous + * call to 'jm_mtsafe_setlocale_numeric'. */ + locale_t loc = uselocale((locale_t)0); + if (loc == (locale_t)0) { + jm_log_error(cb, module, "'uselocale' failed to get current locale."); + return 1; + } + uselocale(jmloc->locale_old); + + freelocale(loc); + } +#endif + + free(jmloc); + return 0; +} diff --git a/src/XML/src/FMI2/fmi2_xml_parser.c b/src/XML/src/FMI2/fmi2_xml_parser.c index 3719ae15..f61397cc 100644 --- a/src/XML/src/FMI2/fmi2_xml_parser.c +++ b/src/XML/src/FMI2/fmi2_xml_parser.c @@ -23,6 +23,7 @@ #include "fmi2_xml_model_description_impl.h" #include "fmi2_xml_parser.h" +#include "JM/jm_portability.h" static const char * module = "FMI2XML"; @@ -118,6 +119,10 @@ void fmi2_xml_parse_free_context(fmi2_xml_parser_context_t *context) { jm_stack_free_data(int)(& context->elmStack ); jm_vector_free_data(char)( &context->elmData ); + if (jm_mtsafe_resetlocale_numeric(context->callbacks, context->jm_locale)) { + jm_log_error(context->callbacks, module, "Failed to reset locale."); + } + context->callbacks->free(context); } @@ -683,6 +688,14 @@ int fmi2_xml_parse_model_description(fmi2_xml_model_description_t* md, context->anyParent = 0; context->anyHandle = xml_callbacks; + /* Set locale such that parsing does not depend on the environment. + * For example, LC_NUMERIC affects what sscanf identifies as the floating + * point delimiter. */ + context->jm_locale = jm_mtsafe_setlocale_numeric(context->callbacks, "C"); + if (!context->jm_locale) { + jm_log_error(context->callbacks, module, "Failed to set locale. Parsing might be incorrect."); + } + memsuite.malloc_fcn = context->callbacks->malloc; memsuite.realloc_fcn = context->callbacks->realloc; memsuite.free_fcn = context->callbacks->free; diff --git a/src/XML/src/FMI2/fmi2_xml_parser.h b/src/XML/src/FMI2/fmi2_xml_parser.h index 5a311ecd..86d03dd9 100644 --- a/src/XML/src/FMI2/fmi2_xml_parser.h +++ b/src/XML/src/FMI2/fmi2_xml_parser.h @@ -216,6 +216,9 @@ struct fmi2_xml_parser_context_t { char* anyToolName; void* anyParent; fmi2_xml_callbacks_t* anyHandle; + + /* Data for restoring locale after parsing */ + jm_locale_t* jm_locale; }; jm_vector(char) * fmi2_xml_reserve_parse_buffer(fmi2_xml_parser_context_t *context, size_t index, size_t size);