Files
opennurbs/opennurbs_locale.cpp
2024-08-22 01:43:04 -07:00

1641 lines
47 KiB
C++

//
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#include "opennurbs.h"
#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif
static ON_CRT_locale_t ON_CRT_C_locale()
{
static ON_CRT_locale_t ON_C_locale = 0;
if (0 == ON_C_locale)
{
#if defined(ON_RUNTIME_WIN)
ON_C_locale = _create_locale(LC_ALL, "C");
#elif defined(ON_RUNTIME_APPLE)
ON_C_locale = _c_locale;
#elif defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) || defined(ON_RUNTIME_WASM)
ON_C_locale = 0;
#else
ON_C_locale = _create_locale(category, locale);
#endif
}
return ON_C_locale;
}
static ON_CRT_locale_t ON_CRT_create_locale_ALL( const char * locale )
{
if ( nullptr == locale || 0 == locale[0] )
return ON_CRT_C_locale();
if (
('C' == locale[0] || 'c' == locale[0])
&& 0 == locale[1]
)
return ON_CRT_C_locale();
if (
('P' == locale[0] || 'p' == locale[0])
&& ('O' == locale[1] || 'o' == locale[1])
&& ('S' == locale[2] || 's' == locale[2])
&& ('I' == locale[3] || 'i' == locale[3])
&& ('X' == locale[4] || 'x' == locale[4])
&& 0 == locale[5]
)
return ON_CRT_C_locale();
#if defined(ON_RUNTIME_WIN)
return _create_locale(LC_ALL, locale);
#elif defined(ON_RUNTIME_APPLE)
// Apple locale names are <language>_<REGION> (underbar)
char language_subtag[32] = { 0 };
const size_t language_subtag_capacity = sizeof(language_subtag)/sizeof(language_subtag[0]);
char region_subtag[32] = { 0 };
const size_t region_subtag_capacity = sizeof(region_subtag)/sizeof(region_subtag[0]);
if ( false == ON_Locale::ParseName(
locale,-1,
language_subtag, language_subtag_capacity,
nullptr, 0,
nullptr, 0,
region_subtag, region_subtag_capacity,
nullptr, 0
))
{
ON_ERROR("locale_name is not valid");
return ON_CRT_C_locale();
}
char apple_name[sizeof(language_subtag) / sizeof(language_subtag[0]) + sizeof(region_subtag) / sizeof(region_subtag[0]) + 2] = { 0 };
for (size_t i = 0; i < language_subtag_capacity; i++)
{
if (0 == (apple_name[i] = language_subtag[i]))
{
if (0 != region_subtag[0])
{
apple_name[i++] = '_'; // Apple needs an underbar here
for (size_t j = 0; j < region_subtag_capacity; j++, i++)
{
if (0 == (apple_name[i] = region_subtag[j]))
break;
}
}
break;
}
}
return newlocale(LC_ALL_MASK, apple_name, ON_CRT_C_locale() );
#elif defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) || defined(ON_RUNTIME_WASM)
return 0;
#else
return _create_locale(category, locale);
#endif
}
ON_Locale::ON_Locale() ON_NOEXCEPT
{
memset(this, 0, sizeof(*this));
m_numeric_locale = ON_CRT_C_locale();
m_string_coll_map_locale = ON_CRT_C_locale();
}
ON__UINT32 ON_Locale::WindowsLCID() const
{
return m_windows_lcid;
}
static char* LocaleStringBuilder(
char c,
const char* source,
size_t source_capacity,
char* dest,
char* dest_end
)
{
if ( nullptr == dest || nullptr == dest_end || nullptr == source || source_capacity <= 0 )
return nullptr;
if ( 0 != source[source_capacity - 1])
return nullptr;
if (dest < dest_end)
{
if (0 == source[0])
{
*dest = 0;
return dest;
}
if (c > 0 )
*dest++ = c;
while (dest < dest_end)
{
if (0 == (*dest = *source++))
return dest;
dest++;
}
}
return nullptr;
}
#define ON_LOCALE_SUBTAG(c,t) c,t,sizeof(t)/sizeof(t[0])
static char* LocaleStringBuilderDestEnd(
char* buffer,
size_t buffer_capacity
)
{
if (buffer_capacity > 0 && nullptr != buffer)
{
memset(buffer, 0, buffer_capacity*sizeof(buffer[0]));
return buffer+buffer_capacity;
}
return nullptr;
}
static wchar_t* LocalWideStringBuider(
const char* cbuffer,
wchar_t* buffer,
size_t buffer_capacity
)
{
if (buffer_capacity > 0 && nullptr != buffer)
{
memset(buffer, 0, buffer_capacity*sizeof(buffer[0]));
if (nullptr != cbuffer)
{
wchar_t* dest = buffer;
wchar_t* dest_end = dest + buffer_capacity;
while (dest < dest_end)
{
if (0 == (*dest++ = (wchar_t)(*cbuffer++)))
return buffer;
}
memset(buffer, 0, buffer_capacity*sizeof(buffer[0]));
}
}
return nullptr;
}
const char* ON_Locale::GetBCP47LanguageTag(
char* buffer,
size_t buffer_capacity
) const
{
if ( nullptr == buffer || buffer_capacity <= 0 )
return nullptr;
char* dest = buffer;
char* dest_end = LocaleStringBuilderDestEnd(buffer,buffer_capacity);
const char* source = m_bcp47_language_tag;
while (dest < dest_end)
{
if (0 == (*dest++ = *source++))
return buffer;
}
memset(buffer, 0, buffer_capacity*sizeof(buffer[0]));
return nullptr;
}
const wchar_t* ON_Locale::GetBCP47LanguageTag(
wchar_t* buffer,
size_t buffer_capacity
) const
{
char cbuffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY];
return (LocalWideStringBuider(
GetBCP47LanguageTag(cbuffer,sizeof(cbuffer)/sizeof(cbuffer[0])),
buffer,
buffer_capacity
));
}
const char* ON_Locale::BCP47LanguageTag() const
{
return m_bcp47_language_tag;
}
const char* ON_Locale::GetWindowsLocaleName(
char* buffer,
size_t buffer_capacity
) const
{
char* buffer_end = LocaleStringBuilderDestEnd(buffer,buffer_capacity);
char* s = buffer;
s = LocaleStringBuilder(ON_LOCALE_SUBTAG(0,m_language_subtag),s,buffer_end);
s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',m_script_subtag),s,buffer_end);
s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',m_region_subtag),s,buffer_end);
s = LocaleStringBuilder(ON_LOCALE_SUBTAG('_',m_windows_sortorder),s,buffer_end);
return ( nullptr == s ) ? nullptr : buffer;
}
const wchar_t* ON_Locale::GetWindowsLocaleName(
wchar_t* buffer,
size_t buffer_capacity
) const
{
char cbuffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY];
return (LocalWideStringBuider(
GetWindowsLocaleName(cbuffer,sizeof(cbuffer)/sizeof(cbuffer[0])),
buffer,
buffer_capacity
));
}
const char* ON_Locale::GetAppleLocaleName(
char* buffer,
size_t buffer_capacity
) const
{
char* buffer_end = LocaleStringBuilderDestEnd(buffer,buffer_capacity);
char* s = buffer;
s = LocaleStringBuilder(ON_LOCALE_SUBTAG(0,m_language_subtag),s,buffer_end);
// Apple local names omit script
// Apple local names use an underbar before <REGION>
// s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',m_script_subtag),s,buffer_end);
s = LocaleStringBuilder(ON_LOCALE_SUBTAG('_',m_region_subtag),s,buffer_end);
return ( nullptr == s ) ? nullptr : buffer;
}
const wchar_t* ON_Locale::GetAppleLocaleName(
wchar_t* buffer,
size_t buffer_capacity
) const
{
char cbuffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY];
return (LocalWideStringBuider(
GetAppleLocaleName(cbuffer,sizeof(cbuffer)/sizeof(cbuffer[0])),
buffer,
buffer_capacity
));
}
const char* ON_Locale::GetAppleLanguageName(
char* buffer,
size_t buffer_capacity
) const
{
char* buffer_end = LocaleStringBuilderDestEnd(buffer,buffer_capacity);
char* s = buffer;
s = LocaleStringBuilder(ON_LOCALE_SUBTAG(0,m_language_subtag),s,buffer_end);
if ( ON_String::EqualOrdinal("zh", 3, buffer, 3, true) || 0 != m_region_subtag[0] )
{
// Apple "language" names use "zh-Hans" for "zh-CN" and "zh-Hant" for "zh-TW"
if ( 0 == m_script_subtag[0] )
{
const char* script_subtag = nullptr;
if ( ON_String::EqualOrdinal("CN", -1, m_region_subtag, -1, true))
script_subtag = "Hans";
else if (ON_String::EqualOrdinal("TW", -1, m_region_subtag, -1, true))
script_subtag = "Hant";
if (0 != script_subtag)
{
s = LocaleStringBuilder('-', script_subtag, 5, s, buffer_end);
return (nullptr == s) ? nullptr : buffer;
}
}
s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-', m_script_subtag), s, buffer_end);
}
s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-', m_region_subtag), s, buffer_end);
return ( nullptr == s ) ? nullptr : buffer;
}
const wchar_t* ON_Locale::GetAppleLanguageName(
wchar_t* buffer,
size_t buffer_capacity
) const
{
char cbuffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY];
return (LocalWideStringBuider(
GetAppleLanguageName(cbuffer,sizeof(cbuffer)/sizeof(cbuffer[0])),
buffer,
buffer_capacity
));
}
const char* ON_Locale::LanguageCode() const
{
return m_language_subtag;
}
const char* ON_Locale::ScriptCode() const
{
return m_script_subtag;
}
const char* ON_Locale::RegionCode() const
{
return m_region_subtag;
}
const char* ON_Locale::WindowsSortOrder() const
{
return m_windows_sortorder;
}
static bool ZeroWideBuffer(
wchar_t*& buffer,
size_t& buffer_capacity,
size_t sizeof_buffer_element
)
{
bool rc
= (nullptr != buffer && buffer_capacity > 0 && sizeof_buffer_element > 0 )
|| 0 == buffer_capacity;
if (nullptr == buffer || buffer_capacity <= 0 || sizeof_buffer_element <= 0 )
{
buffer = nullptr;
buffer_capacity = 0;
}
else
{
memset(buffer,0,buffer_capacity*sizeof_buffer_element);
}
return rc;
}
static bool ZeroCharBuffer(
char*& buffer,
size_t& buffer_capacity,
size_t sizeof_buffer_element
)
{
bool rc
= (nullptr != buffer && buffer_capacity > 0 && sizeof_buffer_element > 0 )
|| 0 == buffer_capacity;
if (nullptr == buffer || buffer_capacity <= 0 || sizeof_buffer_element <= 0 )
{
buffer = nullptr;
buffer_capacity = 0;
}
else
{
memset(buffer,0,buffer_capacity*sizeof_buffer_element);
}
return rc;
}
static bool IsAlpha(char c)
{
return (c >= 'A' && c <= 'Z' ) || (c >= 'a' && c <= 'z' );
}
static bool IsDigit(char c)
{
return (c >= '0' && c <= '1' );
}
static bool IsAlphaOrDigit(char c)
{
return IsAlpha(c) || IsDigit(c);
}
static bool IsHyphen(char c)
{
return ('-' == c);
}
static bool IsUnderbar(char c)
{
return ('_' == c);
}
static char ToUpper(char c)
{
return (c >= 'a' && c <= 'z') ? (c - ('a' - 'A')) : c;
}
static char ToLower(char c)
{
return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c;
}
static bool IsHyphenOrUnderbar(char c)
{
return IsHyphen(c) || IsUnderbar(c);
}
#if defined(ON_RUNTIME_WIN)
static int ASCIIWideToChar(
const wchar_t* wASCII,
size_t wASCII_length,
char* sASCII,
size_t sASCII_capacity
)
{
for (;;)
{
if (nullptr == wASCII || wASCII_length <= 0)
break;
if (nullptr == sASCII || sASCII_capacity <= 0)
break;
if (sASCII_capacity < wASCII_length)
break;
size_t i;
for (i = 0; i < wASCII_length; i++)
{
const wchar_t c = wASCII[i];
if (c < 0 || c > 127)
break;
if (0 == c)
{
sASCII[i++] = 0;
wASCII_length = i;
break;
}
sASCII[i] = (char)c;
}
if ( i < wASCII_length )
break;
for(/*empty init*/;i < sASCII_capacity; i++)
sASCII[i] = 0;
if ( 0 != sASCII[sASCII_capacity-1])
break;
return true;
}
if ( nullptr != sASCII && sASCII_capacity > 0)
memset(sASCII,0,sASCII_capacity*sizeof(sASCII[0]));
return false;
}
#endif
static bool ASCIICharToWide(
const char* sASCII,
size_t sASCII_length,
wchar_t* wASCII,
size_t wASCII_capacity
)
{
for (;;)
{
if (nullptr == sASCII || sASCII_length <= 0)
break;
if (nullptr == wASCII || wASCII_capacity <= 0)
break;
if (wASCII_capacity < sASCII_length)
break;
size_t i;
for (i = 0; i < sASCII_length; i++)
{
const char c = sASCII[i];
if (c < 0 || c > 127)
break;
if (0 == c)
{
wASCII[i++] = 0;
sASCII_length = i;
break;
}
wASCII[i] = (wchar_t)c;
}
if ( i < sASCII_length )
break;
for(/*empty init*/;i < wASCII_capacity; i++)
wASCII[i] = 0;
if ( 0 != wASCII[wASCII_capacity-1])
break;
return true;
}
if ( nullptr != wASCII && wASCII_capacity > 0)
memset(wASCII,0,wASCII_capacity*sizeof(wASCII[0]));
return false;
}
bool ON_Locale::ParseName(
const wchar_t* locale_name,
int locale_name_element_count,
wchar_t* language_code,
size_t language_code_capacity,
wchar_t* extlang_code,
size_t extlang_code_capacity,
wchar_t* script_code,
size_t script_code_capacity,
wchar_t* region_code,
size_t region_code_capacity,
wchar_t* sortorder,
size_t sortorder_capacity
)
{
bool rc = true;
if (false == ZeroWideBuffer(language_code, language_code_capacity, sizeof(language_code[0])))
rc = false;
if (false == ZeroWideBuffer(extlang_code, extlang_code_capacity, sizeof(extlang_code[0])))
rc = false;
if (false == ZeroWideBuffer(script_code, script_code_capacity, sizeof(script_code[0])))
rc = false;
if (false == ZeroWideBuffer(region_code, region_code_capacity, sizeof(region_code[0])))
rc = false;
if (false == ZeroWideBuffer(sortorder, sortorder_capacity, sizeof(sortorder[0])))
rc = false;
if (false == rc)
return false;
if ( locale_name_element_count < 0 )
locale_name_element_count = ON_wString::Length(locale_name);
if ( 0 == locale_name_element_count )
return true;
if ( nullptr == locale_name || 0 == locale_name[0] )
return false;
if ( locale_name_element_count < 2 )
return false;
ON_String buffer;
buffer.ReserveArray(locale_name_element_count);
buffer.SetLength(locale_name_element_count);
char* sname = buffer.Array();
for (int i = 0; i < locale_name_element_count; i++)
{
const wchar_t w = locale_name[i];
if ( w > 127 )
return false; // illegal symbol in language or locale name
if (0 == w)
{
locale_name_element_count = i;
break;
}
sname[i] = (char)w;
}
sname[locale_name_element_count] = 0;
const size_t ON_S_CAPACITY = 64;
char slanguage_code[ON_S_CAPACITY] = { 0 };
char sextlang_code[ON_S_CAPACITY] = { 0 };
char sscript_code[ON_S_CAPACITY] = { 0 };
char sregion_code[ON_S_CAPACITY] = { 0 };
char ssortorder[ON_S_CAPACITY] = { 0 };
rc = ON_Locale::ParseName(
sname,
locale_name_element_count,
slanguage_code, ON_S_CAPACITY,
sextlang_code, ON_S_CAPACITY,
sscript_code, ON_S_CAPACITY,
sregion_code, ON_S_CAPACITY,
ssortorder, ON_S_CAPACITY
);
if (!rc)
return false;
if ( !ASCIICharToWide(slanguage_code,ON_S_CAPACITY,language_code,language_code_capacity) )
rc = false;
if ( !ASCIICharToWide(sscript_code,ON_S_CAPACITY,script_code,script_code_capacity) )
rc = false;
if ( !ASCIICharToWide(sregion_code,ON_S_CAPACITY,region_code,region_code_capacity) )
rc = false;
if ( !ASCIICharToWide(ssortorder,ON_S_CAPACITY,sortorder,sortorder_capacity) )
rc = false;
return rc;
}
bool ON_Locale::ParseName(
const char* locale_name,
int locale_name_element_count,
char* language_code,
size_t language_code_capacity,
char* extlang_code,
size_t extlang_code_capacity,
char* script_code,
size_t script_code_capacity,
char* region_code,
size_t region_code_capacity,
char* sortorder,
size_t sortorder_capacity
)
{
bool rc = true;
if ( false == ZeroCharBuffer(language_code, language_code_capacity, sizeof(language_code[0])) )
rc = false;
if ( false == ZeroCharBuffer(extlang_code, extlang_code_capacity, sizeof(extlang_code[0])) )
rc = false;
if ( false == ZeroCharBuffer(script_code, script_code_capacity, sizeof(script_code[0])) )
rc = false;
if ( false == ZeroCharBuffer(region_code, region_code_capacity, sizeof(region_code[0])) )
rc = false;
if ( false == ZeroCharBuffer(sortorder, sortorder_capacity, sizeof(sortorder[0])) )
rc = false;
if (!rc)
return false;
if ( nullptr == locale_name || 0 == locale_name[0] || 0 == locale_name_element_count )
return true;
if ( locale_name_element_count < 0 )
locale_name_element_count = ON_String::Length(locale_name);
if ( locale_name_element_count < 2 )
return false;
const char* locale_name_end = locale_name + locale_name_element_count;
const char* s0 = locale_name;
const char* s1 = s0;
while( IsAlpha(*s1) )
s1++;
if ( s1-s0 < 2 )
return false;
for ( size_t i = 0; i < language_code_capacity && s0 < s1 ; i++)
language_code[i] = ToLower(*s0++); // lower case for language code is a convention
if ( 0 == language_code[0] || 0 == language_code[1] )
return false;
bool bScriptTest = true;
bool bExtlangTest = true;
bool bRegionTest = true;
bool bSortOrderTest = true;
for (int pass = 0; pass < 4 && s1 < locale_name_end; pass++)
{
char c0 = *s1;
if (0 == c0)
return true;
if (false == IsHyphenOrUnderbar(c0))
return false;
s1++;
s0 = s1;
while (s1 < locale_name_end && IsAlphaOrDigit(*s1))
s1++;
if ( s1-s0 < 2 )
return false;
if (bExtlangTest)
{
bExtlangTest = false;
if ( IsHyphen(c0)
&& 3 == s1 - s0
&& IsAlpha(s0[0])
&& IsAlpha(s0[1])
&& IsAlpha(s0[2])
)
{
if ( extlang_code_capacity > 0 && extlang_code_capacity < (size_t)(s1-s0) )
return false;
if (extlang_code_capacity > 0)
{
for (size_t i = 0; i < extlang_code_capacity && s0 < s1; i++)
extlang_code[i] = ToLower(*s0++); // lower case for extlang code is a convention
}
continue;
}
}
if (bScriptTest)
{
bScriptTest = false;
if ( IsHyphen(c0)
&& 4 == s1 - s0
&& IsAlpha(s0[0])
&& IsAlpha(s0[1])
&& IsAlpha(s0[2])
&& IsAlpha(s0[3])
)
{
// ISO 15924 script code
if ( script_code_capacity > 0 && script_code_capacity < (size_t)(s1-s0) )
return false;
if (script_code_capacity > 0)
{
// convention is for script codes is CAPITAL, small, small, small case
script_code[0] = ToUpper(*s0++);
for (size_t i = 1; i < script_code_capacity && s0 < s1; i++)
script_code[i] = ToLower(*s0++);
}
continue;
}
}
if (bRegionTest)
{
bRegionTest = false;
// IsUnderbar(c0) is here to handle Apple OS X and iOS "locale id" names like "en_US" which use and underbar before region
if (
( (IsHyphen(c0) || IsUnderbar(c0)) && 2 == s1 - s0 && IsAlpha(s0[0]) && IsAlpha(s0[1]) ) // ISO 3166 country/region identifier (2 alpha)
|| ( IsHyphen(c0) && 3 == s1 - s0 && IsDigit(s0[0]) && IsDigit(s0[1]) && IsDigit(s0[2]) ) // UN M.49 code (3 digits)
)
{
if ( region_code_capacity > 0 && region_code_capacity <= (size_t)(s1-s0) )
return false;
for (size_t i = 0; i < region_code_capacity && s0 < s1; i++)
region_code[i] = ToUpper(*s0++); // uppercase for regions is a convention
continue;
}
}
if (bSortOrderTest)
{
bSortOrderTest = false;
if (IsUnderbar(c0))
{
// Windows sort order
if (sortorder_capacity > 0 && sortorder_capacity <= (size_t)(s1 - s0))
return false;
for (size_t i = 0; i < sortorder_capacity && s0 < s1; i++)
sortorder[i] = *s0++;
continue;
}
}
ON_ERROR("Parser needs to be enhanced or input is not valid");
return false;
}
return true;
}
ON_Locale ON_Locale::FromWindowsLCID(
ON__UINT32 windows_lcid
)
{
if (ON_Locale::OrdinalLCID == windows_lcid
|| 1 == windows_lcid // "Rhino locale"
|| ON_Locale::InvariantCultureLCID == windows_lcid
)
{
// This code is called to initialize ON_Locale::Ordinal and ON_Locale::InvariantCulture.
ON_Locale locale;
locale.m_windows_lcid = (ON__UINT32)windows_lcid;
return locale;
}
#if defined(ON_RUNTIME_WIN)
wchar_t wide_locale_name[LOCALE_NAME_MAX_LENGTH + 1] = { 0 };
const int locale_name_capacity = (int)(sizeof(wide_locale_name) / sizeof(wide_locale_name[0]) - 1);
int local_name_length = ::LCIDToLocaleName(
windows_lcid,
wide_locale_name,
locale_name_capacity,
LOCALE_ALLOW_NEUTRAL_NAMES
);
if (0 == local_name_length)
{
ON_ERROR("Windows ::LCIDToLocaleName() failed.");
return ON_Locale::Ordinal;
}
if (local_name_length >= ON_Locale::BUFFER_MAXIMUM_CAPACITY)
{
ON_ERROR("Windows locale name is too long.");
return ON_Locale::Ordinal;
}
wide_locale_name[locale_name_capacity] = 0;
char locale_name[ON_Locale::BUFFER_MAXIMUM_CAPACITY + 1] = { 0 };
if (false == ASCIIWideToChar(wide_locale_name, local_name_length, locale_name, sizeof(locale_name) / sizeof(locale_name[0])) )
{
ON_ERROR("Windows locale name contains invalid wchar_t element.");
return ON_Locale::Ordinal;
}
return ON_Locale::FromWindowsLCIDAndName(windows_lcid,locale_name);
#else
switch (windows_lcid)
{
case ON_Locale::WindowsLCID::cs_CZ_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "cs-CZ" );
break;
case ON_Locale::WindowsLCID::de_DE_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "de-DE" );
break;
case ON_Locale::WindowsLCID::en_US_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "en-US" );
break;
case ON_Locale::WindowsLCID::es_ES_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "es-ES" );
break;
case ON_Locale::WindowsLCID::es_ES_tradnl_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "es-ES_tradnl" );
break;
case ON_Locale::WindowsLCID::fr_FR_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "fr-FR" );
break;
case ON_Locale::WindowsLCID::it_IT_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "it-IT" );
break;
case ON_Locale::WindowsLCID::ja_JP_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "ja-JP" );
break;
case ON_Locale::WindowsLCID::ko_KR_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "ko-KR" );
break;
case ON_Locale::WindowsLCID::pl_PL_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "pl-PL" );
break;
case ON_Locale::WindowsLCID::pt_PT_LCID:
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "pt-PT" );
break;
case ON_Locale::WindowsLCID::zh_CN_LCID:
// "Hans" script is implied by BCP 47 and should not be in the language tag
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "zh-CN" );
break;
case ON_Locale::WindowsLCID::zh_TW_LCID:
// "Hant" script is implied by BCP 47 and should not be in the language tag
return ON_Locale::FromWindowsLCIDAndName(windows_lcid, "zh-TW" );
break;
}
ON_ERROR("No case for this windows_lcid in the generic runtime code.");
return ON_Locale::Ordinal;
#endif
}
ON_Locale ON_Locale::FromWindowsLCIDAndName(
ON__UINT32 windows_lcid,
const char* name
)
{
if ( windows_lcid == ON_Locale::OrdinalLCID )
return ON_Locale::Ordinal;
if ( windows_lcid == ON_Locale::InvariantCultureLCID )
return ON_Locale::InvariantCulture;
if (0 == name || 0 == name[0])
{
return ON_Locale::InvariantCulture;
}
ON_Locale locale;
locale.m_windows_lcid = windows_lcid;
if (false == ON_Locale::ParseName(
name,
-1,
locale.m_language_subtag, sizeof(locale.m_language_subtag) / sizeof(locale.m_language_subtag[0]),
nullptr, 0,
locale.m_script_subtag, sizeof(locale.m_script_subtag) / sizeof(locale.m_script_subtag[0]),
locale.m_region_subtag, sizeof(locale.m_region_subtag) / sizeof(locale.m_region_subtag[0]),
locale.m_windows_sortorder, sizeof(locale.m_windows_sortorder) / sizeof(locale.m_windows_sortorder[0])
))
{
ON_ERROR("ParseLocaleName() failed.");
return ON_Locale::Ordinal;
}
if (0 == locale.m_language_subtag[0])
{
ON_ERROR("ParseLocaleName() returned empty language name.");
return ON_Locale::Ordinal;
}
if (0 == locale.m_language_subtag[1])
{
ON_ERROR("ParseLocaleName() returned invalid language name.");
return ON_Locale::Ordinal;
}
char* buffer_end = LocaleStringBuilderDestEnd(locale.m_bcp47_language_tag,sizeof(locale.m_bcp47_language_tag)/sizeof(locale.m_bcp47_language_tag[0]));
char* s = locale.m_bcp47_language_tag;
s = LocaleStringBuilder(ON_LOCALE_SUBTAG(0,locale.m_language_subtag),s,buffer_end);
s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',locale.m_script_subtag),s,buffer_end);
s = LocaleStringBuilder(ON_LOCALE_SUBTAG('-',locale.m_region_subtag),s,buffer_end);
if (nullptr == s)
{
ON_ERROR("Unable to create m_bcp47_language_tag.");
return ON_Locale::Ordinal;
}
locale.m_numeric_locale = ON_CRT_C_locale();
locale.m_string_coll_map_locale = ON_CRT_create_locale_ALL( locale.m_bcp47_language_tag );
if (0 == locale.m_string_coll_map_locale)
{
ON_ERROR("ON_CRT_create_locale(LC_ALL, locale.m_bcp47_language_tag) failed.");
return ON_Locale::Ordinal;
}
return locale;
}
ON_Locale ON_Locale::FromWindowsName(
const char* locale_name
)
{
if (0 == locale_name || 0 == locale_name[0])
return ON_Locale::InvariantCulture;
if ('C' == locale_name[0] && 0 == locale_name[1] )
return ON_Locale::Ordinal;
#if defined(ON_RUNTIME_WIN)
const ON_wString buffer1(locale_name);
const wchar_t* wlocale_name = static_cast< const wchar_t* >(buffer1);
wchar_t buffer2[LOCALE_NAME_MAX_LENGTH] = { 0 };
if (false == ::IsValidLocaleName(wlocale_name))
{
const int buffer2_capacity = (int)(sizeof(buffer2) / sizeof(buffer2[0]));
if (0 != ::ResolveLocaleName(wlocale_name,buffer2,buffer2_capacity)
&& ::IsValidLocaleName(buffer2)
)
{
wlocale_name = buffer2;
}
else
{
return ON_Locale::InvariantCulture;
}
}
LCID windows_lcid = ::LocaleNameToLCID(
wlocale_name,
LOCALE_ALLOW_NEUTRAL_NAMES
);
if (0 == windows_lcid)
{
ON_ERROR("Windows ::LocaleNameToLCID() failed.");
return ON_Locale::Ordinal;
}
char slocale_name[ON_Locale::BUFFER_MAXIMUM_CAPACITY] = { 0 };
if (false == ASCIIWideToChar(wlocale_name, ON_wString::Length(wlocale_name), slocale_name, sizeof(slocale_name) / sizeof(slocale_name[0])))
{
ON_ERROR("locale_name contains invalid values.");
return ON_Locale::Ordinal;
}
return ON_Locale::FromWindowsLCIDAndName(
(ON__UINT32)windows_lcid,
slocale_name
);
#else
char language_subtag[32] = { 0 };
char script_subtag[32] = { 0 };
char region_subtag[32] = { 0 };
char windows_sortorder[32] = { 0 };
if (false == ON_Locale::ParseName(
locale_name,
-1,
language_subtag, sizeof(language_subtag) / sizeof(language_subtag[0]),
nullptr, 0,
script_subtag, sizeof(script_subtag) / sizeof(script_subtag[0]),
region_subtag, sizeof(region_subtag) / sizeof(region_subtag[0]),
windows_sortorder, sizeof(windows_sortorder) / sizeof(windows_sortorder[0])
))
{
ON_ERROR("ParseLocaleName() failed.");
return ON_Locale::Ordinal;
}
if (0 == language_subtag[0])
{
ON_ERROR("ParseLocaleName() returned empty language name.");
return ON_Locale::Ordinal;
}
if (0 == language_subtag[1])
{
ON_ERROR("ParseLocaleName() returned invalid language name.");
return ON_Locale::Ordinal;
}
if ( ON_String::EqualOrdinal("zh", -1, language_subtag, -1, true) )
{
// Rhino supports two Chinese locales.
if (ON_String::EqualOrdinal("Hans", -1, script_subtag, -1, true))
{
// Apple uses "zh-Hans" to mean BCP 47 "zh-CN"
// January 26, 2024 - Tim
// I don't think we should fail if the region is not set to China, "zh-Hans" is sufficient to choose "zh-CN"
//if ( 0 == region_subtag[0] || ON_String::EqualOrdinal("CN", -1, region_subtag, -1, true) )
return ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_CN_LCID, "zh-CN" );
}
else if (ON_String::EqualOrdinal("Hant", -1, script_subtag, -1, true))
{
// Apple uses "zh-Hant" to mean BCP 47 "zh-CN"
// January 26, 2024 - Tim
// I don't think we should fail if the region is not set to Taiwan, "zh-Hant" is sufficient to choose "zh-TW"
//if ( 0 == region_subtag[0] || ON_String::EqualOrdinal("TW", -1, region_subtag, -1, true) )
return ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_TW_LCID, "zh-TW" );
}
else if ( ON_String::EqualOrdinal("CN", -1, region_subtag, -1, true) )
return ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_CN_LCID, "zh-CN" );
else if ( ON_String::EqualOrdinal("TW", -1, region_subtag, -1, true) )
return ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_TW_LCID, "zh-TW" );
else
return ON_Locale::FromWindowsLCIDAndName(ON_Locale::zh_TW_LCID, "zh-TW" );
}
else
{
// Rhino supports a single region and default script for these languages.
ON_String cleaned_loc = language_subtag;
cleaned_loc += '-';
cleaned_loc +=region_subtag;
if ( 0 != windows_sortorder[0])
{
cleaned_loc += '_';
cleaned_loc += windows_sortorder;
}
#define LANG_CASE(lcid,lang) if (ON_String::EqualOrdinal(lang, -1, cleaned_loc, -1, true)) return ON_Locale::FromWindowsLCIDAndName(lcid, lang )
LANG_CASE( ON_Locale::cs_CZ_LCID, "cs-CZ" );
LANG_CASE( ON_Locale::de_DE_LCID, "de-DE" );
LANG_CASE( ON_Locale::en_US_LCID, "en-US" );
LANG_CASE( ON_Locale::es_ES_tradnl_LCID, "es-ES_tradnl" ); // _tradnl is Windows sort order
LANG_CASE( ON_Locale::es_ES_LCID, "es-ES" );
LANG_CASE( ON_Locale::fr_FR_LCID, "fr-FR" );
LANG_CASE( ON_Locale::it_IT_LCID, "it-IT" );
LANG_CASE( ON_Locale::ja_JP_LCID, "ja-JP" );
LANG_CASE( ON_Locale::ko_KR_LCID, "ko-KR" );
LANG_CASE( ON_Locale::pl_PL_LCID, "pl-PL" );
LANG_CASE( ON_Locale::pt_PT_LCID, "pt-PT" );
#undef LANG_CASE
// Bail out clause to handle string like fr-US.
#define LANG_CASE_2(two_letter_lang_code,lcid,lang) if (ON_String::EqualOrdinal(two_letter_lang_code, -1, language_subtag, -1, true)) return ON_Locale::FromWindowsLCIDAndName(lcid, lang )
LANG_CASE_2( "cs", ON_Locale::cs_CZ_LCID, "cs-CZ" );
LANG_CASE_2( "de", ON_Locale::de_DE_LCID, "de-DE" );
LANG_CASE_2( "en", ON_Locale::en_US_LCID, "en-US" );
LANG_CASE_2( "es", ON_Locale::es_ES_LCID, "es-ES" );
LANG_CASE_2( "fr", ON_Locale::fr_FR_LCID, "fr-FR" );
LANG_CASE_2( "it", ON_Locale::it_IT_LCID, "it-IT" );
LANG_CASE_2( "ja", ON_Locale::ja_JP_LCID, "ja-JP" );
LANG_CASE_2( "ko", ON_Locale::ko_KR_LCID, "ko-KR" );
LANG_CASE_2( "pl", ON_Locale::pl_PL_LCID, "pl-PL" );
LANG_CASE_2( "pt", ON_Locale::pt_PT_LCID, "pt-PT" );
#undef LANG_CASE_2
}
ON_ERROR("Unsupported language name.");
return ON_Locale::InvariantCulture;
#endif
}
ON_Locale ON_Locale::FromWindowsName(
const wchar_t* locale_name
)
{
const ON_String s_locale_name(locale_name);
return ON_Locale::FromWindowsName((const char*)s_locale_name);
}
ON_Locale ON_Locale::FromBCP47LanguageName(
const char* language_name
)
{
return ON_Locale::FromWindowsName(language_name);
}
ON_Locale ON_Locale::FromBCP47LanguageName(
const wchar_t* language_name
)
{
return ON_Locale::FromWindowsName(language_name);
}
ON_Locale ON_Locale::FromAppleName(
const char* apple_name
)
{
// Apple "locale" names have underbars before <REGION>
// Apple "language" names have hyphens before <REGION> or no <REGION> in the case of Chinese
ON_String buffer(apple_name);
buffer.Replace('_', '-');
apple_name = buffer;
// According to https://en.wikipedia.org/wiki/Chinese_language, Chinese is a family of language
// varieties, often mutually unintelligible. Specifying both Script and REGION
// (zh-Hans-CN or zh-Hant-TW) doesn't narrow things down nearly enough.
//
// Basically we have to hope the string collate and mapping functions supplied by the OS and
// the translations supplied by our staff work well for our customers who select from the
// two types of "Chinese" Rhino offers.
//
if (ON_String::EqualOrdinal("zh-Hans", -1, apple_name, -1, true) || ON_String::EqualOrdinal("zh-CN", -1, apple_name, -1, true))
{
// Apple uses "zh-Hanx" for the "language" name and "zh_CN" for the "locale" name
// when identifying OS X string services that might be useful to people who live
// in the Peoples Republic of China.
return ON_Locale::FromWindowsLCIDAndName(ON_Locale::WindowsLCID::zh_CN_LCID, "zh-CN");
}
if (ON_String::EqualOrdinal("zh-Hant", -1, apple_name, -1, true) || ON_String::EqualOrdinal("zh-TW", -1, apple_name, -1, true))
{
// Apple uses "zh-Hant" for the "language" name and "zh_TW" for the "locale" name
// when identifying OS X string services that might be useful to people who live
// in Taiwan.
return ON_Locale::FromWindowsLCIDAndName(ON_Locale::WindowsLCID::zh_TW_LCID, "zh-TW");
}
return ON_Locale::FromWindowsName( static_cast<const char*>(buffer) );
}
ON_Locale ON_Locale::FromAppleName(
const wchar_t* apple_name
)
{
const ON_String s(apple_name);
return ON_Locale::FromAppleName(s);
}
bool ON_Locale::SetCurrentCulture(
const ON_Locale& current_culture_locale
)
{
ON_Locale::m_CurrentCulture = current_culture_locale;
char buffer[ON_Locale::BUFFER_MAXIMUM_CAPACITY] = { 0 };
size_t buffer_capacity = sizeof(buffer) / sizeof(buffer[0]);
const char* s = nullptr;
if (false == current_culture_locale.IsOrdinalOrInvariantCulture())
{
#if defined(ON_RUNTIME_WIN)
// Windows sometimes appends _<sortorder>
s = current_culture_locale.GetWindowsLocaleName(buffer,buffer_capacity);
#elif defined(ON_RUNTIME_APPLE)
// Apple puts an _ before <REGION>
s = current_culture_locale.GetAppleLocaleName(buffer,buffer_capacity);
#else
// The current "standard" ?
s = current_culture_locale.GetBCP47LanguageTag(buffer, buffer_capacity);
#endif
}
// String collate and mapping functions should use the locale for the name.
if ( nullptr == s || 0 == s[0] )
{
setlocale(LC_ALL,"C");
}
else
{
setlocale(LC_ALL,s);
}
// number parsing and formatting use a decimal point in all cases.
setlocale(LC_NUMERIC,"C");
return true;
}
ON_Locale ON_Locale::FromSubtags(
const char* language_code,
const char* script,
const char* region_identifier
)
{
if (nullptr == language_code || 0 == language_code[0])
return ON_Locale::InvariantCulture;
ON_String language_name(language_code);
language_name.MakeLowerOrdinal();
if ( nullptr != script && (0 != script[0] || (0 != script[1] && 0 != script[2] && 0 != script[3] && 0 == script[4])) )
{
char Script[6];
Script[0] = '-';
Script[1] = ON_String::MapCharacterOrdinal(ON_StringMapOrdinalType::UpperOrdinal,script[0]);
Script[2] = ON_String::MapCharacterOrdinal(ON_StringMapOrdinalType::LowerOrdinal,script[0]);
Script[3] = ON_String::MapCharacterOrdinal(ON_StringMapOrdinalType::LowerOrdinal,script[0]);
Script[4] = ON_String::MapCharacterOrdinal(ON_StringMapOrdinalType::LowerOrdinal,script[0]);
Script[5] = 0;
language_name += Script;
}
if (nullptr != region_identifier && 0 != region_identifier[0])
{
ON_String REGION = '-';
REGION += region_identifier;
REGION.MakeUpperOrdinal();
language_name += REGION;
}
return ON_Locale::FromBCP47LanguageName(language_name);
}
ON_Locale ON_Locale::FromSubtags(
const wchar_t* language_code,
const wchar_t* script,
const wchar_t* region_identifier
)
{
const ON_String s_language_code(language_code);
const ON_String s_script(script);
const ON_String s_region_identifier(region_identifier);
return ON_Locale::FromSubtags(
static_cast<const char*>(s_language_code),
static_cast<const char*>(s_script),
static_cast<const char*>(s_region_identifier)
);
}
ON_CRT_locale_t ON_Locale::NumericLocalePtr() const
{
return (ON_CRT_locale_t)m_numeric_locale;
}
ON_CRT_locale_t ON_Locale::StringCollateAndMapLocalePtr() const
{
return (ON_CRT_locale_t)m_string_coll_map_locale;
}
bool ON_Locale::IsInvariantCulture() const
{
return
0x0027 == m_windows_lcid
&& 0 != m_numeric_locale
&& ON_CRT_C_locale() == m_numeric_locale
&& m_numeric_locale == m_string_coll_map_locale;
}
bool ON_Locale::IsOrdinal() const
{
return
0 == m_windows_lcid
&& 0 != m_numeric_locale
&& ON_CRT_C_locale() == m_numeric_locale
&& m_numeric_locale == m_string_coll_map_locale;
}
bool ON_Locale::IsOrdinalOrInvariantCulture() const
{
return IsOrdinal() || IsInvariantCulture();
}
class ON_CRT_LOCALE
{
public:
static bool Validate_sprintf()
{
// Test formatted printing
char buffer[64] = { 0 };
// Testing C-runtime - do not using ON_String::FormatIntoBuffer
int printf_rc = sprintf(buffer, m_validation_print_format, m_validation_value);
return ValidateString(buffer, sizeof(buffer), printf_rc);
}
static bool Validate_sprintf_s()
{
#if defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU)
// Test formatted printing
char buffer[64] = { 0 };
size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
// Testing C-runtime - do not using ON_String::FormatIntoBuffer
int printf_rc = snprintf(buffer, buffer_capacity, m_validation_print_format, m_validation_value);
return ValidateString(buffer, sizeof(buffer), printf_rc);
#else
// Test formatted printing
char buffer[64] = { 0 };
size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
// Testing C-runtime - do not using ON_String::FormatIntoBuffer
int printf_rc = sprintf_s(buffer, buffer_capacity, m_validation_print_format, m_validation_value);
return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
}
static bool Validate_sprintf_l()
{
#if defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU)
#if defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) || defined(ON_RUNTIME_WASM)
// Test formatted printing
char buffer[64] = { 0 };
// Testing C-runtime - do not using ON_String::FormatIntoBuffer
int printf_rc = sprintf(buffer, m_validation_print_format, m_validation_value);
return ValidateString(buffer, sizeof(buffer), printf_rc);
#else
// Test formatted printing
char buffer[64] = { 0 };
// Testing C-runtime - do not using ON_String::FormatIntoBuffer
int printf_rc = sprintf_l(buffer, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_print_format, m_validation_value);
return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
#else
// Test formatted printing
char buffer[64] = { 0 };
// Testing C-runtime - do not using ON_String::FormatIntoBuffer
int printf_rc = _sprintf_l(buffer, m_validation_print_format, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_value);
return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
}
static bool Validate_sprintf_s_l()
{
#if defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU)
#if defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) || defined(ON_RUNTIME_WASM)
// Test formatted printing
char buffer[64] = { 0 };
size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
// Testing C-runtime - do not using ON_String::FormatIntoBuffer
int printf_rc = snprintf(buffer, buffer_capacity, m_validation_print_format, m_validation_value);
return ValidateString(buffer, sizeof(buffer), printf_rc);
#else
// Test formatted printing
char buffer[64] = { 0 };
size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
// Testing C-runtime - do not using ON_String::FormatIntoBuffer
int printf_rc = snprintf_l(buffer, buffer_capacity, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_print_format, m_validation_value);
return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
#else
// Test formatted printing
char buffer[64] = { 0 };
size_t buffer_capacity = (sizeof(buffer) / sizeof(buffer[0])) - 1;
// Testing C-runtime - do not using ON_String::FormatIntoBuffer
int printf_rc = _sprintf_s_l(buffer, buffer_capacity, m_validation_print_format, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_value);
return ValidateString(buffer, sizeof(buffer), printf_rc);
#endif
}
static bool Validate_sscanf()
{
// Test formatted scanning
double a = ON_UNSET_VALUE;
// Testing C-runtime - do not using ON_String::Scan
int scanf_rc = sscanf(m_validation_string, m_validation_scan_format, &a);
return ValidateDouble(a, scanf_rc);
}
static bool Validate_sscanf_s()
{
#if defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU)
// Test formatted scanning
double a = ON_UNSET_VALUE;
// Testing C-runtime - do not using ON_String::Scan
int scanf_rc = sscanf(m_validation_string, m_validation_scan_format, &a);
return ValidateDouble(a, scanf_rc);
#else
// Test formatted scanning
double a = ON_UNSET_VALUE;
// Testing C-runtime - do not using ON_String::Scan
int scanf_rc = sscanf_s(m_validation_string, m_validation_scan_format, &a);
return ValidateDouble(a, scanf_rc);
#endif
}
static bool Validate_sscanf_l()
{
#if defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU)
#if defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) || defined(ON_RUNTIME_WASM)
// Test formatted scanning
double a = ON_UNSET_VALUE;
// Testing C-runtime - do not using ON_String::Scan
int scanf_rc = sscanf(m_validation_string, m_validation_scan_format, &a);
return ValidateDouble(a, scanf_rc);
#else
// Test formatted scanning
double a = ON_UNSET_VALUE;
// Testing C-runtime - do not using ON_String::Scan
int scanf_rc = sscanf_l(m_validation_string, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_scan_format, &a);
return ValidateDouble(a, scanf_rc);
#endif
#else
// Test formatted scanning
double a = ON_UNSET_VALUE;
// Testing C-runtime - do not using ON_String::Scan
int scanf_rc = _sscanf_l(m_validation_string, m_validation_scan_format, ON_Locale::InvariantCulture.NumericLocalePtr(), &a);
return ValidateDouble(a, scanf_rc);
#endif
}
static bool Validate_sscanf_s_l()
{
#if defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU)
#if defined(ON_RUNTIME_ANDROID) || defined(ON_RUNTIME_LINUX) || defined(ON_RUNTIME_WASM)
// Test formatted scanning
double a = ON_UNSET_VALUE;
// Testing C-runtime - do not using ON_String::Scan
int scanf_rc = sscanf(m_validation_string, m_validation_scan_format, &a);
return ValidateDouble(a, scanf_rc);
#else
// Test formatted scanning
double a = ON_UNSET_VALUE;
// Testing C-runtime - do not using ON_String::Scan
int scanf_rc = sscanf_l(m_validation_string, ON_Locale::InvariantCulture.NumericLocalePtr(), m_validation_scan_format, &a);
return ValidateDouble(a, scanf_rc);
#endif
#else
// Test formatted scanning
double a = ON_UNSET_VALUE;
// Testing C-runtime - do not using ON_String::Scan
int scanf_rc = _sscanf_s_l(m_validation_string, m_validation_scan_format, ON_Locale::InvariantCulture.NumericLocalePtr(), &a);
return ValidateDouble(a, scanf_rc);
#endif
}
private:
static bool ValidateString(
const char* buffer,
size_t sizeof_buffer,
int printf_rc
)
{
bool rc = false;
if (0 == buffer
|| printf_rc <= 0
|| sizeof_buffer <= 0
|| 0 == m_validation_string
|| m_validation_length <= 0
|| m_validation_length >= sizeof_buffer
|| m_validation_length != (size_t)printf_rc
)
{
rc = (0 == buffer && 0 == sizeof_buffer && 0 == m_validation_string && 0 == m_validation_length && 0 == printf_rc);
}
else
{
for (size_t i = 0; i < sizeof_buffer; i++)
{
if (i > m_validation_length)
break;
if (buffer[i] != m_validation_string[i])
break;
if (0 == m_validation_string[i])
{
rc = (i == m_validation_length);
break;
}
}
}
return rc;
}
static bool ValidateDouble(
double a,
int scan_rc
)
{
bool rc;
if (1 == scan_rc && a == a && a == m_validation_value)
{
rc = true;
}
else
{
rc = false;
}
return rc;
}
static const double m_validation_value;
static const char* m_validation_print_format;
static const char* m_validation_scan_format;
static const char* m_validation_string;
static const size_t m_validation_length;
};
const double ON_CRT_LOCALE::m_validation_value = 12345678901234.25;
const char* ON_CRT_LOCALE::m_validation_print_format = "%.17lg";
const char* ON_CRT_LOCALE::m_validation_scan_format = "%lg";
const char* ON_CRT_LOCALE::m_validation_string = "12345678901234.25";
const size_t ON_CRT_LOCALE::m_validation_length = 17;
bool ON_Locale::PeriodIsCRuntimeDecimalPoint()
{
// Test formatted printing
// These tests use the locale ON_Locale::InvariantCulture.LocalePtr()
// and should always pass.
if (!ON_CRT_LOCALE::Validate_sprintf_l())
return false;
if (!ON_CRT_LOCALE::Validate_sprintf_s_l())
return false;
// These tests use the C-runtime locale and will fail if
// the locale does not use a period as the decimal separator.
if (!ON_CRT_LOCALE::Validate_sprintf())
return false;
if (!ON_CRT_LOCALE::Validate_sprintf_s())
return false;
// Test formatted scanning
// These tests use the locale ON_Locale::InvariantCulture.LocalePtr()
// and should always pass.
if (!ON_CRT_LOCALE::Validate_sscanf_l())
return false;
if (!ON_CRT_LOCALE::Validate_sscanf_s_l())
return false;
// These tests use the C-runtime locale and will fail if
// the locale does not use a period as the decimal separator.
if (!ON_CRT_LOCALE::Validate_sscanf())
return false;
if (!ON_CRT_LOCALE::Validate_sscanf_s())
return false;
return true;
}
bool ON_Locale::SetPeriodAsCRuntimeDecimalPoint()
{
bool rc = ON_Locale::PeriodIsCRuntimeDecimalPoint();
if (false == rc)
{
#if defined(ON_COMPILER_MSC)
// Microsoft's C compiler
const int prev_type = _configthreadlocale(_DISABLE_PER_THREAD_LOCALE);
const char* s = setlocale(LC_NUMERIC, "C");
rc = (0 != s && 'C' == s[0] && 0 == s[1]);
if (rc)
rc = ON_Locale::PeriodIsCRuntimeDecimalPoint();
if (prev_type != _DISABLE_PER_THREAD_LOCALE && prev_type >= 0)
_configthreadlocale(prev_type);
#elif defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU)
// Apple's Clang compiler
const char* s = setlocale(LC_NUMERIC, "C");
rc = (0 != s && 'C' == s[0] && 0 == s[1]);
if (rc)
rc = ON_Locale::PeriodIsCRuntimeDecimalPoint();
#else
// another compiler
const int prev_type = _configthreadlocale(_DISABLE_PER_THREAD_LOCALE);
const char* s = setlocale(LC_NUMERIC, "C");
rc = (0 != s && 'C' == s[0] && 0 == s[1]);
if (rc)
rc = ON_Locale::PeriodIsCRuntimeDecimalPoint();
if (prev_type != _DISABLE_PER_THREAD_LOCALE && prev_type >= 0)
_configthreadlocale(prev_type);
#endif
}
return rc;
}
unsigned int ON_Locale::EnforcePeriodAsCRuntimeDecimalPoint()
{
if (true == ON_Locale::PeriodIsCRuntimeDecimalPoint())
return 1; // decimal point = period;
if (false == ON_Locale::SetPeriodAsCRuntimeDecimalPoint())
return 0; // attempt to set decimal point = period failed
if (false == ON_Locale::PeriodIsCRuntimeDecimalPoint())
return 0; // decimal point != period
return 2; // decimal point = period
}