Unicode ma właściwości dla każdego punktu kodowego, które zawierają general category i raport techniczny wymienia regex classifications (alfa, wykres, itp). Klasyfikacja unicode print
zawiera tabulatory, w których std::isprint
(przy użyciu ustawień regionalnych C) nie ma. print
zawiera litery, znaki, cyfry, znaki interpunkcyjne, symbole, spacje i punkty kodu formatowania. Punkty kodu formatowania do not include CR
or LF
, ale do obejmują code points that affect the appearance sąsiednich znaków. Wierzę, że to dokładnie to, czego chciałeś (z wyjątkiem karty); specyfikacja została zaprojektowana z myślą o wsparciu tych właściwości postaci.
Większość funkcji klasyfikacyjnych, takich jak std::isprint
, może mieć tylko jedną wartość skalarną naraz, więc kodowanie UTF32 jest oczywistym wyborem. Niestety, nie ma gwarancji, że twój system obsługuje ustawienia regionalne UTF32, ani nie gwarantuje się, że wchar_t
jest niezbędnym 20 bitom potrzebnym do przechowywania wszystkich punktów kodowych Unicode. Dlatego rozważałbym użycie klasyfikacji boost::spirit::char_encoding::unicode
, jeśli możesz. Ma wewnętrzną tabelę wszystkich kategorii unikodowych i implementuje klasyfikacje wymienione w raporcie technicznym regex. Wygląda na to, że używa starszej bazy danych Unicode 5.2, ale zapewniono C++ używane do generowania tabel i można je zastosować do nowszych plików.
Wielobajtowa sekwencja UTF8 nadal będzie wymagać konwersji do poszczególnych punktów kodowych (UTF32), a użytkownik wyraźnie wspomniał o możliwości pominięcia poprzednich nieprawidłowych sekwencji UTF8.Ponieważ jestem programista C++, postanowiłem niepotrzebnie spam ekran i wdrożyć constexpr UTF8-> funkcję UTF32:
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <boost/range/iterator_range.hpp>
#include <boost/spirit/home/support/char_encoding/unicode.hpp>
namespace {
struct multi_byte_info {
std::uint8_t id_mask;
std::uint8_t id_matcher;
std::uint8_t data_mask;
};
constexpr const std::uint8_t multi_byte_id_mask = 0xC0;
constexpr const std::uint8_t multi_byte_id_matcher = 0x80;
constexpr const std::uint8_t multi_byte_data_mask = 0x3F;
constexpr const std::uint8_t multi_byte_bits = 6;
constexpr const multi_byte_info multi_byte_infos[] = {
// skip 1 byte info
{0xE0, 0xC0, 0x1F},
{0xF0, 0xE0, 0x0F},
{0xF8, 0xF0, 0x07}};
constexpr const unsigned max_length =
(sizeof(multi_byte_infos)/sizeof(multi_byte_info));
constexpr const std::uint32_t overlong[] = {0x80, 0x800, 0x10000};
constexpr const std::uint32_t max_code_point = 0x10FFFF;
}
enum class extraction : std::uint8_t { success, failure };
struct extraction_attempt {
std::uint32_t code_point;
std::uint8_t bytes_processed;
extraction status;
};
template <typename Iterator>
constexpr extraction_attempt next_code_point(Iterator position,
const Iterator &end) {
static_assert(
std::is_same<typename std::iterator_traits<Iterator>::iterator_category,
std::random_access_iterator_tag>{},
"bad iterator type");
extraction_attempt result{0, 0, extraction::failure};
if (end - position) {
result.code_point = std::uint8_t(*position);
++position;
++result.bytes_processed;
if (0x7F < result.code_point) {
unsigned expected_length = 1;
for (const auto info : multi_byte_infos) {
if ((result.code_point & info.id_mask) == info.id_matcher) {
result.code_point &= info.data_mask;
break;
}
++expected_length;
}
if (max_length < expected_length || (end - position) < expected_length) {
return result;
}
for (unsigned byte = 0; byte < expected_length; ++byte) {
const std::uint8_t next_byte = *(position + byte);
if ((next_byte & multi_byte_id_mask) != multi_byte_id_matcher) {
return result;
}
result.code_point <<= multi_byte_bits;
result.code_point |= (next_byte & multi_byte_data_mask);
++result.bytes_processed;
}
if (max_code_point < result.code_point) {
return result;
}
if (overlong[expected_length - 1] > result.code_point) {
return result;
}
}
result.status = extraction::success;
} // end multi-byte processing
return result;
}
template <typename Range>
constexpr extraction_attempt next_code_point(const Range &range) {
return next_code_point(std::begin(range), std::end(range));
}
template <typename T>
boost::iterator_range<T>
next_character_bytes(const boost::iterator_range<T> &range,
const extraction_attempt result) {
return boost::make_iterator_range(range.begin(),
range.begin() + result.bytes_processed);
}
template <std::size_t Length>
constexpr bool test(const char (&range)[Length],
const extraction expected_status,
const std::uint32_t expected_code_point,
const std::uint8_t expected_bytes_processed) {
const extraction_attempt result =
next_code_point(std::begin(range), std::end(range) - 1);
switch (expected_status) {
case extraction::success:
return result.status == extraction::success &&
result.bytes_processed == expected_bytes_processed &&
result.code_point == expected_code_point;
case extraction::failure:
return result.status == extraction::failure &&
result.bytes_processed == expected_bytes_processed;
default:
return false;
}
}
int main() {
static_assert(test("F", extraction::success, 'F', 1), "");
static_assert(test("\0", extraction::success, 0, 1), "");
static_assert(test("\x7F", extraction::success, 0x7F, 1), "");
static_assert(test("\xFF\xFF", extraction::failure, 0, 1), "");
static_assert(test("\xDF", extraction::failure, 0, 1), "");
static_assert(test("\xDF\xFF", extraction::failure, 0, 1), "");
static_assert(test("\xC1\xBF", extraction::failure, 0, 2), "");
static_assert(test("\xC2\x80", extraction::success, 0x80, 2), "");
static_assert(test("\xDF\xBF", extraction::success, 0x07FF, 2), "");
static_assert(test("\xEF\xBF", extraction::failure, 0, 1), "");
static_assert(test("\xEF\xBF\xFF", extraction::failure, 0, 2), "");
static_assert(test("\xE0\x9F\xBF", extraction::failure, 0, 3), "");
static_assert(test("\xE0\xA0\x80", extraction::success, 0x800, 3), "");
static_assert(test("\xEF\xBF\xBF", extraction::success, 0xFFFF, 3), "");
static_assert(test("\xF7\xBF\xBF", extraction::failure, 0, 1), "");
static_assert(test("\xF7\xBF\xBF\xFF", extraction::failure, 0, 3), "");
static_assert(test("\xF0\x8F\xBF\xBF", extraction::failure, 0, 4), "");
static_assert(test("\xF0\x90\x80\x80", extraction::success, 0x10000, 4), "");
static_assert(test("\xF4\x8F\xBF\xBF", extraction::success, 0x10FFFF, 4), "");
static_assert(test("\xF7\xBF\xBF\xBF", extraction::failure, 0, 4), "");
static_assert(test("", extraction::success, 0x1D56B, 4), "");
constexpr const static char text[] =
"Hello あにま ➦ ⊆ \x02\x01\b \xff\xff\xff ";
std::cout << text << std::endl;
auto data = boost::make_iterator_range(text);
while (!data.empty()) {
const extraction_attempt result = next_code_point(data);
switch (result.status) {
case extraction::success:
if (boost::spirit::char_encoding::unicode::isprint(result.code_point)) {
std::cout << next_character_bytes(data, result);
break;
}
default:
case extraction::failure:
std::cout << "[";
std::cout << std::hex << std::setw(2) << std::setfill('0');
for (const auto byte : next_character_bytes(data, result)) {
std::cout << int(std::uint8_t(byte));
}
std::cout << "]";
break;
}
data.advance_begin(result.bytes_processed);
}
return 0;
}
wyjściowa:
Hello あにま ➦ ⊆ ���
Hello あにま ➦ ⊆ [02][01][08] [ff][ff][ff] [00]
Jeśli moja UTF8-> UTF32 realizacja cię przeraża, lub jeśli potrzebujesz wsparcia dla użytkowników regionalne:
std::mbtoc32
- imponujące, ponieważ jest to najbardziej oczywisty wybór, a jednak nie jest realizowany w libstdC++ lub libC++ (może bagażnik buduje?)
- Czy nie reetrant (bieżące locale i być zmieniane w innym nagle)
- iterators provided by boost .
- Zgłasza się na nieprawidłowych sekwencji czyniąc go bezużytecznym (nie może się rozwijać w przeszłości złe sekwencje).
boost::locale::conv
i C++ 11 std::codecvt
- przeznaczony do konwersji zakresów kodowania.
- należy albo wyjście UTF32 do konsoli (zmiana lokalizacji) lub konwertować znak w swoim czasie, aby dopasować bajt (ów) źródło z wartością UTF32.
- UTF8-CPP
utf::next
(i nie do rzucania utf8::internal::validate_next
).
- IMO zarówno inconsistently update the iterator position. Jeśli funkcja nie wykona pewnych testów poprawności, pozycja iteratora jest w końcu bajtem prawidłowej sekwencji utf8 reprezentującej zły punkt kodowy. Dokumentacja mówi:
go: odniesienie do iterator wskazujący na początek na kodowanie UTF-8 punkt kodowy. Po powrocie funkcja jest zwiększana, aby wskazywała początek następnego punktu kodowania.
co nie wskazuje na efekty uboczne w przypadku wyjątków (na pewno jest ich kilka).
Zastanawiam się, to samo. Dla mojego przypadku użycia (umieszczenie stylu \ x123 ucieka w łańcuch), moim prostym obejściem będzie traktowanie wszystkich bajtów ciągu znaków> 127 i znaków kontrolnych ASCII jako wymagających ucieczki. isPrint może przyjmować szerokie znaki do 32 bitów, ale nie ma gwarancji, że "znak" jest pojedynczym punktem kodowym. – DouglasHeriot
Szukasz rozwiązania wykorzystującego tylko funkcję Boost? Czy korzystanie z innych bibliotek, takich jak ICU, jest akceptowalne? – akakatak
Ogólnie rzecz biorąc, nie ma prostej reguły określającej, czy kodowany kod Unicode jest pirntable czy nie. Znaczenie "do wydrukowania" zależy od kontekstu. Na przykład, jeśli zezwalasz na wiele punktów połączenia z jednym glifem lub nie. Prawdopodobnie jednym standardowym sposobem jest użycie [ICU] (http://site.icu-project.org/). Na pierwszy grance, wydaje się on oferować [funkcję] (http://icu-project.org/apiref/icu4c/classicu_1_1LEFontInstance.html # ae6d42b1467060adbdf00d30bde96a5fe) do filtrowania glifów o zerowej szerokości, ale nie jestem pewien. – akakatak