Чтение онлайн

ЖАНРЫ

Программирование на языке Ruby
Шрифт:

Можно побайтно просматривать строку, как обычно, с помощью итератора

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. Составные и монолитные формы

Монолитная форма "'e"
Название символа Глиф Кодовая позиция Байты UTF-8 Примечания
Строчная латинская e с акутом 'e U+00E9 0xC3 0хА9 Один символ, одна кодовая позиция, один байт
Составная форма "'e"
Название символа Глиф Кодовая позиция Байты UTF-8 Примечания
Строчная латинская е е U+0065 0x65 Один символ, две кодовых позиции (два «программистских символа»), три байта UTF-8
Модифицирующий акут U+0301 0xCC 0x81

Что еще надо учитывать при работе с интернациональными строками? Квадратные скобки по-прежнему относятся к байтам, а не к символам. Но при желании это можно изменить. Ниже приведена одна из возможных реализаций (не особенно эффективная, зато понятная):

class String

 def [](index)

self.scan(/./)[index]

 end

 def []=(index,value)

arr = self.scan(/./)

arr[index] = value

self.replace(arr.join)

value

 end

end

Конечно, здесь не реализована значительная часть функциональности настоящего метода

[]
, который понимает диапазоны, регулярные выражения и т.д. Если вам все это нужно, придется запрограммировать самостоятельно.

У метода

unpack
есть параметры, помогающие манипулировать Unicode-строками. Указав в форматной строке параметр
U*
, мы можем преобразовать строку в кодировке UTF-8 в массив кодовых позиций (
U
без звездочки преобразует только первую кодовую позицию):

codepoints = sword.unpack('U*') # [233, 112, 233, 101]

Вот несколько более полезный пример, в котором все кодовые позиции в строке, отличные от ASCII (то есть начиная с U+0080), преобразуются к виду U+XXXX, который мы обсуждали выше:

def reveal_non_ascii(str)

 str.unpack('U*').map do |cp|

 if cp < 0x80

cp.chr

 else

'(U+%04X)' % cp

 end

 end.join

end

У

метода
String#unpack
есть «близкий родственник»
Array#pack
, выполняющий обратную операцию:

[233, 112, 233, 101].pack('U*') # "'ep'ee"

Мы можем воспользоваться им, чтобы вставить Unicode-символы, которые трудно ввести с клавиатуры:

eacute = [0хЕ9].pack('U')

cafe = "caf#{eacute}" # "caf'e"

Регулярным выражениям тоже известно о многобайтовых символах, особенно если вы пользуетесь библиотекой Oniguruma (мы рассматривали ее в главе 3). Например, образец

/./
сопоставляется с одним многобайтовым символом.

Модификатор

u
извещает регулярное выражение о том, что мы работаем с кодировкой UTF-8. Если
$KCODE
равно
"u"
, то модификатор можно не задавать, однако это и не повредит. (К тому же такая избыточность может быть полезна, если код является частью большой программы, а какое значение переменной
$KCODE
в ней установлено, вам неизвестно.)

Даже без Oniguruma регулярные выражения распознают, относится ли данный многобайтовый символ к категории тех, что могут входить в состав слова:

$KCODE = "u"

sword =~ /\w/ #0

sword =~ /\W/ # nil

При наличии Oniguruma последовательности, начинающиеся с символа обратной косой черты (

\w
,
\s
и т.п.) распознают и более широкие диапазоны кодовых точек: слова, пропуски и т.д.

Регулярные выражения позволяют безопасно выполнять простые манипуляции со строками. Мы и так можем без труда усекать строки. Следующий код возвращает не более 20 символов из строки

ascii_string
:

ascii_string[0,20]

Однако, поскольку кодовая позиция Unicode может занимать более одного байта такую технику нельзя безопасно применять к строке в кодировке UTF-8. Есть риск, что в конце строки окажется недопустимая последовательность байтов. Кроме того, это не слишком полезно, так как мы не можем заранее сказать, сколько в результате получится кодовых позиций. На помощь приходят регулярные выражения:

def truncate(str, max_length)

 str[/.{0,#{max_length}}/m]

end

4.2.3. Распознавание кодировки

Распознать, в какой кодировке записана данная строка, довольно сложно. Многобайтовые кодировки обладают отличительными признаками, по которым их можно опознать, но с однобайтовыми — а именно они применяются в западных языках — дело обстоит куда хуже. Для решения можно применить статистические методы, но эта тема выходит за рамки данной книги (к тому же результат в общем случае получается не слишком надежным).

К счастью, обычно перед нами стоит более простая задача — выяснить, записана ли строка в кодировке UTF-8. На этот вопрос можно дать достаточно надёжный ответ. Приведем один способ (основанный на том, что метод

unpack
возбуждает исключение, если ему передана некорректная строка):

class String

 def utf8?

unpack('U*') rescue return false

true

 end

end

Поделиться с друзьями: