Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 [ 68 ] 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242

6.18. Поиск многобайтовых символов 221

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

Perl исходит из предположения, что один байт соответствует одному символу. В ASCII все работает нормально, но поиск по шаблону в строках, содержащих многобайтовые символы, - задача по меньшей мере нетривиальная. Механизм поиска не понимает, где в последовательности байтов расположены границы символов, и может вернуть «совпадения» от середины одного символа до середины другого.

Решение

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

Комментарий

в качестве примера мы рассмотрим одну из кодировок японского языка, EUC-JP, и разберемся, как воспользоваться ей для решения многих проблем, связанных с многобайтовыми символами. В EUC-JP можно представить тысячи символов, но в сущности эта кодировка является надмножеством ASCII. Байты с О по 127 (0x00 - 0x7F) почти точно совпадают с ASCII-аналогами и соответствуют однобайтовым символам. Некоторые символы представляются двумя байтами; первый байт равен 0х8Е, а второй принимает значения из интервала OxAO-OxDF. Другие символы представляются тремя байтами; первый байт равен Ox8F, а остальные принадлежат интервалу OxAI-OxFE. Наконец, часть символов представляется двумя байтами, каждый из которых принадлежит интервалу OxAI-OxFE.

Исходя из этих данных, можно построить регулярное выражение. Для удобства последующего применения мы определим строку $eucjp с регулярным выражением, которое совпадает с одним символом кодировки EUC-JP;

my $euc]p = q{ # Компоненты кодировки EUC-JP

[\xOO-\x7F] # ASCII/JIS-Roman (один байт/символ)

I \x8E[\xA0-\xDF] tt катакана половинной ширины (два байта/символ)

I \x8F[\xA1-xFE][\xA1-\xFE] tt JIS X 0212-1990 (три байта/символ) I [\xA1-\xFE][\xA1-\xFE] # JIS X 0208 1997 (два байта/символ)

(строка содержит комментарии и пропуски, поэтому при ее использовании для поиска или замены необходимо указывать модификатор /х). Располагая этим шаблоном, мы расскажем, как:

• Выполнить обычный поиск без «ложных» совпадений.

• Подсчитать, преобразовать (в другую кодировку) и/или отфильтровать символы.

• Убедиться в том, что проверяемый текст содержит символы данной кодировки.

• Узнать, какая кодировка используется в некотором тексте.



Во всех приведенных примерах используется кодировка EUC-JP, однако они будут работать и в большинстве других распространенных многобайтовых кодировок, встречающихся при обработке текстов - например, Unicode, Big-5 и т. д.

Страховка от ложных совпадений

Ложное совпадение происходит, когда найденное совпадение приходится на середину многобайтового представления одного символа. Чтобы избежать ложных совпадений, необходимо контролировать процесс поиска и следить, чтобы механизм поиска синхронизировался с границами символов.

Для этого можно связать шаблон с началом строки и вручную пропустить байты, для которых в текущей позиции не может произойти нормальное совпадение. В примере с EUC-JP за «пропуск символов» отвечает часть шаблона /(? :$еис]р).?/. $eucjp совпадает с любым допустимым символом. Поскольку он применяется с минимальным квантификатором совпадение возможно лишь в том случае, если не совпадает то, что идет после него (искомый текст). Рассмотрим реальный пример:

/" (?: Seucjp )*? \xC5\xEC\xB5\xFE/ox П Пытаемся найти Токио

В кодировке EUC-JP японское название Токио записывается двумя символами - первый кодируется двумя байтами \xC5\xEC, а второй - двумя байтами \xB5\xFE. С точки зрения Perl мы имеем дело с обычной 4-байтовой последовательностью \xC5\xEC\xB5\xFE. Однако, поскольку использование (:$еис]р)*? обеспечивает перемещение в строке только по символам целевой кодировки, мы знаем, что синхронизация сохраняется.

Не забывайте о модификаторах /ох. Модификатор /х особенно важен из-за наличия пропусков в шаблоне $еис]р. Модификатор /о повышает эффективность, поскольку значение $eucjp заведомо остается неизменным.

Аналогично выполняется и замена, но поскольку текст перед настоящим совпадением также является частью общего совпадения, мы должны заключить его в круглые скобки и включить в заменяющую строку. Предположим, переменным $Токуо и $Osaka были присвоены последовательности байтов с названиями городов Токио и Осака в кодировке EUC-JP.Замена Токио на Осаку происходит следующим образом:

/ ( (?:eucip).? ) $Tokvo/$10saka/ox

При использовании модификатора /д поиск должен быть привязан не к началу строки, а к концу предыдущего совпадения. Для этого достаточно заменить " на \G:

/\G ( (:eucjp)? ) $Tokyo/$10saka/gox

Разделение строк в многобайтовой кодировке

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

©chars = /$eucjp/gox; # По одному символу на каждый элемент списка



6.18. Поиск многобайтовых символов 223

Теперь каждый элемент ©chars содержит один символ строки. В следующем фрагменте этот прием используется для создания фильтра:

while (о) {

ту @chars = /$eucjp/gox; # Каждый элемент списка содержит один символ for my $char (@chars) { If (length($char) == 1) {

# Сделать что-то интересное с однобайтовым символом } else {

# Сделать что-то интересное с многобайтовым символом

ту Sline = ]oin("",@chars); # Объединить символы списка в строке print Sline;

Любые изменения $char в двух фрагментах, где происходит «что-то интересное», отражаются на выходных данных при объединении символов @chars.

Проверка многобайтовых строк

Успешная работа приемов, подобных /$eucjp/gox, существенно зависит от правильного форматирования входных строк в предполагаемой кодировке (EUC-JP). Если кодировка не соблюдается, шаблон /$еис] р/ не будет работать, что приведет к пропуску байтов.

Одно из возможных решений - использование /\G$eucjp/gox. Этот шаблон запрещает механизму поиска пропускать байты при поиске совпадений (модификатор \G означает, что новое совпадение должно находиться сразу же после предыдущего). Но и такой подход не идеален, потому что он просто прекращает выдавать совпадения для входных данных неправильного формата.

Более удачный способ убедиться в правильности кодировки строки - воспользоваться конструкцией следующего вида:

$is eucjp = m/~(:Seucjp).S/xo;

Если строка от начала до конца состоит только из допустимых символов, значит, она имеет правильную кодировку.

И все же существует потенциальная проблема, связанная с особенностями работы метасимвола конца строки $: совпадения возможны как в конце строки (что нам и требуется), так и перед символом перевода строки в ее конце. Следовательно, успешное совпадение возможно даже в том случае, если символ перевода строки не является допустимым в кодировке. Проблема решается заменой $ более сложной конструкцией (?!\п).

Базовая методика проверки позволяет определить кодировку. Например, японский текст обычно кодируется либо в EUC-JP, либо в другой кодировке, которая называется Shift-JIS. Имея шаблоны $eucjp и $sjis, можно определить кодировку следующим образом:

$is euc]p = m/"(?:$eucjp)«$/xo; $is s]is = m/"(?:$s]is)«$/xo;



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 [ 68 ] 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242