Метод jlength
возвращает число кодовых позиций в строке, а не байтов. Если нужно получить число байтов, пользуйтесь методом length
.
$KCODE = 'u'
require 'jcode'
sword = 'épée'
sword.jlength # 4
sword.length # 6
Такие методы, как upcase
и capitalize
, обычно неправильно работают со специальными символами. Это ограничение текущей версии Ruby. (Не стоит считать ошибкой, поскольку получить представление слова с первой прописной буквой довольно трудно; такая задача просто не решается в схеме интернационализации Ruby. Считайте, что это нереализованное поведение.)
$KCODE = 'u'
sword.upcase # 'ÉPÉE'
sword.capitalize # 'épée'
Если вы не пользуетесь монолитной формой, то в некоторых случаях метод может сработать, поскольку латинские буквы отделены от диакритических знаков. Но в общем случае работать не будет — в частности, для турецкого, немецкого, голландского и любого другого языка с нестандартными правилами преобразования регистра.
Возможно, вы думаете, что неакцентированные символы в некотором смысле эквивалентны своим акцентированным вариантам. Это почти всегда не так. Здесь мы имеем дело с разными символами. Убедимся в этом на примере метода count
:
$KCODE = 'u'
sword.count('e') # 1 (не 3)
Но для составных (не монолитных) символов верно прямо противоположное. В этом случае латинская буква распознается.
Метод count
возвращает сбивающий с толку результат, когда ему передается многобайтовый символ. Метод jcount
ведет себя в этом случае правильно:
$KCODE = 'u'
sword.count('eé') # 5 (не 3)
sword.jcount('eé') # 3
Существует вспомогательный метод mbchar?
, который определяет, есть ли в строке многобайтовые символы.
$KCODE = 'u'
sword.mbchar? # 0 (смещение первого многобайтового символа)
'foo'.mbchar? # nil
В библиотеке jcode
переопределены также методы chop
, delete
, squeeze
, succ
, tr
и tr_s
. Применяя их в режиме UTF-8, помните, что вы работаете с версиями, «знающими о многобайтовости». При попытке манипулировать многобайтовыми строками без библиотеки jcode
вы можете получить странные или ошибочные результаты.
Можно побайтно просматривать строку, как обычно, с помощью итератора each_byte
. А можно просматривать посимвольно с помощью итератора each_char
. Второй способ имеет дело с односимвольными строками, первый (в текущей версии Ruby) — с однобайтными целыми. Разумеется, мы в очередной раз приравниваем кодовую позицию к символу. Несмотря на название, метод each_char
на самом деле перебирает кодовые позиции, а не символы.
$KCODE = 'u'
sword.each_byte {|x| puts x } # Шесть строк с целыми числами.
sword.each_char {|x| puts x } # Четыре строки со строками.
Если вы запутались, не переживайте. Все мы через это проходили. Я попытался свести все вышесказанное в таблицу 4.1.
Таблица 4.1. Составные и монолитные формы
Монолитная форма 'é' | ||||
---|---|---|---|---|
Название символа | Глиф | Кодовая позиция | Байты UTF-8 | Примечания |
Строчная латинская e с акутом | é | U+00E9 | 0xC3 0хА9 | Один символ, одна кодовая позиция, один байт |
Составная форма 'é' | ||||
Название символа | Глиф | Кодовая позиция | Байты UTF-8 | Примечания |
Строчная латинская е | е | U+0065 | 0x65 | Один символ, две кодовых позиции (два «программистских символа»), три байта UTF-8 |
Модифицирующий акут | ́ | U+0301 | 0xCC 0x81 |
Что еще надо учитывать при работе с интернациональными строками? Квадратные скобки по- прежнему относятся к байтам, а не к символам. Но при желании это можно изменить. Ниже приведена одна из возможных реализаций (не особенно эффективная, зато понятная):