Кроссплатформенная итерация строки Unicode (подсчет графем с использованием ICU) - PullRequest
19 голосов
/ 02 января 2011

Я хочу выполнить итерацию каждого символа строки Unicode, , обрабатывая каждую суррогатную пару и объединяя последовательность символов в одну единицу (одна графема).

Пример

Текст "नमस्ते" состоит из кодовых точек: U+0928, U+092E, U+0938, U+094D, U+0924, U+0947, из которых U+0938 и U+0947 являются объединяющими знаками .

static void Main(string[] args)
{
    const string s = "नमस्ते";

    Console.WriteLine(s.Length); // Ouptuts "6"

    var l = 0;
    var e = System.Globalization.StringInfo.GetTextElementEnumerator(s);
    while(e.MoveNext()) l++;
    Console.WriteLine(l); // Outputs "4"
}

Так что у нас это есть в .NET. У нас также есть Win32 CharNextW()

#include <Windows.h>
#include <iostream>
#include <string>

int main()
{
    const wchar_t * s = L"नमस्ते";

    std::cout << std::wstring(s).length() << std::endl; // Gives "6"

    int l = 0;
    while(CharNextW(s) != s)
    {
        s = CharNextW(s);
        ++l;
    }

    std::cout << l << std::endl; // Gives "4"

    return 0;
}

Вопрос

Оба способа, которые я знаю, специфичны для Microsoft. Существуют ли портативные способы сделать это?

  • Я слышал об ICU, но не смог быстро найти что-то связанное (UnicodeString(s).length() все еще дает 6). Будет приемлемым ответом для указания на соответствующую функцию / модуль в ICU.
  • C ++ не имеет понятия Unicode, поэтому легкая кроссплатформенная библиотека для решения этих проблем может дать приемлемый ответ.

Редактировать: правильный ответ, используя ICU

@ МакДауэлл дал подсказку использовать BreakIterator от ICU, который, я думаю, можно рассматривать как де-факто кроссплатформенный стандарт для работы с Unicode. Вот пример кода, демонстрирующий его использование (поскольку примеры удивительно редки):

#include <unicode/schriter.h>
#include <unicode/brkiter.h>

#include <iostream>
#include <cassert>
#include <memory>

int main()
{
    const UnicodeString str(L"नमस्ते");

    {
        // StringCharacterIterator doesn't seem to recognize graphemes
        StringCharacterIterator iter(str);
        int count = 0;
        while(iter.hasNext())
        {
            ++count;
            iter.next();
        }
        std::cout << count << std::endl; // Gives "6"
    }

    {
        // BreakIterator works!!
        UErrorCode err = U_ZERO_ERROR;
        std::unique_ptr<BreakIterator> iter(
            BreakIterator::createCharacterInstance(Locale::getDefault(), err));
        assert(U_SUCCESS(err));
        iter->setText(str);

        int count = 0;
        while(iter->next() != BreakIterator::DONE) ++count;
        std::cout << count << std::endl; // Gives "4"
    }

    return 0;
}

Ответы [ 3 ]

13 голосов
/ 02 января 2011

Вы должны быть в состоянии использовать ICU BreakIterator для этого (экземпляр символа, предполагая, что он эквивалентен версии Java).

2 голосов
/ 02 января 2011

Класс Glib ustring дает вам строки utf-8, если вам подходит использование utf-8.Он разработан, чтобы быть похожим на std::string.Поскольку utf-8 является родным для Linux, ваша задача довольно проста:

int main()
{
    Glib::ustring s = L"नमस्ते";
    cout << s.size();
}

вы также можете перебирать символы строки, как обычно, с помощью Glib::ustring::iterator

0 голосов
/ 09 августа 2017

ICU имеет очень старый интерфейс, Boost.Locale намного лучше:

#include <iostream>
#include <string_view>

#include <boost/locale.hpp>

using namespace std::string_view_literals;

int main()
{
    boost::locale::generator gen;
    auto string = "noël ??"sv;
    boost::locale::boundary::csegment_index map{
        boost::locale::boundary::character, std::begin(string),
        std::end(string), gen("")};
    for (const auto& i : map)
    {
        std::cout << i << '\n';
    }
}

Текст от здесь

...