diff --git a/include/ustring.h b/include/ustring.h index 102d39c..d4d8d68 100755 --- a/include/ustring.h +++ b/include/ustring.h @@ -24,7 +24,7 @@ private: private: inline void clear_internal_buffers() const; - inline void init_from_utf8_str(const char* str); + inline void init_from_utf8_str(const char* str, bool needUnescape); inline void init_from_utf16_str(const char16_t* str, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN); inline void init_from_utf32_str(const char32_t* str, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN); @@ -38,10 +38,10 @@ public: ustring(); ustring(const ustring& str); ustring(ustring&& str); - ustring(const char* str); + ustring(const char* str, bool needUnescape = false); ustring(const char16_t* str, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN); ustring(const char32_t* str, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN); - ustring(const std::string& str); + ustring(const std::string& str, bool needUnescape = false); ustring(const std::wstring& str); explicit ustring(std::size_t n); diff --git a/include/utf.h b/include/utf.h index d8a7835..5ed6878 100755 --- a/include/utf.h +++ b/include/utf.h @@ -60,13 +60,13 @@ const char32_t* utf32_skip_bom(const char32_t* str); std::size_t significant_bits(uint32_t v); void utf32_strcpy_with_convert_byteorder(const char32_t* src, char32_t* dst, std::size_t num, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN); -void utf8_to_ucs4(const char* src, char32_t* dst, std::size_t symbols); +void utf8_to_ucs4(const char* src, char32_t* dst, std::size_t symbols, bool needUnescape); void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols); void utf16_to_ucs4(const char16_t* src, char32_t* dst, std::size_t symbols, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN); void ucs4_to_utf16(const char32_t* src, char16_t* dst, std::size_t symbols, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN); -std::size_t utf8_str_len(const char* str); -std::size_t utf8_get_symbol_size(const char* str); +std::size_t utf8_str_len(const char* str, bool needUnescape); +std::size_t utf8_get_symbol_size(const char* str, bool needUnescape); std::size_t utf16_str_len(const char16_t* str, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN); std::size_t utf16_get_symbol_size(const char16_t* str, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN); std::size_t utf32_str_len(const char32_t* str); diff --git a/src/ustring.cpp b/src/ustring.cpp index d0f1b82..9e45b0f 100755 --- a/src/ustring.cpp +++ b/src/ustring.cpp @@ -44,12 +44,13 @@ ustring::ustring(ustring&& str): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(null /** * Конструктор из UTF-8 строки * @param str Исходная строка в UTF-8 (null terminated) + * @param needUnescape Определяет нужно ли декодировать экранированные UTF-8 символы (\uXXXX и \UXXXXXXXX) * @detailed Так как символы ASCII являются подмножеством UTF-8, при создании строки * из массива ASCII символов нужно применять именно этот конструктор */ -ustring::ustring(const char* str): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +ustring::ustring(const char* str, bool needUnescape /* = false */): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) { - init_from_utf8_str(str); + init_from_utf8_str(str, needUnescape); } /** @@ -75,10 +76,11 @@ ustring::ustring(const char32_t* str, byte_order byteOrder /* = BYTE_ORDER_LITTL /** * Конструктор из std::string * @param str Исходная строка в UTF-8 + * @param needUnescape Определяет нужно ли декодировать экранированные UTF-8 символы (\uXXXX и \UXXXXXXXX) */ -ustring::ustring(const std::string& str): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +ustring::ustring(const std::string& str, bool needUnescape /* = false */): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) { - init_from_utf8_str(str.c_str()); + init_from_utf8_str(str.c_str(), needUnescape); } /** @@ -102,13 +104,13 @@ ustring::ustring( std::size_t n ): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nu } -void ustring::init_from_utf8_str(const char* str) +void ustring::init_from_utf8_str(const char* str, bool needUnescape) { const char* strData = utf8_skip_bom(str); - _len = utf8_str_len(strData); + _len = utf8_str_len(strData, needUnescape); _capacity = _len; _pData = new char32_t[_len + 1]; - utf8_to_ucs4(strData, _pData, _len); + utf8_to_ucs4(strData, _pData, _len, needUnescape); _pData[_len] = char32_t(0); } diff --git a/src/utf.cpp b/src/utf.cpp index f2e2830..caf6b96 100755 --- a/src/utf.cpp +++ b/src/utf.cpp @@ -113,18 +113,19 @@ char32_t invert_byte_order_32(char32_t val) * @param src Указатель на буфер с UTF-8 строкой (без BOM) * @param dst Указатель на буфер для UCS-4 строки * @param symbols Количество символов в строке + * @param needUnescape Определяет нужно ли декодировать экранированные UTF-8 символы (\uXXXX и \UXXXXXXXX) * @throw bad_conversion Генерируется в случае ошибки декодирования (строка на входе закодирована не в UTF-8) * @detailed Функция поддерживает декодирование экранированных UCS-2 (\uXXXX) * и UCS-4 (\UXXXXXXXX) символов */ -void utf8_to_ucs4(const char* src, char32_t* dst, std::size_t symbols) +void utf8_to_ucs4(const char* src, char32_t* dst, std::size_t symbols, bool needUnescape) { std::size_t bytes = 0; uint32_t s = 0; for(std::size_t i = 0; i < symbols; ++i) { - bytes = utf8_get_symbol_size(src); + bytes = utf8_get_symbol_size(src, needUnescape); s = 0; if(bytes == 1) // ASCII @@ -236,15 +237,16 @@ void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols) /** * Возвращает длину UTF-8 строки * @param str UTF-8 строка + * @param needUnescape Определяет нужно ли декодировать экранированные UTF-8 символы (\uXXXX и \UXXXXXXXX) * @return Длина втроки в символах */ -std::size_t utf8_str_len(const char* str) +std::size_t utf8_str_len(const char* str, bool needUnescape) { std::size_t len = 0; while(*str) { - str += utf8_get_symbol_size(str); + str += utf8_get_symbol_size(str, needUnescape); ++len; } @@ -272,13 +274,14 @@ std::size_t ucs4_get_utf8_str_bytes(const char32_t* str) /** * Возвращает размер символа UTF-8 строки с учетом экранированных UCS-2 и UCS-4 символов * @param str Указатель на требуемый символ в UTF-8 строке + * @param needUnescape Определяет нужно ли декодировать экранированные UTF-8 символы (\uXXXX и \UXXXXXXXX) * @return Количество байт, занимаемых символом */ -std::size_t utf8_get_symbol_size(const char* str) +std::size_t utf8_get_symbol_size(const char* str, bool needUnescape) { uint32_t s = 0; - if(*str == '\\') // UCS unescape - { + if(needUnescape && *str == '\\') // UCS unescape + { if(*(str + 1) == 'u') { return 6; diff --git a/test/main.cpp b/test/main.cpp index e18254d..d18c24d 100755 --- a/test/main.cpp +++ b/test/main.cpp @@ -92,9 +92,9 @@ const unsigned char helloResultUcs4[] = { 0x48, 0x00, 0x00, 0x00, 0x65, 0x00, 0x TEST(Convert, utf8_symbol_size) { - ASSERT_EQ(1, utf8_get_symbol_size((const char*)&helloStrUtf8[0])); - ASSERT_EQ(2, utf8_get_symbol_size((const char*)&helloStrUtf8[15])); - ASSERT_EQ(4, utf8_get_symbol_size((const char*)&helloStrUtf8[41])); + ASSERT_EQ(1, utf8_get_symbol_size((const char*)&helloStrUtf8[0], false)); + ASSERT_EQ(2, utf8_get_symbol_size((const char*)&helloStrUtf8[15], false)); + ASSERT_EQ(4, utf8_get_symbol_size((const char*)&helloStrUtf8[41], false)); } // @@ -114,7 +114,7 @@ TEST(Convert, utf8_symbol_size_by_ucs4_symbol) TEST(Convert, utf8_length) { - ASSERT_EQ(helloStrLen, utf8_str_len((const char*)helloStrUtf8)); + ASSERT_EQ(helloStrLen, utf8_str_len((const char*)helloStrUtf8, false)); } // @@ -193,9 +193,9 @@ TEST(Convert, utf32_length) // TEST(Convert, utf8_to_ucs4) { - std::size_t len = utf8_str_len((const char*)helloStrUtf8); + std::size_t len = utf8_str_len((const char*)helloStrUtf8, false); char32_t* data = new char32_t[len]; - utf8_to_ucs4((const char*)helloStrUtf8, data, len); + utf8_to_ucs4((const char*)helloStrUtf8, data, len, false); EXPECT_EQ(0, memcmp(helloStrUcs4, data, len*sizeof(char32_t))); delete[] data; } @@ -274,7 +274,7 @@ TEST(Convert, ucs4_to_utf16be) TEST(ConvertError, utf8_length_error) { - EXPECT_THROW(utf8_str_len((const char*)badStr2Utf8), bad_conversion); + EXPECT_THROW(utf8_str_len((const char*)badStr2Utf8, false), bad_conversion); } // @@ -283,9 +283,9 @@ TEST(ConvertError, utf8_length_error) TEST(ConvertError, utf8_decode_error) { - std::size_t len = utf8_str_len((const char*)badStrUtf8); + std::size_t len = utf8_str_len((const char*)badStrUtf8, false); char32_t* data = new char32_t[len]; - EXPECT_THROW(utf8_to_ucs4((const char*)badStrUtf8, data, len), bad_conversion); + EXPECT_THROW(utf8_to_ucs4((const char*)badStrUtf8, data, len, false), bad_conversion); delete[] data; } @@ -335,7 +335,15 @@ TEST(ConvertError, ucs4_wrong_symbol) TEST(Ustring, utf8_constructor) { ustring str((const char*)helloStrUtf8); + ustring str2("Hello\\u0020world!", true); + ustring str3("Hello\\u0020world!", false); + ustring str4 = "Hello\\u0020world!"; + + EXPECT_EQ(0, memcmp(helloStrUcs4, str.utf32_str(), sizeof(helloStrUcs4))); + EXPECT_TRUE(str2 == "Hello world!"); + EXPECT_TRUE(str3 == "Hello\\u0020world!"); + EXPECT_TRUE(str4 == "Hello\\u0020world!"); } // diff --git a/todo b/todo index c51acac..de7a7b5 100644 --- a/todo +++ b/todo @@ -1,9 +1,9 @@ Задачи на будущее -1. Сделать декодирование экранированных UCS-2 (\uXXXX) и UCS-4 (\UXXXXXXXX) символов опциональным (по умолчанию выключено), добавить соответствующий тестю +1. Сделать декодирование экранированных UCS-2 (\uXXXX) и UCS-4 (\UXXXXXXXX) символов опциональным (по умолчанию выключено), добавить соответствующий тест. 2. В функции ucs4_to_utf8 добавить опциональное экранирование не-ANSI символов (по умолчанию выключено), добавить тесты. -3. Тестирование на android. +3. Тестирование на android. Можно сделать в QEMU виртуальную машину с ARM процессором. 4. Сравнение по производительности с std::string, профилирование, оптимизация (возможно переписывание узких мест на ассемблере с использованием SIMD инструкций)