Добавил возможность экранировать не-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::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;

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 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);

View File

@ -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
{
if(!_pUtf8)
const char* ustring::utf8_str(bool addBom /*= false*/ , bool needEscape /*= false*/) const
{
std::size_t bomSize = (addBom ? sizeof(g_utf8_bom) : 0);
std::size_t len = ucs4_get_utf8_str_bytes(_pData);
std::size_t len = ucs4_get_utf8_str_bytes(_pData, needEscape);
_pUtf8 = new char[bomSize + len + 1];
ucs4_to_utf8(_pData, _pUtf8 + bomSize, length());
ucs4_to_utf8(_pData, _pUtf8 + bomSize, length(), needEscape);
_pUtf8[len + bomSize] = char(0);
if(addBom)
{
memcpy(_pUtf8, g_utf8_bom, bomSize);
}
}
return _pUtf8;
}
@ -179,8 +177,6 @@ const char* ustring::utf8_str( bool addBom /*= false*/ ) const
* @return Указатель на буфер со строкой, закодированной в UTF-16
*/
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;
@ -192,7 +188,6 @@ const char16_t* ustring::utf16_str( bool addBom /*= false*/, byte_order byteOrde
{
memcpy(_pUtf16, (byteOrder == BYTE_ORDER_LITTLE_ENDIAN ? g_utf16le_bom : g_utf16be_bom), sizeof(g_utf16le_bom));
}
}
return _pUtf16;
}
@ -203,8 +198,6 @@ const char16_t* ustring::utf16_str( bool addBom /*= false*/, byte_order byteOrde
* @return Указатель на буфер со строкой, закодированной в UTF-32
*/
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];
@ -215,7 +208,6 @@ const char32_t* ustring::utf32_str( bool addBom /*= false*/, byte_order byteOrde
{
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:

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 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<uint32_t>(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,16 +345,29 @@ 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;
std::size_t restBits = 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, 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)));
}
//