フラグを使用しない場合、u
使用できる 16 進数の範囲は です[\x{00}-\x{ff}]
が、u
フラグを使用すると 4 バイト値\x{7fffffff}
( [\x{00000000}-\x{7fffffff}]
) まで拡張されます。
したがって、以下のコードを実行すると:
preg_match("/[\x{00000000}-\x{80000000}]+/u", $str, $match);
このエラーが発生します:
Warning: preg_match(): Compilation failed: character value in \x{...} sequence is too large
��
したがって、 のような文字を の16進値と一致させることはできませんf0 a1 83 81
。問題は、これらの文字をどのように一致させるかではなく、この範囲と境界がu
修飾子として文字列を次のように扱うべきである理由です。UTF-16
PCREはバージョン8.30以降UTF-16をサポートしています。
echo PCRE_VERSION;
PHP 5.3.24 - 5.3.28、5.4.14 - 5.5.7 の PCRE バージョン:
8.32 2012-11-30
PHP 5.3.19 - 5.3.23、5.4.9 - 5.4.13 の PCRE バージョン:
8.31 2012-07-06
ベストアンサー1
Unicode および UTF-8、UTF-16、UTF-32 エンコード
Unicode は文字からコード ポイントへのマッピングを指定する文字セットであり、文字エンコーディング (UTF-8、UTF-16、UTF-32) は Unicode コード ポイントの保存方法を指定します。
Unicode では、文字は単一のコード ポイントにマップされますが、エンコード方法に応じて異なる表現になる場合があります。
この議論をもう一度繰り返すつもりはないので、まだよくわからない場合は、以下をお読みください。すべてのソフトウェア開発者が Unicode と文字セットについて絶対に知っておく必要がある最低限の知識 (言い訳はなし!)。
質問の例を使用すると、��
コード ポイント にマップされますが、 UTF-8、UTF-16、およびUTF-32 でU+210C1
エンコードできます。F0 A1 83 81
D844 DCC1
000210C1
正確に言うと、上記の例はコードポイントをコードユニット(文字エンコード形式)にマッピングする方法を示しています。コードユニットをオクテットシーケンスにマッピングする方法は別の問題です。Unicode エンコード モデル
PCRE 8 ビット、16 ビット、32 ビット ライブラリ
PHP はまだ PCRE2 (バージョン 10.10) を採用していないため、引用されたテキストは元の PCRE のドキュメントからのものです。
16ビットおよび32ビットライブラリのサポート
PCRE には、デフォルトの 8 ビット ライブラリに加えて、バージョン 8.30 では 16 ビット文字列のサポート、バージョン 8.32 からは 32 ビット文字列のサポートが含まれています。
PCREは8ビット文字列のサポートに加えて、16ビット文字列(リリース8.30以降)と32ビット文字列(リリース8.32以降)もサポートしています。2つの追加ライブラリを介して。これらは、8 ビット ライブラリと同様に、または 8 ビット ライブラリの代わりに構築できます。 [...]
8ビット、16ビット、32ビットの意味
ここでの 8 ビット、16 ビット、32 ビットはデータ単位 (コード単位) を指します。
このドキュメント内のバイトと UTF-8 への参照は、特に指定がない限り、16 ビット ライブラリを使用する場合は 16 ビット データ ユニットと UTF-16 への参照として、32 ビット ライブラリを使用する場合は 32 ビット データ ユニットと UTF-32 への参照として読み取られます。16 ビット ライブラリと 32 ビット ライブラリの具体的な違いの詳細については、pcre16 および pcre32 のページを参照してください。
つまり、8 ビット/16 ビット/32 ビット ライブラリでは、パターンと入力文字列が 8 ビット/16 ビット/32 ビット データ ユニットのシーケンス、または有効な UTF-8/UTF-16/UTF-32 文字列であることを期待します。
データ単位の幅に応じて異なるAPI
PCRE は、8 ビット、16 ビット、32 ビットのライブラリに対して、プレフィックス (それぞれ、) で区別される 3 セットの同一 API を提供しpcre_
ます。pcre16_
pcre_32
16 ビットおよび 32 ビットの関数は、8 ビットの関数と同じように動作します。引数と結果に異なるデータ型を使用し、名前が ではなくまたは で
pcre16_
始まる点が異なります。名前に UTF8 が含まれるすべてのオプション (たとえば) には、UTF8 がそれぞれ UTF16 または UTF32 に置き換えられた、対応する 16 ビットおよび 32 ビットの名前があります。この機能は実際には単なる表面的なものであり、16 ビットおよび 32 ビットのオプション名は同じビット値を定義します。pcre32_
pcre_
PCRE_UTF8
PCRE2では、同様の関数命名規則が使用されるここで、8 ビット/16 ビット/32 ビット関数にはそれぞれ_8
、、_16
の_32
接尾辞が付きます。1 つのコード単位幅のみを使用するアプリケーションでは、接尾辞なしで関数の汎用名を使用するように定義できますPCRE2_CODE_UNIT_WIDTH
。
UTF モードと非 UTF モード
UTF モードが設定されている場合(パターン内オプション(*UTF)
、、、1またはコンパイル オプション、、経由)、すべてのデータ ユニットのシーケンスは、サロゲートと BOM を除く、U+0000 から U + 10FFFF までのすべてのコード ポイントで構成される Unicode 文字のシーケンスとして解釈されます。(*UTF8)
(*UTF16)
(*UTF32)
PCRE_UTF8
PCRE_UTF16
PCRE_UTF32
1パターン内オプション(*UTF8)
、(*UTF16)
は、(*UTF32)
対応するライブラリでのみ使用できます。 は、(*UTF16)
8 ビット ライブラリでは使用できません。また、一致しない組み合わせも意味をなさないため、使用できません。 は(*UTF)
すべてのライブラリで使用でき、パターン内で UTF モードを指定するための移植可能な方法を提供します。
UTFモードでは、パターン(データ単位のシーケンス)は解釈されたそして検証済みコンパイルされる前に、シーケンスを UTF-8/UTF-16/UTF-32 データ (使用する API によって異なります) としてデコードすることにより、Unicode コード ポイントのシーケンスとして解釈されます。入力文字列は、マッチング プロセス中に Unicode コード ポイントのシーケンスとしても解釈され、オプションで検証されます。このモードでは、文字クラスは 1 つの有効な Unicode コード ポイントと一致します。
一方でUTFモードが設定されていない場合(非UTFモード)、すべての操作はデータユニットシーケンスに対して直接実行されます。このモードでは、文字クラスは1つのデータユニットと一致し、1つのデータユニットに格納できる最大値を除いて、データユニットの値に制限はありません。このモードは、バイナリデータ内の構造のマッチングに使用できます。ただし、Unicode文字を扱う場合はこのモードを使用しないでくださいただし、ASCII で十分で、他の言語を無視する場合は別です。
文字値の制約
8 進数または 16 進数を使用して指定される文字は、次のように特定の値に制限されます。
8-bit non-UTF mode less than 0x100 8-bit UTF-8 mode less than 0x10ffff and a valid codepoint 16-bit non-UTF mode less than 0x10000 16-bit UTF-16 mode less than 0x10ffff and a valid codepoint 32-bit non-UTF mode less than 0x100000000 32-bit UTF-32 mode less than 0x10ffff and a valid codepoint
無効な Unicode コードポイントは、0xd800 から 0xdfff までの範囲 (いわゆる「サロゲート」コードポイント) と 0xffef です。
PHP と PCRE
のPCRE関数PHPでは次のように実装されていますPHP固有のフラグと呼び出しをPCRE APIに変換するラッパー(PHP 5.6.10 ブランチで確認)。
ソース コードは PCRE 8 ビット ライブラリ API ( pcre_
) を呼び出すため、関数に渡される文字列はpreg_
8 ビット データ ユニット (バイト) のシーケンスとして解釈されます。したがって、PCRE 16 ビットおよび 32 ビット ライブラリがビルドされていても、PHP 側の API 経由ではまったくアクセスできません。
その結果、PHP の PCRE 関数は次のことを期待します。
- ... 非 UTF モード (デフォルト) のバイト配列。ライブラリはこれを 8 ビットの「文字」として読み取り、8 ビットの「文字」の文字列と一致するようにコンパイルします。
- ... UTF-8 でエンコードされた Unicode 文字列を含むバイト配列。ライブラリはこれを Unicode 文字で読み取り、UTF-8 Unicode 文字列と一致するようにコンパイルします。
これは、質問に見られる動作を説明します。
- 非UTFモード(
u
フラグなし)では、16進正規表現エスケープシーケンスの最大値はFFです(図を参照[\x{00}-\x{ff}]
)。 - UTF モードでは、16 進正規表現エスケープ シーケンス内の 0x10ffff を超える値 ( など
\x{7fffffff}
) は意味をなしません。
サンプルコード
このサンプルコードは次のことを示しています。
- PHP 文字列は単なるバイト配列であり、エンコードについては何も理解しません。
- PCRE 関数における UTF モードと非 UTF モードの違い。
- PCRE 関数は 8 ビット ライブラリに呼び出されます
// NOTE: Save this file as UTF-8
// Take note of double-quoted string literal, which supports escape sequence and variable expansion
// The code won't work correctly with single-quoted string literal, which has restrictive escape syntax
// Read more at: https://php.net/language.types.string
$str_1 = "\xf0\xa1\x83\x81\xf0\xa1\x83\x81";
$str_2 = "����";
$str_3 = "\xf0\xa1\x83\x81\x81\x81\x81\x81\x81";
echo ($str_1 === $str_2)."\n";
var_dump($str_3);
// Test 1a
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_1, $match);
print_r($match); // Only match ��
// Test 1b
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_2, $match);
print_r($match); // Only match �� (same as 1a)
// Test 1c
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_3, $match);
print_r($match); // Match �� and the five bytes of 0x81
// Test 2a
$match = null;
preg_match("/��+/", $str_1, $match);
print_r($match); // Only match �� (same as 1a)
// Test 2b
$match = null;
preg_match("/��+/", $str_2, $match);
print_r($match); // Only match �� (same as 1b and 2a)
// Test 2c
$match = null;
preg_match("/��+/", $str_3, $match);
print_r($match); // Match �� and the five bytes of 0x81 (same as 1c)
// Test 3a
$match = null;
preg_match("/\xf0\xa1\x83\x81+/u", $str_1, $match);
print_r($match); // Match two ��
// Test 3b
$match = null;
preg_match("/\xf0\xa1\x83\x81+/u", $str_2, $match);
print_r($match); // Match two �� (same as 3a)
// Test 4a
$match = null;
preg_match("/��+/u", $str_1, $match);
print_r($match); // Match two �� (same as 3a)
// Test 4b
$match = null;
preg_match("/��+/u", $str_2, $match);
print_r($match); // Match two �� (same as 3b and 4a)
PHP 文字列は単なるバイト配列なので、ファイルが ASCII 互換のエンコードで正しく保存されている限り、PHP は元のエンコードを気にすることなくバイトを読み取ります。文字列を正しくエンコードおよびデコードする責任は完全にプログラマにあります。
- テキストを扱うために、すべてのプログラマーが絶対に知っておく必要があるエンコーディングと文字セットについて(セクション「PHP のエンコーディング処理の使用と悪用」)
- 文字セット / 文字エンコーディングの問題
- PHP 文字セットに関する FAQ - ソース ファイルにはどのエンコーディングを使用すればよいですか?
上記の理由により、上記のファイルを UTF-8 エンコードで保存すると、$str_1
と$str_2
が同じ文字列であることがわかります。$str_1
はエスケープ シーケンスからデコードされ、 は$str_2
ソース コードからそのまま読み取られます。 その結果、"/\xf0\xa1\x83\x81+/u"
と は、"/��+/u"
その下にある同じ文字列になります ("/\xf0\xa1\x83\x81+/"
との場合も同様です"/��+/"
)。
UTF モードと非 UTF モードの違いは、上記の例で明確に示されています。
"/��+/"
F0 A1 83 81 2B
は、文字が 1 バイトである文字列として認識されます。したがって、結果の正規表現はF0 A1 83
、バイト81
が 1 回以上繰り返されるシーケンスに一致します。"/��+/u"
UTF-8 文字のシーケンスとして検証および解釈されます。したがって、結果の正規表現は、UTF-8 文字列内で 1 回以上繰り返されるU+210C1 U+002B
コード ポイントと一致します。U+210C1
一致するUnicode文字
入力に他のバイナリ データが含まれていない限り、常にu
モードをオンにすることを強くお勧めします。パターンは、Unicode 文字を適切に一致させるためのすべての機能にアクセスでき、入力とパターンの両方が有効な UTF 文字列として検証されます。
もう一度、��
例として、上記の例では正規表現を指定する 2 つの方法を示しています。
"/\xf0\xa1\x83\x81+/u"
"/��+/u"
最初の方法は、一重引用符で囲まれた文字列では機能しません。\x
一重引用符ではエスケープ シーケンスが認識されないため、ライブラリは文字列 を受け取ります。\xf0\xa1\x83\x81+
これは、UTF モードと組み合わされて、U+00F0 U+00A1 U+0083
に続いてU+0081
1 回以上繰り返されたものと一致します。それだけでなく、コードを読む次の人にとっても混乱を招きます。1 文字以上の Unicode 文字が 1 つだけ繰り返されていることを、どうやって知ることができるのでしょうか。
2番目の方法はうまく機能し、シングルクォート文字列でも使用できますが、UTF-8エンコードでファイルを保存する必要があります。特に、 のような文字の場合はÿ
、文字がシングルバイトエンコードでも有効であるためです。この方法は、単一の文字または一連の文字に一致させたい場合のオプションです。ただし、文字範囲の終了点として、一致させようとしているものが明確でない場合があります。a-z
、A-Z
、0-9
、を(ほとんどの文字に一致します)א-ת
と比較してください。一-龥
CJK 統合漢字ブロック (4E00–9FFF)(末尾の未割り当てコード ポイントを除く) または一-十
(1 から 10 までの数字に中国語の文字を一致させようとする誤った試みです)。
3 番目の方法は、16 進エスケープでコード ポイントを直接指定することです。
"/\x{210C1}/u"
'/\x{210C1}/u'
これは、ファイルが ASCII 互換のエンコードで保存されている場合に機能し、一重引用符と二重引用符の両方の文字列で機能し、文字範囲内の明確なコード ポイントも提供します。この方法には、文字がどのように見えるかわからないという欠点があり、Unicode 文字のシーケンスを指定するときにも読みにくくなります。