From 9edaae6000e4bc9d096752d5d8b26d79dc7ec370 Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Mon, 10 Jun 2013 21:57:42 +0400 Subject: [PATCH] Initial uploading sources --- CMakeLists.txt | 20 ++ include/ustring.h | 84 ++++++ include/utf.h | 79 ++++++ src/CMakeLists.txt | 19 ++ src/ustring.cpp | 679 ++++++++++++++++++++++++++++++++++++++++++++ src/utf.cpp | 609 +++++++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 50 ++++ test/main.cpp | 554 ++++++++++++++++++++++++++++++++++++ 8 files changed, 2094 insertions(+) create mode 100755 CMakeLists.txt create mode 100755 include/ustring.h create mode 100755 include/utf.h create mode 100755 src/CMakeLists.txt create mode 100755 src/ustring.cpp create mode 100755 src/utf.cpp create mode 100755 test/CMakeLists.txt create mode 100755 test/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..ed213a2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 2.8) + +set(CMAKE_BUILD_TYPE DEBUG) + +if(WIN32) + set(CMAKE_CXX_FLAGS " /D _VARIADIC_MAX=10") +else() + set(CMAKE_CXX_FLAGS "-std=c++11") + set(CMAKE_CXX_FLAGS_DEBUG "-O0") + set(CMAKE_CXX_FLAGS_RELEASE "-O2") +endif() + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) +set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/lib) +set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/usr) + +include_directories(include) + +add_subdirectory(src) +add_subdirectory(test) diff --git a/include/ustring.h b/include/ustring.h new file mode 100755 index 0000000..102d39c --- /dev/null +++ b/include/ustring.h @@ -0,0 +1,84 @@ +#ifndef USTRING_H +#define USTRING_H + +#include "utf.h" +#include +#include + +/** + * Класс строк, работающий с некоторыми кодировками юникода (UTF-8, UTF-16, UTF-32) + * Хранит данные внутри себя в UCS-4 и инкапсулирует в себе кодирование/декодирование + * указанных выше кодировок. + */ +class ustring +{ +private: + std::size_t _len; + char32_t* _pData; + +private: + mutable std::size_t _capacity; + mutable char* _pUtf8; + mutable char16_t* _pUtf16; + mutable char32_t* _pUtf32; + +private: + inline void clear_internal_buffers() const; + inline void init_from_utf8_str(const char* str); + 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); + +public: + static const std::size_t npos = -1; + +public: + enum encoding { UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE }; + +public: + ustring(); + ustring(const ustring& str); + ustring(ustring&& str); + ustring(const char* str); + 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::wstring& str); + explicit ustring(std::size_t n); + + operator std::string() const; + operator std::wstring() const; + + const char* utf8_str(bool addBom = 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; + + std::size_t length() const; + std::size_t load_from_file(const ustring& fileName, encoding enc); + std::size_t store_to_file(const ustring& fileName, encoding enc) const; + + ustring& operator=(const ustring& str); + ustring& operator=(ustring&& str); + ustring& operator+=(const ustring& str); + ustring& operator+(const ustring& str); + bool operator==(const ustring& str) const; + bool operator!=(const ustring& str) const; + char32_t& operator[](std::size_t index); + + ustring& assign(const char32_t* str, std::size_t n); + ustring& erase(std::size_t pos = 0, std::size_t len = npos); + ustring& insert(std::size_t pos, const ustring& str); + ustring& replace(const ustring& what, const ustring& by); + void clear(); + bool empty() const; + + std::size_t find(char32_t symbol, std::size_t pos = 0) const; + std::size_t find(const ustring& str, std::size_t pos = 0) const; + ustring substr(std::size_t pos = 0, std::size_t len = npos) const; + + friend std::ostream& operator<<(std::ostream& os, const ustring& ustr); + friend std::istream& operator>>(std::istream& is, ustring& ustr); + + ~ustring(); +}; + +#endif // USTRING_H diff --git a/include/utf.h b/include/utf.h new file mode 100755 index 0000000..d8a7835 --- /dev/null +++ b/include/utf.h @@ -0,0 +1,79 @@ +#ifndef UTF_H +#define UTF_H + +#include +#include +#include +#include + +/** + * @brief Класс исключения неправильной перекодировки + * @detailed Как правило исключение этого типа означает, что исходная строка + * не соответствует указанной кодировке + */ +class bad_conversion: public std::exception +{ +private: + std::string _msg; + char32_t _badSymbol; + +public: + bad_conversion(const char* msg, char32_t badData): _badSymbol(badData) + { + std::stringstream errStream; + errStream << msg << " bad symbol: " << _badSymbol; + _msg = errStream.str(); + } + + virtual ~bad_conversion() throw() {} + + virtual const char* what() const throw() + { + return _msg.c_str(); + } +}; + +#define UTF8_BOM_SIZE 3 +#define UTF16_BOM_SIZE 2 +#define UTF32_BOM_SIZE 4 + +extern const unsigned char g_utf8_bom[UTF8_BOM_SIZE]; +extern const unsigned char g_utf16le_bom[UTF16_BOM_SIZE]; +extern const unsigned char g_utf16be_bom[UTF16_BOM_SIZE]; +extern const unsigned char g_utf32le_bom[UTF32_BOM_SIZE]; +extern const unsigned char g_utf32be_bom[UTF32_BOM_SIZE]; + +enum byte_order +{ + BYTE_ORDER_LITTLE_ENDIAN, + BYTE_ORDER_BIG_ENDIAN +}; + +byte_order current_byte_order(); +inline char16_t invert_byte_order_16(char16_t val); +inline char32_t invert_byte_order_32(char32_t val); + +std::size_t utf8_first_byte_mask(std::size_t symbolSize); +const char* utf8_skip_bom(const char* str); +const char16_t* utf16_skip_bom(const char16_t* str); +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 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 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); + +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_utf16_symbol_size(const char32_t symbol); +std::size_t ucs4_get_utf16_str_bytes(const char32_t* str); + +#endif // UTF_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100755 index 0000000..652ddb2 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8) + +set(MAIN_PROJECT_NAME "cpputil") +set(SRC_DIR ".") +set(INCLUDE_DIR "../include") + +project(${MAIN_PROJECT_NAME} CXX) +aux_source_directory(${SRC_DIR} SRC_FILES) +include_directories(${INCLUDE_DIR}) +add_library(${MAIN_PROJECT_NAME} STATIC ${SRC_FILES}) +target_link_libraries(${MAIN_PROJECT_NAME}) + +install(TARGETS ${MAIN_PROJECT_NAME} + RUNTIME DESTINATION bin + ARCHIVE DESTINATION lib) + +install(DIRECTORY ${INCLUDE_DIR}/ + DESTINATION include/${PROJECT_NAME} + FILES_MATCHING PATTERN "*.h") diff --git a/src/ustring.cpp b/src/ustring.cpp new file mode 100755 index 0000000..d0f1b82 --- /dev/null +++ b/src/ustring.cpp @@ -0,0 +1,679 @@ +#include "ustring.h" +#include +#include + +/** + * Конструктор по умолчанию + */ +ustring::ustring(): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +{ + _pData = new char32_t(0); + _len = 0; + _capacity = 0; +} + +/** + * Копирующий конструктор + * @param str Исходная строка + */ +ustring::ustring(const ustring& str): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +{ + std::cout << "ustring copy constructor" << std::endl; + _len = str.length(); + _capacity = _len; + _pData = new char32_t[_len + 1]; + std::memcpy(_pData, str._pData, _len*sizeof(char32_t)); + _pData[_len] = char32_t(0); +} + +/** + * Конструктор перемещения + * @param str Исходная строка + * @detailed Данные перемещаются (не копируются!) из исходной строки + */ +ustring::ustring(ustring&& str): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +{ + std::cout << "ustring move constructor" << std::endl; + _pData = std::move(str._pData); + _len = str._len; + _capacity = _len; + str._pData = nullptr; + str._len = 0; +} + +/** + * Конструктор из UTF-8 строки + * @param str Исходная строка в UTF-8 (null terminated) + * @detailed Так как символы ASCII являются подмножеством UTF-8, при создании строки + * из массива ASCII символов нужно применять именно этот конструктор + */ +ustring::ustring(const char* str): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +{ + init_from_utf8_str(str); +} + +/** + * Конструктор из UTF-16 строки + * @param str Исходная строка в UTF-16 (null terminated) + * @param byteOrder Порядок байт + */ +ustring::ustring(const char16_t* str, byte_order byteOrder /* = BYTE_ORDER_LITTLE_ENDIAN */): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +{ + init_from_utf16_str(str, byteOrder); +} + +/** + * Конструктор из UTF-32 строки + * @param str Исходная строка в UTF-32 (null terminated) + * @param byteOrder Порядок байт + */ +ustring::ustring(const char32_t* str, byte_order byteOrder /* = BYTE_ORDER_LITTLE_ENDIAN */): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +{ + init_from_utf32_str(str, byteOrder); +} + +/** + * Конструктор из std::string + * @param str Исходная строка в UTF-8 + */ +ustring::ustring(const std::string& str): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +{ + init_from_utf8_str(str.c_str()); +} + +/** + * Конструктор из std::wstring + * @param str Исходная строка в UTF-16/UTF-32 (в зависимости от платформы) + */ +ustring::ustring(const std::wstring& str): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +{ +#ifdef __GNUC__ + init_from_utf32_str((const char32_t*)str.c_str()); +#else + init_from_utf16_str((const char16_t*)str.c_str()); +#endif +} + +ustring::ustring( std::size_t n ): _pUtf8(nullptr), _pUtf16(nullptr), _pUtf32(nullptr) +{ + _pData = new char32_t[n + 1]; + _len = 0; + _capacity = n; +} + + +void ustring::init_from_utf8_str(const char* str) +{ + const char* strData = utf8_skip_bom(str); + _len = utf8_str_len(strData); + _capacity = _len; + _pData = new char32_t[_len + 1]; + utf8_to_ucs4(strData, _pData, _len); + _pData[_len] = char32_t(0); +} + +inline void ustring::init_from_utf16_str( const char16_t* str, byte_order byteOrder /*= BYTE_ORDER_LITTLE_ENDIAN*/ ) +{ + const char16_t* strData = utf16_skip_bom(str); + _len = utf16_str_len(strData); + _capacity = _len; + _pData = new char32_t[_len + 1]; + utf16_to_ucs4(strData, _pData, _len, byteOrder); + _pData[_len] = char32_t(0); +} + +inline void ustring::init_from_utf32_str( const char32_t* str, byte_order byteOrder /*= BYTE_ORDER_LITTLE_ENDIAN*/ ) +{ + const char32_t* strData = utf32_skip_bom(str); + _len = utf32_str_len(strData); + _capacity = _len; + _pData = new char32_t[_len + 1]; + utf32_strcpy_with_convert_byteorder(str, _pData, _len, byteOrder); + _pData[_len] = char32_t(0); +} + +ustring::operator std::string() const +{ + return utf8_str(); +} + +ustring::operator std::wstring() const +{ +#ifdef __GNUC__ + return (wchar_t*)utf32_str(); +#else + return (wchar_t*)utf16_str(); +#endif +} + +/** + * Возвращает указатель на временный буфер с UTF-8 строкой + * @param addBom Указывает, добавлять ли BOM в возвращаемую строку + * @return Указатель на буфер со строкой, закодированной в UTF-8 + * @detailed Этот буфер является временным и будет высвобожден при любом изменении строки + */ +const char* ustring::utf8_str( bool addBom /*= 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); + } + } + + return _pUtf8; +} + +/** + * @param addBom Указывает, добавлять ли BOM в возвращаемую строку + * @param byteOrder Задает порядок байт в возвращаемой строке + * @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; + _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; +} + +/** + * @param addBom Указывает, добавлять ли BOM в возвращаемую строку + * @param byteOrder Задает порядок байт в возвращаемой строке + * @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]; + 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; +} + +ustring::~ustring() +{ + delete[] _pData; + delete[] _pUtf8; + delete[] _pUtf16; + delete[] _pUtf32; +} + +void ustring::clear_internal_buffers() const +{ + if(_pUtf8) + { + delete[] _pUtf8; + _pUtf8 = nullptr; + } + if(_pUtf16) + { + delete[] _pUtf16; + _pUtf16 = nullptr; + } + if(_pUtf32) + { + delete[] _pUtf32; + _pUtf32 = nullptr; + } +} + +std::ostream& operator<<(std::ostream& os, const ustring& str) +{ + os << str.utf8_str(); + + return os; +} + +std::istream& operator>>(std::istream& is, ustring& ustr) +{ + std::string str; + + is >> str; + ustr = str; + + return is; +} + +/** + * @brief Возвращает длину строки + * @return Длина строки в символах (не вы байтах!) + */ +std::size_t ustring::length() const +{ + return _len; +} + +ustring& ustring::operator=(const ustring& str) +{ + std::cout << "ustring copy assign operator" << std::endl; + + if(this == &str) + { + return *this; + } + + delete _pData; + clear_internal_buffers(); + + _len = str.length(); + _capacity = _len; + _pData = new char32_t[_len + 1]; + std::memcpy(_pData, str._pData, _len*sizeof(char32_t)); + _pData[_len] = char32_t(0); + + return *this; +} + +ustring& ustring::operator=(ustring&& str) +{ + std::cout << "ustring move assign operator" << std::endl; + + if(this == &str) + { + return *this; + } + + delete _pData; + clear_internal_buffers(); + + _pData = std::move(str._pData); + _len = str._len; + _capacity = _len; + str._pData = nullptr; + str._len = 0; + + return *this; +} + +ustring& ustring::operator+=(const ustring& str) +{ + clear_internal_buffers(); + + std::size_t curLength = length(), strLength = str.length(); + std::size_t totalLength = curLength + strLength; + + char32_t* pBuffer = new char32_t[totalLength + 1]; + std::memcpy(pBuffer, _pData, curLength*sizeof(char32_t)); + std::memcpy(pBuffer + curLength, str._pData, strLength*sizeof(char32_t)); + pBuffer[totalLength] = char32_t(0); + delete _pData; + _pData = pBuffer; + _len = totalLength; + return *this; +} + + +ustring& ustring::operator+(const ustring& str) +{ + return (*this += str); +} + +char32_t& ustring::operator[](std::size_t index) +{ + return _pData[index]; +} + +bool ustring::operator==( const ustring& str ) const +{ + return ( (_len == str._len) && (!memcmp(_pData, str._pData, _len*sizeof(char32_t))) ); +} + +bool ustring::operator!=( const ustring& str ) const +{ + return !(*this == str); +} + +/** + * Поиск символа в строке + * @param symbol Символ, который требуется найти + * @param pos Начальная позиция, с которой начинается поиск + * @return индекс символа в строке или ustring::npos, если символ не найден + */ +std::size_t ustring::find(char32_t symbol, std::size_t pos /* = 0 */) const +{ + char32_t* pBuf = _pData + pos; + std::size_t result = 0; + + do + { + if(*pBuf == symbol) + { + return (result + pos); + } + ++result; + } + while(*pBuf++ != 0); + + return npos; +} + +/** + * Поиск подстроки в строке + * @param строка, которую требуется найти + * @param pos Начальная позиция, с которой начинается поиск + * @return индекс символа в строке или ustring::npos, если строка не найдена + */ +std::size_t ustring::find(const ustring& str, std::size_t pos /* = 0 */) const +{ + if(str.length() > _len) + { + return npos; + } + + const char32_t* pStr = str._pData; + const std::size_t len = _len - str.length(); + for(std::size_t i = pos; i <= len; ++i) + { + if(_pData[i] == *pStr) + { + if(!memcmp(&_pData[i], pStr, str.length()*sizeof(char32_t))) + { + return i; + } + } + } + + return npos; +} + +/** + * Загружает содержимое текстового файла в строку + * @param fileName Имя файла + * @param enc Кодировка файла + * @return Количество считанных байт + */ +std::size_t ustring::load_from_file( const ustring& fileName, encoding enc ) // TODO: this terrible function needs refactoring +{ + std::ifstream file; + std::size_t symbolTypeSize = 0; + +#ifdef WIN32 + file.open(fileName.utf16_str(), std::ios::binary); +#else + file.open(fileName.utf8_str(), std::ios::binary); +#endif + + // get file size + file.seekg(0, std::ios::end); + std::size_t fileLength = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + + switch(enc) + { + case ustring::UTF8: + symbolTypeSize = sizeof(char); + break; + case ustring::UTF16LE: + symbolTypeSize = sizeof(char16_t); + break; + case ustring::UTF32LE: + symbolTypeSize = sizeof(char32_t); + break; + default: + break; + } + + char* fileData = new char[fileLength + symbolTypeSize]; + file.read(fileData, fileLength); + std::memset(fileData + fileLength, 0, symbolTypeSize); + + switch(enc) + { + case ustring::UTF8: + *this = ustring(fileData); + break; + case ustring::UTF16LE: + *this = ustring(reinterpret_cast(fileData)); + break; + case ustring::UTF32LE: + *this = ustring(reinterpret_cast(fileData)); + break; + default: + break; + } + + return fileLength; +} + +/** + * Сохраняет содержимое строки в текстовый файл + * @param fileName Имя файла + * @param enc Кодировка файла + * @return Количество записанных байт + */ +std::size_t ustring::store_to_file( const ustring& fileName, encoding enc ) const // TODO: this terrible function needs refactoring +{ + std::ofstream file; + const char* data = nullptr; + std::size_t dataSize = 0; + + const char utf16leBom[] = { char(0xFF), char(0xFE) }; + const char utf8Bom[] = { char(0xEF), char(0xBB), char(0xBF) }; + const char utf32leBom[] = { char(0xFF), char(0xFE), char(0x00), char(0x00)}; + +#ifdef WIN32 + file.open(fileName.utf16_str(), std::ios::binary); +#else + file.open(fileName.utf8_str(), std::ios::binary); +#endif + + switch(enc) + { + case ustring::UTF8: + data = utf8_str(); + dataSize = ucs4_get_utf8_str_bytes(_pData); + file.write(utf8Bom, sizeof(utf8Bom)); + break; + case ustring::UTF16LE: + data = reinterpret_cast(utf16_str()); + dataSize = ucs4_get_utf16_str_bytes(_pData); + file.write(utf16leBom, sizeof(utf16leBom)); + break; + case ustring::UTF32LE: + data = reinterpret_cast(utf32_str()); + dataSize = utf32_str_len(utf32_str())*sizeof(char32_t); + file.write(utf32leBom, sizeof(utf32leBom)); + break; + default: + break; + } + + file.write(data, dataSize); + + return dataSize; +} + +/** + * Очищает строку + */ +void ustring::clear() +{ + *_pData = char32_t(0); + _len = 0; + clear_internal_buffers(); +} + +/** + * Проверяет строку на пустоту + * @return true, если строка имеет ненулевую длину, иначе false + */ +bool ustring::empty() const +{ + return (_len == 0 ? true : false); +} + +/** + * Выделяет подстроку из строки + * @param pos Позиция требуемой подстроки + * @param len Длина подстроки в символах + * @return Возвращает искомую подстроку + */ +ustring ustring::substr( size_t pos /*= 0*/, size_t len /*= npos*/ ) const +{ + std::size_t bufLength = (len == npos ? (_len - pos) : len); + ustring retVal(bufLength); + + retVal.assign(_pData + pos, bufLength); + return retVal; +} + +/** + * Формирует строку из массива символов заданного размера + * @param str Массив символов UCS-4 + * @param n Количество символов + * @return *this + */ +ustring& ustring::assign( const char32_t* str, std::size_t n ) +{ + clear_internal_buffers(); + + if(n == 0) + { + clear(); + return *this; + } + + if(n > _capacity) + { + delete[] _pData; + _pData = new char32_t[n + 1]; + _capacity = n; + } + + memcpy(_pData, str, n*sizeof(char32_t)); + _pData[n] = char32_t(0); + _len = n; + + return *this; +} + +/** + * Вырезает часть строки + * @param pos Начало вырезаемой части строки + * @param len Длина вырезаемой части строки + * @return *this + */ +ustring& ustring::erase( std::size_t pos /*= 0*/, std::size_t len /*= npos*/ ) +{ + if((pos >= _len) || (len == 0)) + { + return *this; + } + + std::size_t bufLength = (len == npos ? (_len - pos) : len); + + if(len == npos || (pos + len == _len)) + { + _pData[pos] = char32_t(0); + _len = pos; + } + else + { + memmove(_pData + pos, _pData + pos + bufLength, (_len - pos - len)*sizeof(char32_t)); + _pData[_len - bufLength] = char32_t(0); + _len -= bufLength; + } + + return *this; +} + +/** + * Вставляет строку в произвольное место исходной строки + * @param pos Позиция для вставки строки + * @param str Вставляемая строка + * @return *this + */ +ustring& ustring::insert( std::size_t pos, const ustring& str ) +{ + std::size_t destLength = _len + str.length(); + clear_internal_buffers(); + + if(pos > _len) + { + pos = _len; + } + + if(_capacity < destLength) + { + const char32_t* oldData = _pData; + _pData = new char32_t[destLength + 1]; + _capacity = destLength; + memcpy(_pData, oldData, pos*sizeof(char32_t)); + memcpy(_pData + pos, str._pData, str.length()*sizeof(char32_t)); + memcpy(_pData + pos + str.length(), oldData + pos, (_len - pos)*sizeof(char32_t)); + delete[] oldData; + } + else + { + memmove(_pData + pos + str.length(), _pData + pos, (_len - pos)*sizeof(char32_t)); + memcpy(_pData + pos, str._pData, str.length()*sizeof(char32_t)); + } + + _len = destLength; + return *this; +} + +/** + * Заменяет все вхождения подстроки "what" на строку "by" + * @param what Подстрока, которую нужно заменить + * @param by Строка, которая замещает исходную + * @return *this + */ +ustring& ustring::replace( const ustring& what, const ustring& by ) +{ + std::ptrdiff_t diff = by.length() - what.length(); + std::size_t pos = 0, newLength = _len; + clear_internal_buffers(); + + while((pos = find(what, pos)) != npos) + { + if((diff > 0) && (_len + diff > _capacity)) + { + newLength = _len + diff*5; + const char32_t* oldData = _pData; + _pData = new char32_t[newLength + 1]; + _capacity = newLength; + memcpy(_pData, oldData, pos*sizeof(char32_t)); + memcpy(_pData + pos, by._pData, by.length()*sizeof(char32_t)); + memcpy(_pData + pos + by.length(), oldData + pos + what.length(), (_len - pos - what.length())*sizeof(char32_t)); + delete[] oldData; + } + else + { + memmove(_pData + pos + by.length(), _pData + pos + what.length(), (_len - pos - what.length())*sizeof(char32_t)); + memcpy(_pData + pos, by._pData, by.length()*sizeof(char32_t)); + } + + _len += diff; + } + + return *this; +} diff --git a/src/utf.cpp b/src/utf.cpp new file mode 100755 index 0000000..f2e2830 --- /dev/null +++ b/src/utf.cpp @@ -0,0 +1,609 @@ +#include "utf.h" +#include + +#ifdef __GNUC__ +#include +#endif + +/** + * Максимальный размер символа UTF-8 в байтах + */ +#define UTF8_MAX_SYMBOL_SIZE 4 + +/** + * Количество бит в символе UCS-4 + */ +#define UCS4_SYMBOL_BITS 32 + +/** + * Маска для выделения бит, определяющих суррогатную пару + */ +#define UTF16_SP_MASK 0xFC00 + +/** + * Маска для установки первого слова суррогатной пары + */ +#define UTF16_SP_LEADING_WORD 0xD800 + +/** + * Маска для установки второго слова суррогатной пары + */ +#define UTF16_SP_TRAILING_WORD 0xDC00 + +/** + * Начало служебного диапазона символов, используемого для формирования суррогатных пар + */ +#define UTF16_SP_RANGE_BEGIN 0xD800 + +/** + * Конец служебного диапазона символов, используемого для формирования суррогатных пар + */ +#define UTF16_SP_RANGE_END 0xDFFF + +/** + * Маска для выделения данных в случае суррогатной пары + */ +#define UTF16_DATA_MASK 0x3FF + +/** + * Максимально возможный код символа юникода + */ +#define MAX_UNICODE_CHARACTER_CODE 0x10FFFF + +/** + * Текущий порядок байт в системе. Определяется в рантайме, так как на железках с bi-endian + * (например ARM) порядок байт не задан жестко, а устанавливается операционной системой + */ +static const byte_order g_system_byte_order = current_byte_order(); + +const unsigned char g_utf8_bom[] = {0xef, 0xbb, 0xbf}; +const unsigned char g_utf16le_bom[] = {0xff, 0xfe}; +const unsigned char g_utf16be_bom[] = {0xfe, 0xff}; +const unsigned char g_utf32le_bom[] = {0xff, 0xfe, 0x00, 0x00}; +const unsigned char g_utf32be_bom[] = {0x00, 0x00, 0xfe, 0xff}; + +/** + * + * @return Порядок байт + */ +byte_order current_byte_order() +{ + union { + uint32_t i; + char c[sizeof(uint32_t)]; + } bint = {0x01020304}; + + return (bint.c[0] == 1 ? BYTE_ORDER_BIG_ENDIAN : BYTE_ORDER_LITTLE_ENDIAN); +} + +/** + * Меняет порядок байт в 16-битном символе + * @param val Исходный символ + * @return Символ с инвертированным порядком байт + */ +char16_t invert_byte_order_16( char16_t val ) +{ +#ifdef __GNUC__ + return bswap_16(val); +#elif defined(_MSC_VER) + return _byteswap_ushort(val); +#else + #error compiler not supported +#endif +} + +/** + * Меняет порядок байт в 32-битном символе + * @param val Исходный символ + * @return Символ с инвертированным порядком байт + */ +char32_t invert_byte_order_32(char32_t val) +{ +#ifdef __GNUC__ + return bswap_32(val); +#elif defined(_MSC_VER) + return _byteswap_ulong(val); +#else + #error compiler not supported +#endif +} + +/** + * Декодирует UTF-8 в UCS-4 + * @param src Указатель на буфер с UTF-8 строкой (без BOM) + * @param dst Указатель на буфер для UCS-4 строки + * @param symbols Количество символов в строке + * @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) +{ + std::size_t bytes = 0; + uint32_t s = 0; + + for(std::size_t i = 0; i < symbols; ++i) + { + bytes = utf8_get_symbol_size(src); + s = 0; + + if(bytes == 1) // ASCII + { + s = *src; + } + else if(bytes > 1 && bytes <= UTF8_MAX_SYMBOL_SIZE) // Unicode + { + std::size_t pos = 0; + for(std::size_t n = bytes; n > 1; --n) + { + if((*(src + n - 1) & 0xC0) != 0x80) + { + throw bad_conversion("error converting UTF-8 to UCS-4", *(char32_t*)src); + } + + s |= (static_cast(static_cast(*(src + n - 1)) & 0x3F) << pos++*6); + } + + s |= (static_cast(static_cast(*src << bytes) >> bytes) << pos*6); + } + else if(bytes == 6 || bytes == 10) // Escaped unicode character + { + if(*src == '\\') + { + if(*(src + 1) == 'u') + { + if(sscanf(src + 2, "%4X", &s) == EOF) + { + throw bad_conversion("error converting UTF-8 to UCS-4", *(char32_t*)src); + } + } + else if(*(src + 1) == 'U') + { + if(sscanf(src + 2, "%8X", &s) == EOF) + { + throw bad_conversion("error converting UTF-8 to UCS-4", *(char32_t*)src); + } + } + else + { + throw bad_conversion("error converting UTF-8 to UCS-4", *(char32_t*)src); + } + } + else + { + throw bad_conversion("error converting UTF-8 to UCS-4", *(char32_t*)src); + } + } + else + { + throw bad_conversion("error converting UTF-8 to UCS-4", *(char32_t*)src); + } + + dst[i] = s; + src += bytes; + } +} + +/** + * Кодирует UCS-4 в UTF-8 + * @param src Указатель на буфер с UCS-4 строкой + * @param dst Указатель на буфер для UTF-8 строки + * @param symbols Количество символов в строке + * @throw bad_conversion Генерируется в случае ошибки кодирования (хотя бы один символ не входит + * в набор символов юникода (UCS-4)) + */ +void ucs4_to_utf8(const char32_t* src, char* dst, std::size_t symbols) +{ + std::size_t sBits, totalBytes; + uint32_t s; + + for(std::size_t i = 0; i < symbols; ++i) + { + if(src[i] > MAX_UNICODE_CHARACTER_CODE) + { + throw bad_conversion("error converting UCS-4 to UTF-8", *src); + } + + sBits = significant_bits(src[i]); + totalBytes = ucs4_get_utf8_symbol_size(src[i]); + s = static_cast(src[i]); + + if(totalBytes == 1) + { + *dst = static_cast(src[i]); + ++dst; + continue; + } + + std::size_t pos = 0, shiftLeft, shiftRight; + for(std::size_t n = totalBytes; n > 1; --n) + { + shiftLeft = UCS4_SYMBOL_BITS - (pos + 1)*6; + shiftRight = pos*6; + ++pos; + + *(dst + n - 1) = static_cast((s << shiftLeft) >> (shiftLeft + shiftRight)) | 0x80; + } + + shiftLeft = UCS4_SYMBOL_BITS - sBits; + shiftRight = pos*6; + *dst = static_cast((s << shiftLeft) >> (shiftLeft + shiftRight)) | utf8_first_byte_mask(totalBytes); + + dst += totalBytes; + } +} + +/** + * Возвращает длину UTF-8 строки + * @param str UTF-8 строка + * @return Длина втроки в символах + */ +std::size_t utf8_str_len(const char* str) +{ + std::size_t len = 0; + + while(*str) + { + str += utf8_get_symbol_size(str); + ++len; + } + + return len; +} + +/** + * Возвращает количество байт, которые займет строка, будучи кодированной в UTF-8 + * @param str Исходная строка в UCS-4 + * @return Количество байт, необходимые для UTF-8 представления строки + */ +std::size_t ucs4_get_utf8_str_bytes(const char32_t* str) +{ + std::size_t bytes = 0; + + while(*str) + { + bytes += ucs4_get_utf8_symbol_size(*str); + ++str; + } + + return bytes; +} + +/** + * Возвращает размер символа UTF-8 строки с учетом экранированных UCS-2 и UCS-4 символов + * @param str Указатель на требуемый символ в UTF-8 строке + * @return Количество байт, занимаемых символом + */ +std::size_t utf8_get_symbol_size(const char* str) +{ + uint32_t s = 0; + if(*str == '\\') // UCS unescape + { + if(*(str + 1) == 'u') + { + return 6; + } + else if(*(str + 1) == 'U') + { + return 10; + } + } + + if(*str > 0) return 1; + else if((*str & 0xC0) && !(*str & 0x20)) return 2; + else if((*str & 0xE0) && !(*str & 0x10)) return 3; + else if((*str & 0xF0) && !(*str & 0x8)) return 4; + else throw bad_conversion("cannot calc size of UTF-8 symbol", *(char32_t*)str); +} + +/** + * Возвращает маску для установки старших бит первого байта UTF-8 символа + * Используется для кодирования в UTF-8 + * @param symbolSize Размер символа в байтах + * @return Маска для старших бит первого байта символа + */ +std::size_t utf8_first_byte_mask(std::size_t symbolSize) +{ + switch(symbolSize) + { + case 2: return 0xC0; + case 3: return 0xE0; + case 4: return 0xF0; + case 5: return 0xF8; + case 6: return 0xE0; + default: return 0; + } + + return 0; +} + +/** + * Вычисляет, какой размер будет у символа, если кодировать его в UTF-8 + * @param symbol Исходный символ в UCS-4 + * @return Размер UTF-8 символа в байтах + * @throw bad_conversion Генерируется если символ не входит в набор символов юникода (UCS-4) + */ +std::size_t ucs4_get_utf8_symbol_size(const char32_t symbol) +{ + if(symbol > MAX_UNICODE_CHARACTER_CODE) + { + throw bad_conversion("error converting UCS-4 to UTF-8", symbol); + } + + std::size_t sBits = significant_bits(symbol); + std::size_t sixBitBytes = sBits / 6; + std::size_t restBits = sBits % 6; + uint32_t s = static_cast(symbol); + + if(sBits <= 7) + { + return 1; + } + + std::size_t totalBytes; + if(restBits <= (6 - sixBitBytes)) + { + totalBytes = sixBitBytes + 1; + } + else + { + totalBytes = sixBitBytes + 2; + } + + return totalBytes; +} + +/** + * Вычисляет количество значимых бит в 32-х битном числе (количество бит, минимально необходимых + * для представления числа). Например в числе 18 - 5 значимых бит (10010) + * @param v Исходное число + * @return Количество значимых бит + */ +std::size_t significant_bits(uint32_t v) +{ + const std::size_t lut[32] = {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; + + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return lut[(unsigned int)(v * 0x07C4ACDDU) >> 27] + 1; +} + +/** + * Декодирует UTF-16 в UCS-4 + * @param src Исходная строка в UTF-16 без BOM + * @param dst Указатель на буфер для строки в UCS-4 + * @param symbols Количество декодируемых символов + * @param byteOrder Порядок байт в строке + * @throw bad_conversion Генерируется в случае ошибки декодирования (строка на входе закодирована не в UTF-16) + */ +void utf16_to_ucs4( const char16_t* src, char32_t* dst, std::size_t symbols, byte_order byteOrder /*= BYTE_ORDER_LITTLE_ENDIAN*/ ) +{ + char16_t leadingWord = 0, trailingWord = 0; + + for(std::size_t i = 0; i < symbols; ++i) + { + leadingWord = (byteOrder == g_system_byte_order ? *src : invert_byte_order_16(*src)); + if((leadingWord < UTF16_SP_RANGE_BEGIN) || (leadingWord > UTF16_SP_RANGE_END)) + { + dst[i] = static_cast(leadingWord); + ++src; + } + else if((leadingWord & UTF16_SP_MASK) == UTF16_SP_LEADING_WORD) // surrogate pair + { + trailingWord = (byteOrder == g_system_byte_order ? *(src + 1) : invert_byte_order_16(*(src + 1))); + if((trailingWord & UTF16_SP_MASK) != UTF16_SP_TRAILING_WORD) + { + throw bad_conversion("error converting UTF-16LE to UCS-4", *(char32_t*)src); + } + + dst[i] = (((static_cast(leadingWord) & UTF16_DATA_MASK) << 10) | (static_cast(trailingWord) & UTF16_DATA_MASK)) + 0x10000; + src += 2; + } + else + { + throw bad_conversion("error converting UTF-16 to UCS-4", *(char32_t*)src); + } + } +} + +/** + * Кодирует UCS-4 в UTF-16 + * @param src Исходная строка в UCS-4 + * @param dst Указатель на буфер для UTF-16 строки + * @param symbols Количество кодируемых символов + * @param byteOrder Порядок байт в строке + * @throw bad_conversion Генерируется в случае ошибки кодирования (хотя бы один символ + * строки на входе не входит в диапазон UCS-4) + */ +void ucs4_to_utf16( const char32_t* src, char16_t* dst, std::size_t symbols, byte_order byteOrder /*= BYTE_ORDER_LITTLE_ENDIAN*/ ) +{ + for(std::size_t i = 0; i < symbols; ++i) + { + if(src[i] > MAX_UNICODE_CHARACTER_CODE) + { + throw bad_conversion("error converting UCS-4 to UTF-16", *src); + } + else if(src[i] < 0x10000) + { + *dst = static_cast(src[i]); + if(byteOrder != g_system_byte_order) + { + *dst = invert_byte_order_16(*dst); + } + ++dst; + } + else // construct surrogate pair + { + *(dst) = static_cast(((src[i] - 0x10000) >> 10) | UTF16_SP_LEADING_WORD); + *(dst + 1) = static_cast(((src[i] - 0x10000) & UTF16_DATA_MASK) | UTF16_SP_TRAILING_WORD); + if(byteOrder != g_system_byte_order) + { + *dst = invert_byte_order_16(*dst); + *(dst + 1) = invert_byte_order_16(*(dst + 1)); + } + dst += 2; + } + } +} + +/** + * Возвращает длину UTF-16 строки + * @param str Строка, длину которой нужно посчитать (null terminated) + * @param byteOrder Порядок байт в строке + * @return Длина строки в символах + */ +std::size_t utf16_str_len( const char16_t* str, byte_order byteOrder /*= BYTE_ORDER_LITTLE_ENDIAN*/ ) +{ + std::size_t len = 0; + + while(*str) + { + str += utf16_get_symbol_size(str, byteOrder)/2; + ++len; + } + + return len; +} + +/** + * Возвращает размер символа в UTF-16 строке + * @param str Указатель на начало символа в строке + * @param byteOrder Порядок байт в строке + * @return Размер символа в байтах + */ +std::size_t utf16_get_symbol_size( const char16_t* str, byte_order byteOrder /*= BYTE_ORDER_LITTLE_ENDIAN*/ ) +{ + char16_t symbol = (byteOrder == g_system_byte_order ? *str : invert_byte_order_16(*str)); + return ((symbol < UTF16_SP_RANGE_BEGIN) || (symbol > UTF16_SP_RANGE_END)) ? 2 : 4; +} + +/** + * Рассчитывает, сколько байт займет UCS-4 символ, будучи закодированным в UTF-16 + * @param symbol Исходный UCS-4 символ + * @return Размер символа в UTF-16 (в байтах) + * @throw bad_conversion Генерируется если символ не входит в диапазон UCS-4 + */ +std::size_t ucs4_get_utf16_symbol_size( const char32_t symbol ) +{ + if(symbol > MAX_UNICODE_CHARACTER_CODE) + { + throw bad_conversion("error converting UCS-4 to UTF-16", symbol); + } + + return (symbol < 0x10000) ? 2 : 4; +} + +/** + * Рассчитывает, сколько байт займет UCS-4 строка, будучи закодированной в UTF-16 + * @param str Исходная строка в UCS-4 + * @return Размер строки в UTF-16 + */ +std::size_t ucs4_get_utf16_str_bytes( const char32_t* str ) +{ + std::size_t bytes = 0; + + while(*str) + { + bytes += ucs4_get_utf16_symbol_size(*str); + ++str; + } + + return bytes; +} + +/** + * Пропускает метку BOM в UTF-8 строке + * @param str Исходная строка в UTF-8 + * @return Указатель на начало данных в строке + */ +const char* utf8_skip_bom( const char* str ) +{ + if(!memcmp(str, g_utf8_bom, sizeof(g_utf8_bom))) + { + return str + 3; + } + else + { + return str; + } +} + +/** + * Пропускает метку BOM в UTF-16 строке + * @param str Исходная строка в UTF-16 + * @return Указатель на начало данных в строке + */ +const char16_t* utf16_skip_bom( const char16_t* str ) +{ + if(!memcmp(str, g_utf16le_bom, sizeof(g_utf16le_bom)) || !memcmp(str, g_utf16be_bom, sizeof(g_utf16be_bom))) + { + return str + 1; + } + else + { + return str; + } +} + +/** + * Считает длину UTF-32 строки + * @param str Исходная UTF-32 строка (null terminated) + * @return Длина строки в символах + */ +std::size_t utf32_str_len( const char32_t* str ) +{ + std::size_t len = 0; + + while(*str) + { + ++str; + ++len; + } + + return len; +} + +/** + * Пропускает метку BOM в UTF-32 строке + * @param str Исходная строка в UTF-32 + * @return Указатель на начало данных в строке + */ +const char32_t* utf32_skip_bom( const char32_t* str) +{ + if(!memcmp(str, g_utf32le_bom, sizeof(g_utf32le_bom)) || !memcmp(str, g_utf32be_bom, sizeof(g_utf32be_bom))) + { + return str + 1; + } + else + { + return str; + } +} + +/** + * Копирует массив символов UTF-32, если byteOrder не сопадает с текущим порядком байт, + * инвертирует порядок байт в каждом символе + * @param src Исходная строка + * @param dst Строка назначения + * @param num Количество символов + * @param byteOrder Порядок байт + */ +void utf32_strcpy_with_convert_byteorder(const char32_t* src, char32_t* dst, std::size_t num, byte_order byteOrder /* = BYTE_ORDER_LITTLE_ENDIAN */) +{ + if(g_system_byte_order == byteOrder) + { + memcpy(dst, src, num*sizeof(char32_t)); + } + else + { + for(std::size_t i = 0; i < num; ++i) + { + dst[i] = invert_byte_order_32(src[i]); + } + } +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100755 index 0000000..6432932 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 2.8) + +set(TEST_PROJECT_NAME "cpputil_test") +set(SRC_DIR ".") +set(REQUIRED_LIBRARIES cpputil) + +if(WIN32) + INCLUDE(ExternalProject) + SET_DIRECTORY_PROPERTIES(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/ThirdParty) + + ExternalProject_Add( + googletest + SVN_REPOSITORY http://googletest.googlecode.com/svn/trunk/ + TIMEOUT 100 + CMAKE_ARGS -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=Debug + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=Release + -Dgtest_force_shared_crt=ON + -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON) + + ExternalProject_Get_Property(googletest source_dir) + ExternalProject_Get_Property(googletest binary_dir) + INCLUDE_DIRECTORIES(${source_dir}/include) + #link_directories(${binary_dir}/Debug) +else() + FIND_PACKAGE(GTest REQUIRED) + if(NOT GTEST_FOUND) + message(SEND_ERROR "Failed to find Google Test Framework") + return() + else() + include_directories(${GTEST_INCLUDE_DIRS}) + endif() +endif() + +project(${TEST_PROJECT_NAME} CXX) +aux_source_directory(${SRC_DIR} TEST_SRC) +add_executable(${TEST_PROJECT_NAME} ${TEST_SRC}) +SET_TARGET_PROPERTIES(${TEST_PROJECT_NAME} PROPERTIES ENABLE_EXPORTS TRUE) +ADD_DEPENDENCIES(${TEST_PROJECT_NAME} googletest) +target_link_libraries(${TEST_PROJECT_NAME} ${REQUIRED_LIBRARIES} + debug ${binary_dir}/Debug/gtest.lib + optimized ${binary_dir}/Release/gtest.lib) + +install(TARGETS ${TEST_PROJECT_NAME} + RUNTIME DESTINATION bin + ARCHIVE DESTINATION lib) diff --git a/test/main.cpp b/test/main.cpp new file mode 100755 index 0000000..dba63b3 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,554 @@ +#include "ustring.h" +#include "utf.h" +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Данные для тестов +// + +// UTF-8 +const unsigned char helloStrUtf8[] = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0xd0, 0x9f, 0xd1, + 0x80, 0xd0, 0xb8, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x82, 0x2c, 0x20, 0xd0, 0x9c, 0xd0, 0xb8, 0xd1, + 0x80, 0x21, 0x0a, 0xe2, 0x88, 0x86, 0x20, 0x3d, 0x20, 0xf0, 0x9d, 0x9b, 0xbb, 0xc2, 0xb2, 0x0a, + 0x00 }; + +// UTF-16LE +const unsigned char helloStrUtf16le[] = { 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x57, 0x00, 0x6f, 0x00, + 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, 0x0a, 0x00, 0x1f, 0x04, 0x40, 0x04, 0x38, 0x04, + 0x32, 0x04, 0x35, 0x04, 0x42, 0x04, 0x2c, 0x00, 0x20, 0x00, 0x1c, 0x04, 0x38, 0x04, 0x40, 0x04, + 0x21, 0x00, 0x0a, 0x00, 0x06, 0x22, 0x20, 0x00, 0x3d, 0x00, 0x20, 0x00, 0x35, 0xd8, 0xfb, 0xde, + 0xb2, 0x00, 0x0a, 0x00, 0x00, 0x00 }; + +// UTF-16BE +const unsigned char helloStrUtf16be[] = { 0x00, 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x57, 0x00, 0x6f, + 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, 0x0a, 0x04, 0x1f, 0x04, 0x40, 0x04, 0x38, + 0x04, 0x32, 0x04, 0x35, 0x04, 0x42, 0x00, 0x2c, 0x00, 0x20, 0x04, 0x1c, 0x04, 0x38, 0x04, 0x40, + 0x00, 0x21, 0x00, 0x0a, 0x22, 0x06, 0x00, 0x20, 0x00, 0x3d, 0x00, 0x20, 0xd8, 0x35, 0xde, 0xfb, + 0x00, 0xb2, 0x00, 0x0a, 0x00, 0x00 }; + +// UCS-4 (UTF-32LE на x86) +const unsigned char helloStrUcs4[] = { 0x48, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, + 0x6f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, + 0x72, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x1f, 0x04, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x38, 0x04, 0x00, 0x00, + 0x32, 0x04, 0x00, 0x00, 0x35, 0x04, 0x00, 0x00, 0x42, 0x04, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x1c, 0x04, 0x00, 0x00, 0x38, 0x04, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x22, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xfb, 0xd6, 0x01, 0x00, 0xb2, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +// UTF-32BE +const unsigned char helloStrUtf32be[] = { 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6c, + 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x6f, + 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x04, 0x1f, 0x00, 0x00, 0x04, 0x40, 0x00, 0x00, 0x04, 0x38, + 0x00, 0x00, 0x04, 0x32, 0x00, 0x00, 0x04, 0x35, 0x00, 0x00, 0x04, 0x42, 0x00, 0x00, 0x00, 0x2c, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x04, 0x1c, 0x00, 0x00, 0x04, 0x38, 0x00, 0x00, 0x04, 0x40, + 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x22, 0x06, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x01, 0xd6, 0xfb, 0x00, 0x00, 0x00, 0xb2, + 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00 }; + +// Длина тестовой строки в символах +const std::size_t helloStrLen = 33; + +// Некорректные строки для теста обработки ошибок +const unsigned char badStrUtf8[] = { 0xd0, 0xdf, 0x00 }; +const unsigned char badStr2Utf8[] = { 0xf8, 0x9f, 0x00 }; +const unsigned char badStrUcs4[] = { 0xf8, 0x9f, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00 }; +const unsigned char badStrUtf16le[] = { 0x35, 0xc8, 0xfb, 0xde, 0x00, 0x00 }; +const unsigned char badStr2Utf16le[] = { 0x35, 0xd8, 0xfb, 0xce, 0x00, 0x00 }; + +// Для теста конкатенации +// "Hello " +const unsigned char helloPart1Ucs4[] = { 0x48, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, + 0x6f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +// "big" +const unsigned char helloPart2Ucs4[] = { 0x62, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +// " world" +const unsigned char helloPart3Ucs4[] = { 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, + 0x6c, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +// "Hello big world" +const unsigned char helloResultUcs4[] = { 0x48, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, + 0x6f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, + 0x67, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, + 0x72, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Тесты +// + +// +// Определение длины символа UTF-8 +// + +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])); +} + +// +// Определение длины символа UTF-8 по символу UCS-4 +// + +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])); +} + +// +// Определение длины строки UTF-8 +// + +TEST(Convert, utf8_length) +{ + ASSERT_EQ(helloStrLen, utf8_str_len((const char*)helloStrUtf8)); +} + +// +// Определение длины строки UTF-8 по строке UCS-4 +// + +TEST(Convert, utf8_str_len_by_ucs4_str) +{ + ASSERT_EQ(sizeof(helloStrUtf8) - 1, ucs4_get_utf8_str_bytes((const char32_t*)helloStrUcs4)); +} + +// +// Определение длины символа UTF-16LE +// + +TEST(Convert, utf16le_symbol_size) +{ + ASSERT_EQ(2, utf16_get_symbol_size((const char16_t*)&helloStrUtf16le[0])); + ASSERT_EQ(4, utf16_get_symbol_size((const char16_t*)&helloStrUtf16le[60])); +} + +// +// Определение длины символа UTF-16BE +// + +TEST(Convert, utf16be_symbol_size) +{ + ASSERT_EQ(2, utf16_get_symbol_size((const char16_t*)&helloStrUtf16be[0], BYTE_ORDER_BIG_ENDIAN)); + ASSERT_EQ(4, utf16_get_symbol_size((const char16_t*)&helloStrUtf16be[60], BYTE_ORDER_BIG_ENDIAN)); +} + +// +// Определение длины символа UTF-16 по символу UCS-4 +// + +TEST(Convert, utf16_symbol_size_by_ucs4_symbol) +{ + ASSERT_EQ(2, ucs4_get_utf16_symbol_size(*(const char32_t*)&helloStrUcs4[0])); + ASSERT_EQ(4, ucs4_get_utf16_symbol_size(*(const char32_t*)&helloStrUcs4[120])); +} + +// +// Определение длины строки UTF-16LE +// + +TEST(Convert, utf16le_length) +{ + ASSERT_EQ(helloStrLen, utf16_str_len((const char16_t*)helloStrUtf16le)); +} + +// +// Определение длины строки UTF-16BE +// + +TEST(Convert, utf16be_length) +{ + ASSERT_EQ(helloStrLen, utf16_str_len((const char16_t*)helloStrUtf16be, BYTE_ORDER_BIG_ENDIAN)); +} + +TEST(Convert, utf16_str_len_by_ucs4_str) +{ + ASSERT_EQ(sizeof(helloStrUtf16le) - 2, ucs4_get_utf16_str_bytes((const char32_t*)helloStrUcs4)); +} + +// +// Определение длины строки UTF-32 +// + +TEST(Convert, utf32_length) +{ + ASSERT_EQ(helloStrLen, utf32_str_len((const char32_t*)helloStrUcs4)); +} + +// +// Конвертирование UTF-8 в UCS-4 +// +TEST(Convert, utf8_to_ucs4) +{ + std::size_t len = utf8_str_len((const char*)helloStrUtf8); + char32_t* data = new char32_t[len]; + utf8_to_ucs4((const char*)helloStrUtf8, data, len); + EXPECT_EQ(0, memcmp(helloStrUcs4, data, len*sizeof(char32_t))); + delete[] data; +} + +// +// Конвертирование UCS-4 в UTF-8 +// +TEST(Convert, ucs4_to_utf8) +{ + std::size_t len = ucs4_get_utf8_str_bytes((const char32_t*)helloStrUcs4); + 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); + EXPECT_EQ(0, memcmp(helloStrUtf8, data, len)); + delete[] data; +} + +// +// Конвертирование UTF-16LE в UCS-4 +// +TEST(Convert, utf16le_to_ucs4) +{ + std::size_t len = utf16_str_len((const char16_t*)helloStrUtf16le); + char32_t* data = new char32_t[len]; + utf16_to_ucs4((const char16_t*)helloStrUtf16le, data, len); + EXPECT_EQ(0, memcmp(helloStrUcs4, data, len*sizeof(char32_t))); + delete[] data; +} + +// +// Конвертирование UCS-4 в UTF-16LE +// +TEST(Convert, ucs4_to_utf16le) +{ + std::size_t len = ucs4_get_utf16_str_bytes((const char32_t*)helloStrUcs4); + std::size_t symbols = utf32_str_len((const char32_t*)helloStrUcs4); + char16_t* data = new char16_t[len]; + ucs4_to_utf16((const char32_t*)helloStrUcs4, data, symbols); + EXPECT_EQ(0, memcmp(helloStrUtf16le, data, len)); + delete[] data; +} + +// +// Конвертирование UTF-16BE в UCS-4 +// +TEST(Convert, utf16be_to_ucs4) +{ + std::size_t len = utf16_str_len((const char16_t*)helloStrUtf16be); + char32_t* data = new char32_t[len]; + utf16_to_ucs4((const char16_t*)helloStrUtf16be, data, len, BYTE_ORDER_BIG_ENDIAN); + EXPECT_EQ(0, memcmp(helloStrUcs4, data, len*sizeof(char32_t))); + delete[] data; +} + +// +// Конвертирование UCS-4 в UTF-16BE +// +TEST(Convert, ucs4_to_utf16be) +{ + std::size_t len = ucs4_get_utf16_str_bytes((const char32_t*)helloStrUcs4); + std::size_t symbols = utf32_str_len((const char32_t*)helloStrUcs4); + char16_t* data = new char16_t[len]; + ucs4_to_utf16((const char32_t*)helloStrUcs4, data, symbols, BYTE_ORDER_BIG_ENDIAN); + EXPECT_EQ(0, memcmp(helloStrUtf16be, data, len)); + delete[] data; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Тесты на ошибки конвертирования +// + +// +// Первый байт двухбайтового символа содержит единицы в пяти старших разрядах +// + +TEST(ConvertError, utf8_length_error) +{ + EXPECT_THROW(utf8_str_len((const char*)badStr2Utf8), bad_conversion); +} + +// +// Второй байт двухбайтового символа содержит единицы в двух старших разрядах +// + +TEST(ConvertError, utf8_decode_error) +{ + std::size_t len = utf8_str_len((const char*)badStrUtf8); + char32_t* data = new char32_t[len]; + EXPECT_THROW(utf8_to_ucs4((const char*)badStrUtf8, data, len), bad_conversion); + delete[] data; +} + +// +// Некорректное перевое слово суррогатной пары +// + +TEST(ConvertError, utf16_decode_error) +{ + std::size_t len = utf16_str_len((const char16_t*)badStrUtf16le); + char32_t* data = new char32_t[len]; + EXPECT_THROW(utf16_to_ucs4((const char16_t*)badStrUtf16le, data, len), bad_conversion); + delete[] data; +} + +// +// Некорректное второе слово суррогатной пары +// + +TEST(ConvertError, utf16_decode_error2) +{ + std::size_t len = utf16_str_len((const char16_t*)badStr2Utf16le); + char32_t* data = new char32_t[len]; + EXPECT_THROW(utf16_to_ucs4((const char16_t*)badStr2Utf16le, data, len), bad_conversion); + delete[] data; +} + +// +// Код символа не входит в диапазон символов UCS-4 +// + +TEST(ConvertError, ucs4_wrong_symbol) +{ + EXPECT_THROW(ucs4_get_utf8_symbol_size(*(const char32_t*)badStrUcs4), bad_conversion); + EXPECT_THROW(ucs4_get_utf16_symbol_size(*(const char32_t*)badStrUcs4), bad_conversion); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Тесты класса ustring +// + +// +// Конструктор из UTF-8 +// + +TEST(Ustring, utf8_constructor) +{ + ustring str((const char*)helloStrUtf8); + EXPECT_EQ(0, memcmp(helloStrUcs4, str.utf32_str(), sizeof(helloStrUcs4))); +} + +// +// Конструктор из UTF-16LE +// + +TEST(Ustring, utf16_constructor) +{ + ustring str((const char16_t*)helloStrUtf16le); + EXPECT_EQ(0, memcmp(helloStrUcs4, str.utf32_str(), sizeof(helloStrUcs4))); +} + +// +// Конструктор из UTF-32LE +// + +TEST(Ustring, utf32_constructor) +{ + ustring str((const char32_t*)helloStrUcs4); + EXPECT_EQ(0, memcmp(helloStrUcs4, str.utf32_str(), sizeof(helloStrUcs4))); +} + +// +// Оператор UTF-8 +// + +TEST(Ustring, utf8_cast_operator) +{ + ustring str((const char32_t*)helloStrUcs4); + EXPECT_EQ(0, memcmp(helloStrUtf8, str.utf8_str(), sizeof(helloStrUtf8))); +} + +// +// Оператор UTF-16LE +// + +TEST(Ustring, utf16_cast_operator) +{ + ustring str((const char32_t*)helloStrUcs4); + EXPECT_EQ(0, memcmp(helloStrUtf16le, str.utf16_str(), sizeof(helloStrUtf16le))); +} + +// +// Конкатенация строк (operator+=) +// + +TEST(Ustring, concat_op_plus_assign) +{ + ustring s1((const char32_t*)helloPart1Ucs4); + ustring s2((const char32_t*)helloPart2Ucs4); + ustring s3((const char32_t*)helloPart3Ucs4); + + s1 += s2; + s1 += s3; + + EXPECT_EQ(0, memcmp(helloResultUcs4, s1.utf32_str(), sizeof(helloResultUcs4))); +} + +// +// Конкатенация строк (operator+) +// + +TEST(Ustring, concat_op_plus) +{ + ustring s1((const char32_t*)helloPart1Ucs4); + //ustring s2((const char32_t*)helloPart2Ucs4); + //ustring s3((const char32_t*)helloPart3Ucs4); + + ustring res = s1 + "big" + " world"; + + EXPECT_EQ(0, memcmp(helloResultUcs4, res.utf32_str(), sizeof(helloResultUcs4))); +} + +// +// Поиск символа в строке +// +TEST(Ustring, find_symbol) +{ + ustring str((const char32_t*)helloResultUcs4); + EXPECT_EQ(4, str.find('o')); + EXPECT_EQ(11, str.find('o', 8)); +} + +// +// Поиск подстроки в строке +// + +TEST(Ustring, find_string) +{ + ustring str = "Hello World! asdf qwerty asdf"; + EXPECT_EQ(13, str.find("asdf")); + EXPECT_EQ(25, str.find("asdf", 14)); +} + +// +// Вывод в поток std::ostream +// + +TEST(Ustring, std_ostream_output) +{ + std::stringstream ss; + ustring str((const char32_t*)helloResultUcs4); + + ss << str; + EXPECT_EQ(0, strcmp(ss.str().c_str(), "Hello big world")); +} + +// +// Ввод из std::istream +// + +TEST(Ustring, std_istream_intput) +{ + std::stringstream ss; + ustring str; + + ss << "big"; + ss >> str; + EXPECT_EQ(0, memcmp(helloPart2Ucs4, str.utf32_str(), sizeof(helloPart2Ucs4))); +} + +// +// Проверка на равенство +// + +TEST(Ustring, equality) +{ + ustring str((const char32_t*)helloResultUcs4); + ustring str2; + + EXPECT_TRUE(str == "Hello big world"); + EXPECT_TRUE(str2 == ""); +} + +// +// Установление нового значения строки +// + +TEST(Ustring, assign_new_value) +{ + ustring s1 = "first string"; + ustring s2 = "second string"; + + s2.assign(s1.utf32_str(false, current_byte_order()) + 6, 6); + EXPECT_TRUE(s2 == "string"); +} + +// +// Возвращение подстроки +// + +TEST(Ustring, substring) +{ + ustring str = "Hello big world!"; + ustring str2 = str.substr(6, 3); + ustring str3 = str.substr(6, ustring::npos); + EXPECT_TRUE(str2 == "big"); + EXPECT_TRUE(str3 == "big world!"); +} + +// +// Стирание части строки +// + +TEST(Ustring, erasing) +{ + ustring str = "Hello big world!"; + str.erase(6,4); + EXPECT_TRUE(str == "Hello world!"); + str.erase(5, ustring::npos); + EXPECT_TRUE(str == "Hello"); +} + +// +// Вставка строки в строку +// + +TEST(Ustring, insert_string) +{ + ustring str = "bla-bla"; + str.insert(0, "begin "); + EXPECT_TRUE(str == "begin bla-bla"); + str.insert(6, "bla "); + EXPECT_TRUE(str == "begin bla bla-bla"); + str.insert(17, " end"); + EXPECT_TRUE(str == "begin bla bla-bla end"); +} + +// +// Замена подвтроки строкой +// + +TEST(Ustring, replace_string) +{ + ustring str = "111 hello 111 world 111"; + ustring str2 = str, str3 = str; + + str.replace("111", "2222"); + EXPECT_TRUE(str == "2222 hello 2222 world 2222"); + + str2.replace("111", "22"); + EXPECT_TRUE(str2 == "22 hello 22 world 22"); + + str3.replace("111", "222"); + EXPECT_TRUE(str3 == "222 hello 222 world 222"); + + str2 += " more 22 and 22 more 22"; + str2.replace("22", "33"); + EXPECT_TRUE(str2 == "33 hello 33 world 33 more 33 and 33 more 33"); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}