Добавил возможность экранировать не-ASCII символы при кодировании в UTF-8

This commit is contained in:
Selim Mustafaev 2013-10-08 07:28:45 +04:00
parent 9afb0b9976
commit 0d6e6e7333
5 changed files with 87 additions and 56 deletions

View File

@ -48,7 +48,7 @@ public:
operator std::string() const; operator std::string() const;
operator std::wstring() 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 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; const char32_t* utf32_str(bool addBom = false, byte_order byteOrder = BYTE_ORDER_LITTLE_ENDIAN) const;

View File

@ -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 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 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 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); 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 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 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_symbol_size(const char32_t symbol, bool needEscape);
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 ucs4_get_utf16_symbol_size(const char32_t symbol); std::size_t ucs4_get_utf16_symbol_size(const char32_t symbol);
std::size_t ucs4_get_utf16_str_bytes(const char32_t* str); std::size_t ucs4_get_utf16_str_bytes(const char32_t* str);

View File

@ -151,24 +151,22 @@ ustring::operator std::wstring() const
/** /**
* Возвращает указатель на временный буфер с UTF-8 строкой * Возвращает указатель на временный буфер с UTF-8 строкой
* @param addBom Указывает, добавлять ли BOM в возвращаемую строку * @param addBom Указывает, добавлять ли BOM в возвращаемую строку
* @param needEscape Определяет, нужно ли экранировать не-ASCII символы
* @return Указатель на буфер со строкой, закодированной в UTF-8 * @return Указатель на буфер со строкой, закодированной в UTF-8
* @detailed Этот буфер является временным и будет высвобожден при любом изменении строки * @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, needEscape);
std::size_t bomSize = (addBom ? sizeof(g_utf8_bom) : 0); _pUtf8 = new char[bomSize + len + 1];
std::size_t len = ucs4_get_utf8_str_bytes(_pData); ucs4_to_utf8(_pData, _pUtf8 + bomSize, length(), needEscape);
_pUtf8 = new char[bomSize + len + 1]; _pUtf8[len + bomSize] = char(0);
ucs4_to_utf8(_pData, _pUtf8 + bomSize, length());
_pUtf8[len + bomSize] = char(0); if(addBom)
{
if(addBom) memcpy(_pUtf8, g_utf8_bom, bomSize);
{ }
memcpy(_pUtf8, g_utf8_bom, bomSize);
}
}
return _pUtf8; 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 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;
std::size_t bomSize = (addBom ? sizeof(g_utf16le_bom)/2 : 0); _pUtf16 = new char16_t[bomSize + len + 1];
std::size_t len = ucs4_get_utf16_str_bytes(_pData)/2; ucs4_to_utf16(_pData, _pUtf16 + bomSize, length(), byteOrder);
_pUtf16 = new char16_t[bomSize + len + 1]; _pUtf16[len + bomSize] = char16_t(0);
ucs4_to_utf16(_pData, _pUtf16 + bomSize, length(), byteOrder);
_pUtf16[len + bomSize] = char16_t(0); if(addBom)
{
if(addBom) memcpy(_pUtf16, (byteOrder == BYTE_ORDER_LITTLE_ENDIAN ? g_utf16le_bom : g_utf16be_bom), sizeof(g_utf16le_bom));
{ }
memcpy(_pUtf16, (byteOrder == BYTE_ORDER_LITTLE_ENDIAN ? g_utf16le_bom : g_utf16be_bom), sizeof(g_utf16le_bom));
}
}
return _pUtf16; 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 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];
std::size_t bomSize = (addBom ? sizeof(g_utf32le_bom)/sizeof(char32_t) : 0); utf32_strcpy_with_convert_byteorder(_pData, _pUtf32 + bomSize, _len, byteOrder);
_pUtf32 = new char32_t[bomSize + _len + 1]; _pUtf32[_len + bomSize] = char16_t(0);
utf32_strcpy_with_convert_byteorder(_pData, _pUtf32 + bomSize, _len, byteOrder);
_pUtf32[_len + bomSize] = char16_t(0); if(addBom)
{
if(addBom) memcpy(_pUtf32, (byteOrder == BYTE_ORDER_LITTLE_ENDIAN ? g_utf32le_bom : g_utf32be_bom), sizeof(g_utf32le_bom));
{ }
memcpy(_pUtf32, (byteOrder == BYTE_ORDER_LITTLE_ENDIAN ? g_utf32le_bom : g_utf32be_bom), sizeof(g_utf32le_bom));
}
}
return _pUtf32; return _pUtf32;
} }
@ -491,7 +483,7 @@ std::size_t ustring::store_to_file( const ustring& fileName, encoding enc ) cons
{ {
case ustring::UTF8: case ustring::UTF8:
data = utf8_str(); data = utf8_str();
dataSize = ucs4_get_utf8_str_bytes(_pData); dataSize = ucs4_get_utf8_str_bytes(_pData, false);
file.write(utf8Bom, sizeof(utf8Bom)); file.write(utf8Bom, sizeof(utf8Bom));
break; break;
case ustring::UTF16LE: case ustring::UTF16LE:

View File

@ -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 src Указатель на буфер с UCS-4 строкой
* @param dst Указатель на буфер для UTF-8 строки * @param dst Указатель на буфер для UTF-8 строки
* @param symbols Количество символов в строке * @param symbols Количество символов в строке
* @param needEscape Определяет, нужно ли экранировать не-ASCII символы
* @throw bad_conversion Генерируется в случае ошибки кодирования (хотя бы один символ не входит * @throw bad_conversion Генерируется в случае ошибки кодирования (хотя бы один символ не входит
* в набор символов юникода (UCS-4)) * в набор символов юникода (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; std::size_t sBits, totalBytes;
uint32_t s; 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]); 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<uint32_t>(src[i]); s = static_cast<uint32_t>(src[i]);
if(totalBytes == 1) if(totalBytes == 1)
@ -215,6 +216,26 @@ void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols)
++dst; ++dst;
continue; 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; std::size_t pos = 0, shiftLeft, shiftRight;
for(std::size_t n = totalBytes; n > 1; --n) 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 * Возвращает количество байт, которые займет строка, будучи кодированной в UTF-8
* @param str Исходная строка в UCS-4 * @param str Исходная строка в UCS-4
* @param needEscape Определяет, нужно ли экранировать не-ASCII символы
* @return Количество байт, необходимые для UTF-8 представления строки * @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; std::size_t bytes = 0;
while(*str) while(*str)
{ {
bytes += ucs4_get_utf8_symbol_size(*str); bytes += ucs4_get_utf8_symbol_size(*str, needEscape);
++str; ++str;
} }
@ -323,15 +345,28 @@ std::size_t utf8_first_byte_mask(std::size_t symbolSize)
/** /**
* Вычисляет, какой размер будет у символа, если кодировать его в UTF-8 * Вычисляет, какой размер будет у символа, если кодировать его в UTF-8
* @param symbol Исходный символ в UCS-4 * @param symbol Исходный символ в UCS-4
* @param needEscape Определяет, нужно ли экранировать не-ASCII символы
* @return Размер UTF-8 символа в байтах * @return Размер UTF-8 символа в байтах
* @throw bad_conversion Генерируется если символ не входит в набор символов юникода (UCS-4) * @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) if(symbol > MAX_UNICODE_CHARACTER_CODE)
{ {
throw bad_conversion("error converting UCS-4 to UTF-8", symbol); 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 sBits = significant_bits(symbol);
std::size_t sixBitBytes = sBits / 6; std::size_t sixBitBytes = sBits / 6;

View File

@ -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, 0x3d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x01, 0xd6, 0xfb, 0x00, 0x00, 0x00, 0xb2,
0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00 }; 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; const std::size_t helloStrLen = 33;
@ -103,9 +106,9 @@ TEST(Convert, utf8_symbol_size)
TEST(Convert, utf8_symbol_size_by_ucs4_symbol) TEST(Convert, utf8_symbol_size_by_ucs4_symbol)
{ {
ASSERT_EQ(1, ucs4_get_utf8_symbol_size(*(const char32_t*)&helloStrUcs4[0])); 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])); 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])); 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) 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) 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); std::size_t symbols = utf32_str_len((const char32_t*)helloStrUcs4);
char* data = new char[len]; 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)); EXPECT_EQ(0, memcmp(helloStrUtf8, data, len));
delete[] data; delete[] data;
} }
@ -319,7 +322,7 @@ TEST(ConvertError, utf16_decode_error2)
TEST(ConvertError, ucs4_wrong_symbol) 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); 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); ustring str((const char32_t*)helloStrUcs4);
EXPECT_EQ(0, memcmp(helloStrUtf8, str.utf8_str(), sizeof(helloStrUtf8))); EXPECT_EQ(0, memcmp(helloStrUtf8, str.utf8_str(), sizeof(helloStrUtf8)));
EXPECT_EQ(0, memcmp(str.utf8_str(false, true), helloStrEscaped, sizeof(helloStrEscaped)));
} }
// //