diff --git a/include/ustring.h b/include/ustring.h index d4d8d68..81c9d4d 100755 --- a/include/ustring.h +++ b/include/ustring.h @@ -48,7 +48,7 @@ public: operator std::string() const; operator std::wstring() const; - const char* utf8_str(bool addBom = false) const; + const char* utf8_str(bool addBom = false, bool needEscape = false) const; const char16_t* utf16_str(bool addBom = false, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN) const; const char32_t* utf32_str(bool addBom = false, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN) const; diff --git a/include/utf.h b/include/utf.h index 5ed6878..77a4150 100755 --- a/include/utf.h +++ b/include/utf.h @@ -61,7 +61,7 @@ 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, bool needUnescape); -void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols); +void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols, bool needEscape); 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); @@ -71,8 +71,8 @@ std::size_t utf16_str_len(const char16_t* str, byte_order byteOrder = BYTE_ORDER 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); -std::size_t ucs4_get_utf8_symbol_size(const char32_t symbol); -std::size_t ucs4_get_utf8_str_bytes(const char32_t* str); +std::size_t ucs4_get_utf8_symbol_size(const char32_t symbol, bool needEscape); +std::size_t ucs4_get_utf8_str_bytes(const char32_t* str, bool needEscape); std::size_t ucs4_get_utf16_symbol_size(const char32_t symbol); std::size_t ucs4_get_utf16_str_bytes(const char32_t* str); diff --git a/src/ustring.cpp b/src/ustring.cpp index 9e45b0f..dc96d7d 100755 --- a/src/ustring.cpp +++ b/src/ustring.cpp @@ -151,24 +151,22 @@ ustring::operator std::wstring() const /** * Возвращает указатель на временный буфер с UTF-8 строкой * @param addBom Указывает, добавлять ли BOM в возвращаемую строку + * @param needEscape Определяет, нужно ли экранировать не-ASCII символы * @return Указатель на буфер со строкой, закодированной в UTF-8 * @detailed Этот буфер является временным и будет высвобожден при любом изменении строки */ -const char* ustring::utf8_str( bool addBom /*= false*/ ) const +const char* ustring::utf8_str(bool addBom /*= false*/ , bool needEscape /*= false*/) const { - if(!_pUtf8) - { - std::size_t bomSize = (addBom ? sizeof(g_utf8_bom) : 0); - std::size_t len = ucs4_get_utf8_str_bytes(_pData); - _pUtf8 = new char[bomSize + len + 1]; - ucs4_to_utf8(_pData, _pUtf8 + bomSize, length()); - _pUtf8[len + bomSize] = char(0); - - if(addBom) - { - memcpy(_pUtf8, g_utf8_bom, bomSize); - } - } + std::size_t bomSize = (addBom ? sizeof(g_utf8_bom) : 0); + std::size_t len = ucs4_get_utf8_str_bytes(_pData, needEscape); + _pUtf8 = new char[bomSize + len + 1]; + ucs4_to_utf8(_pData, _pUtf8 + bomSize, length(), needEscape); + _pUtf8[len + bomSize] = char(0); + + if(addBom) + { + memcpy(_pUtf8, g_utf8_bom, bomSize); + } return _pUtf8; } @@ -180,19 +178,16 @@ const char* ustring::utf8_str( bool addBom /*= false*/ ) const */ const char16_t* ustring::utf16_str( bool addBom /*= false*/, byte_order byteOrder /*= BYTE_ORDER_LITTLE_ENDIAN*/ ) const { - if(!_pUtf16) - { - std::size_t bomSize = (addBom ? sizeof(g_utf16le_bom)/2 : 0); - std::size_t len = ucs4_get_utf16_str_bytes(_pData)/2; - _pUtf16 = new char16_t[bomSize + len + 1]; - ucs4_to_utf16(_pData, _pUtf16 + bomSize, length(), byteOrder); - _pUtf16[len + bomSize] = char16_t(0); - - if(addBom) - { - memcpy(_pUtf16, (byteOrder == BYTE_ORDER_LITTLE_ENDIAN ? g_utf16le_bom : g_utf16be_bom), sizeof(g_utf16le_bom)); - } - } + std::size_t bomSize = (addBom ? sizeof(g_utf16le_bom)/2 : 0); + std::size_t len = ucs4_get_utf16_str_bytes(_pData)/2; + _pUtf16 = new char16_t[bomSize + len + 1]; + ucs4_to_utf16(_pData, _pUtf16 + bomSize, length(), byteOrder); + _pUtf16[len + bomSize] = char16_t(0); + + if(addBom) + { + memcpy(_pUtf16, (byteOrder == BYTE_ORDER_LITTLE_ENDIAN ? g_utf16le_bom : g_utf16be_bom), sizeof(g_utf16le_bom)); + } return _pUtf16; } @@ -204,18 +199,15 @@ const char16_t* ustring::utf16_str( bool addBom /*= false*/, byte_order byteOrde */ const char32_t* ustring::utf32_str( bool addBom /*= false*/, byte_order byteOrder /*= BYTE_ORDER_LITTLE_ENDIAN*/ ) const { - if(!_pUtf32) - { - std::size_t bomSize = (addBom ? sizeof(g_utf32le_bom)/sizeof(char32_t) : 0); - _pUtf32 = new char32_t[bomSize + _len + 1]; - utf32_strcpy_with_convert_byteorder(_pData, _pUtf32 + bomSize, _len, byteOrder); - _pUtf32[_len + bomSize] = char16_t(0); - - if(addBom) - { - memcpy(_pUtf32, (byteOrder == BYTE_ORDER_LITTLE_ENDIAN ? g_utf32le_bom : g_utf32be_bom), sizeof(g_utf32le_bom)); - } - } + std::size_t bomSize = (addBom ? sizeof(g_utf32le_bom)/sizeof(char32_t) : 0); + _pUtf32 = new char32_t[bomSize + _len + 1]; + utf32_strcpy_with_convert_byteorder(_pData, _pUtf32 + bomSize, _len, byteOrder); + _pUtf32[_len + bomSize] = char16_t(0); + + if(addBom) + { + memcpy(_pUtf32, (byteOrder == BYTE_ORDER_LITTLE_ENDIAN ? g_utf32le_bom : g_utf32be_bom), sizeof(g_utf32le_bom)); + } return _pUtf32; } @@ -491,7 +483,7 @@ std::size_t ustring::store_to_file( const ustring& fileName, encoding enc ) cons { case ustring::UTF8: data = utf8_str(); - dataSize = ucs4_get_utf8_str_bytes(_pData); + dataSize = ucs4_get_utf8_str_bytes(_pData, false); file.write(utf8Bom, sizeof(utf8Bom)); break; case ustring::UTF16LE: diff --git a/src/utf.cpp b/src/utf.cpp index caf6b96..45a6c68 100755 --- a/src/utf.cpp +++ b/src/utf.cpp @@ -190,10 +190,11 @@ void utf8_to_ucs4(const char* src, char32_t* dst, std::size_t symbols, bool need * @param src Указатель на буфер с UCS-4 строкой * @param dst Указатель на буфер для UTF-8 строки * @param symbols Количество символов в строке + * @param needEscape Определяет, нужно ли экранировать не-ASCII символы * @throw bad_conversion Генерируется в случае ошибки кодирования (хотя бы один символ не входит * в набор символов юникода (UCS-4)) */ -void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols) +void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols, bool needEscape) { std::size_t sBits, totalBytes; uint32_t s; @@ -206,7 +207,7 @@ void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols) } sBits = significant_bits(src[i]); - totalBytes = ucs4_get_utf8_symbol_size(src[i]); + totalBytes = ucs4_get_utf8_symbol_size(src[i], needEscape); s = static_cast(src[i]); if(totalBytes == 1) @@ -215,6 +216,26 @@ void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols) ++dst; continue; } + else if(needEscape) + { + if(src[i] < 0xFFFF) + { + if(sprintf(dst, "\\u%04X", src[i]) < 0) + { + throw bad_conversion("error escaping character", src[i]); + } + dst += 6; + } + else + { + if(sprintf(dst, "\\U%08X", src[i]) < 0) + { + throw bad_conversion("error escaping character", src[i]); + } + dst += 10; + } + continue; + } std::size_t pos = 0, shiftLeft, shiftRight; for(std::size_t n = totalBytes; n > 1; --n) @@ -256,15 +277,16 @@ std::size_t utf8_str_len(const char* str, bool needUnescape) /** * Возвращает количество байт, которые займет строка, будучи кодированной в UTF-8 * @param str Исходная строка в UCS-4 + * @param needEscape Определяет, нужно ли экранировать не-ASCII символы * @return Количество байт, необходимые для UTF-8 представления строки */ -std::size_t ucs4_get_utf8_str_bytes(const char32_t* str) +std::size_t ucs4_get_utf8_str_bytes(const char32_t* str, bool needEscape) { std::size_t bytes = 0; while(*str) { - bytes += ucs4_get_utf8_symbol_size(*str); + bytes += ucs4_get_utf8_symbol_size(*str, needEscape); ++str; } @@ -323,15 +345,28 @@ std::size_t utf8_first_byte_mask(std::size_t symbolSize) /** * Вычисляет, какой размер будет у символа, если кодировать его в UTF-8 * @param symbol Исходный символ в UCS-4 + * @param needEscape Определяет, нужно ли экранировать не-ASCII символы * @return Размер UTF-8 символа в байтах * @throw bad_conversion Генерируется если символ не входит в набор символов юникода (UCS-4) */ -std::size_t ucs4_get_utf8_symbol_size(const char32_t symbol) +std::size_t ucs4_get_utf8_symbol_size(const char32_t symbol, bool needEscape) { if(symbol > MAX_UNICODE_CHARACTER_CODE) { throw bad_conversion("error converting UCS-4 to UTF-8", symbol); } + + if(needEscape && symbol > 0x80) + { + if(symbol < 0xFFFF) + { + return 6; // \uXXXX + } + else + { + return 10; // \UXXXXXXXX + } + } std::size_t sBits = significant_bits(symbol); std::size_t sixBitBytes = sBits / 6; diff --git a/test/main.cpp b/test/main.cpp index d18c24d..6ddb15f 100755 --- a/test/main.cpp +++ b/test/main.cpp @@ -53,6 +53,9 @@ const unsigned char helloStrUtf32be[] = { 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x01, 0xd6, 0xfb, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00 }; +// Escaped +const char helloStrEscaped[] = "Hello World!\n\\u041F\\u0440\\u0438\\u0432\\u0435\\u0442, \\u041C\\u0438\\u0440!\n\\u2206 = \\U0001D6FB\\u00B2\n"; + // Длина тестовой строки в символах const std::size_t helloStrLen = 33; @@ -103,9 +106,9 @@ TEST(Convert, utf8_symbol_size) TEST(Convert, utf8_symbol_size_by_ucs4_symbol) { - ASSERT_EQ(1, ucs4_get_utf8_symbol_size(*(const char32_t*)&helloStrUcs4[0])); - ASSERT_EQ(2, ucs4_get_utf8_symbol_size(*(const char32_t*)&helloStrUcs4[52])); - ASSERT_EQ(4, ucs4_get_utf8_symbol_size(*(const char32_t*)&helloStrUcs4[120])); + ASSERT_EQ(1, ucs4_get_utf8_symbol_size(*(const char32_t*)&helloStrUcs4[0], false)); + ASSERT_EQ(2, ucs4_get_utf8_symbol_size(*(const char32_t*)&helloStrUcs4[52], false)); + ASSERT_EQ(4, ucs4_get_utf8_symbol_size(*(const char32_t*)&helloStrUcs4[120], false)); } // @@ -123,7 +126,7 @@ TEST(Convert, utf8_length) TEST(Convert, utf8_str_len_by_ucs4_str) { - ASSERT_EQ(sizeof(helloStrUtf8) - 1, ucs4_get_utf8_str_bytes((const char32_t*)helloStrUcs4)); + ASSERT_EQ(sizeof(helloStrUtf8) - 1, ucs4_get_utf8_str_bytes((const char32_t*)helloStrUcs4, false)); } // @@ -205,10 +208,10 @@ TEST(Convert, utf8_to_ucs4) // TEST(Convert, ucs4_to_utf8) { - std::size_t len = ucs4_get_utf8_str_bytes((const char32_t*)helloStrUcs4); + std::size_t len = ucs4_get_utf8_str_bytes((const char32_t*)helloStrUcs4, false); std::size_t symbols = utf32_str_len((const char32_t*)helloStrUcs4); char* data = new char[len]; - ucs4_to_utf8((const char32_t*)helloStrUcs4, data, symbols); + ucs4_to_utf8((const char32_t*)helloStrUcs4, data, symbols, false); EXPECT_EQ(0, memcmp(helloStrUtf8, data, len)); delete[] data; } @@ -319,7 +322,7 @@ TEST(ConvertError, utf16_decode_error2) TEST(ConvertError, ucs4_wrong_symbol) { - EXPECT_THROW(ucs4_get_utf8_symbol_size(*(const char32_t*)badStrUcs4), bad_conversion); + EXPECT_THROW(ucs4_get_utf8_symbol_size(*(const char32_t*)badStrUcs4, false), bad_conversion); EXPECT_THROW(ucs4_get_utf16_symbol_size(*(const char32_t*)badStrUcs4), bad_conversion); } @@ -374,6 +377,7 @@ TEST(Ustring, utf8_cast_operator) { ustring str((const char32_t*)helloStrUcs4); EXPECT_EQ(0, memcmp(helloStrUtf8, str.utf8_str(), sizeof(helloStrUtf8))); + EXPECT_EQ(0, memcmp(str.utf8_str(false, true), helloStrEscaped, sizeof(helloStrEscaped))); } //