文字セットの複数のインスタンスでのPerl regex窒息
質問
私はPHPでpreg_replaceを使用していくつかのクレイジーな失敗から始め、それをトルコの点線「私」とゆるい「ı」を一緒に使用して複数のキャラクタークラスを持つという問題のケースに煮詰めました。 PHPの簡単なテストケースは次のとおりです。
<?php
echo 'match single normal i: ';
$str = 'mi';
echo (preg_match('!m[ıi]!', $str)) ? "ok\n" : "fail\n";
echo 'match single undotted ı: ';
$str = 'mı';
echo (preg_match('!m[ıi]!', $str)) ? "ok\n" : "fail\n";
echo 'match double normal i: ';
$str = 'misir';
echo (preg_match('!m[ıi]s[ıi]r!', $str)) ? "ok\n" : "fail\n";
echo 'match double undotted ı: ';
$str = 'mısır';
echo (preg_match('!m[ıi]s[ıi]r!', $str)) ? "ok\n" : "fail\n";
?>
そして、Perlで再び同じテストケース:
#!/usr/bin/perl
$str = 'mi';
$str =~ m/m[ıi]/ && print "match single normal i\n";
$str = 'mı';
$str =~ m/m[ıi]/ && print "match single undotted ı\n";
$str = 'misir';
$str =~ m/m[ıi]s[ıi]r/ && print "match double normal i\n";
$str = 'mısır';
$str =~ m/m[ıi]s[ıi]r/ && print "match double undotted ı\n";
最初の3つのテストは正常に機能します。最後のものは一致しません。
なぜこれがキャラクタークラスとして一度正常に機能するのに、同じ式で2回目ではなく動作するのはなぜですか?書かれた文字の組み合わせに関係なく、このような単語と一致するように式を書くにはどうすればよいですか?
編集: 背景 言語の問題 私はプログラムしようとしています。
編集2: 追加 use utf8;
ディレクティブはPERLバージョンを修正します。私の元の問題はPHPプログラムにあり、PERLに切り替えてPHPのバグかどうかを確認しただけなので、それは私をあまり助けません。 誰かがPHPをこれについて窒息させないようにする指令を知っていますか?
解決
Multibyteシーケンスは、UTF-8が8ビットバイトのシーケンスとして誤って解釈されている場合、ブラケット付きCharクラスで必要なことを行いません。それについて考えてください。もしも [nñm]
3つの論理文字としてではなく、4つの物理バイトとして誤解されているため、コードポイントが6EまたはC3またはB1または6Dである文字と一致します。
いくつかの目的のために、あなたは書き直しで逃げるかもしれません [nñm]
なので (?:n|ñ|m)
. 。それはあなたが何をしているかに依存します。ケーシングのものは機能しません。
また、Unicodeには、トルコのドットレスiの特別なケーシングルールがあります。
PHPは十分にモダンではないように聞こえます。はぁ。
他のヒント
ソースファイルにUTF8文字が含まれていることをPerlに伝える必要がある場合があります。試す:
#!/usr/bin/perl
use utf8; # **** Add this line
$str = 'mısır';
$str =~ m/m[ıi]s[ıi]r/ && print "match double undotted ı\n";
これはPHPに役立ちませんが、PHPに同様の指令があるかもしれません。それ以外の場合は、ソースコードにリテラルキャラクターを置くことを避けるために、何らかの形のエスケープシーケンスを使用してみてください。私はPHPについて何も知らないので、それを助けることはできません。
編集
私は、PHPにユニコードサポートがないことを読んでいます。したがって、渡すユニコード入力は、ユニコードがエンコードされたバイトの文字列として扱われる可能性があります。
あなたの入力がUTF-8として入力されていることを保証できる場合、あなたはUTF-8シーケンスに一致させることができます ı
それはそうです \xc4 \xb1
のように:
$str = 'mısır'; # Make sure this source-file is encoded as utf-8 or this match will fail
echo (preg_match('!m(i|\xc4\xb1)s(i|\xc4\xb1)r!', $str)) ? "ok\n" : "fail\n";
それは動作しますか?
もう一度編集:
最初の3つのテストが合格した理由を説明できます。あなたのエンコードでそれをふりかけましょう、 ı
ASとしてエンコードされています ABCDE
. 。次に、PHPは次のものを見ています。
echo 'match single normal i: ';
$str = 'mi';
echo (preg_match('!m[ABCDEi]!', $str)) ? "ok\n" : "fail\n";
echo 'match single undotted ABCDE: ';
$str = 'mABCDE';
echo (preg_match('!m[ABCDEi]!', $str)) ? "ok\n" : "fail\n";
echo 'match double normal i: ';
$str = 'misir';
echo (preg_match('!m[ABCDEi]s[ABCDEi]r!', $str)) ? "ok\n" : "fail\n";
echo 'match double undotted ABCDE: ';
$str = 'mABCDEsABCDEr';
echo (preg_match('!m[ABCDEi]s[ABCDEi]r!', $str)) ? "ok\n" : "fail\n";
これにより、最初の3つのテストが合格し、最後のテストが失敗する理由が明らかになります。開始/終了アンカーを使用する場合 ^...$
最初のテストのみが通過することになると思います。