2014-10-31 6 views
5

Poszukuję sposobu na wyświetlenie łańcucha znaków UTF-8 ze znakami niedrukowalnymi/niepoprawnymi. W czasach ASCII byłem przyzwyczajony do używania isprint, aby zdecydować, czy postać powinna być wydrukowana jak jest, czy też uciekła. Z UTF-8 iteracja jest trudniejsza, ale Boost.Locale robi to dobrze. Jednak nie znalazłem w nim niczego, co mogłoby zdecydować, czy jakaś postać może być wydrukowana, czy nawet prawdziwa.Boost.Locale i isprint

W poniższym źródle ciąg "Hello あにま ➦ ⊆ \x02\x01\b \xff\xff\xff " zawiera kilka złych elementów, których nie można wydrukować (na przykład \b), a inne są niepoprawnymi sekwencjami nieprawidłowymi (\xff\xff\xff). Jaki test powinienem wykonać, aby zdecydować, czy postać może być wydrukowana czy nie?

// Based on an example of Boost.Locale. 
#include <boost/locale.hpp> 
#include <iostream> 
#include <iomanip> 

int main() 
{ 
    using namespace boost::locale; 
    using namespace std; 

    generator gen; 
    std::locale loc = gen(""); 
    locale::global(loc); 
    cout.imbue(loc); 

    string text = "Hello あにま ➦ ⊆ \x02\x01\b \xff\xff\xff "; 

    cout << text << endl; 

    boundary::ssegment_index index(boundary::character, text.begin(), text.end()); 

    for (auto p: index) 
    { 
     cout << '[' << p << '|'; 
     for (uint8_t c: p) 
     cout << std::hex << std::setw(2) << std::setfill('0') << int(c); 
     cout << "] "; 
    } 
    cout << '\n'; 
} 

Po uruchomieniu daje

[H|48] [e|65] [l|6c] [l|6c] [o|6f] [ |20] [あ|e38182] [に|e381ab] [ま|e381be] 
[ |20] [➦|e29ea6] [ |20] [|f09f9199] [ |20] [|f09d95ab] 
[⊆|e28a86] [|f09d95a2] [ |20] [|02] [|01] |08] [ |20] [??? |ffffff20] 

Jak mam zdecydować, że [|01] nie jest do druku, i nie jest [??? |ffffff20], ale [o|6f] jest, i tak jest [|f09f9199]? Z grubsza, test powinien pozwolić mi zdecydować, czy wydrukować lewy element [|] -pair, czy prawy, gdy nie jest isprint.

Dzięki

+0

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

+0

Szukasz rozwiązania wykorzystującego tylko funkcję Boost? Czy korzystanie z innych bibliotek, takich jak ICU, jest akceptowalne? – akakatak

+0

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

Odpowiedz

3

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-CPPutf::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).

Powiązane problemy