[解決済み] Java正規表現でUnicodeに対応するのは?
質問
最近の多くの正規表現実装は
\w
という文字クラスの略記を解釈します。そのため、以下のような正規表現は
\w+
のような単語にマッチします。
hello
,
élève
,
GOÄ_432
または
gefräßig
.
残念ながら、Javaはそうではありません。Javaでは
\w
は制限され
[A-Za-z0-9_]
. このため、上記のような単語のマッチングが困難であるなどの問題があります。
また、どうやら
\b
というワードセパレータが、マッチしないはずの場所でもマッチしているように見えます。
.NETライクでUnicode対応の
\w
または
\b
をJavaで使うことはできますか?他のどのショートカットがUnicodeに対応させるために書き換えが必要ですか?
どのように解決するのですか?
ソースコード
以下で説明する書き換え関数のソースコードです。 はここで入手できます。 .
Java 7でのアップデート
サンが更新した
Pattern
クラスには、すばらしい新しいフラグがあります。
UNICODE_CHARACTER_CLASS
という素晴らしいフラグがあり、これによってすべてが再び正しく動作するようになります。これは、埋め込み可能な
(?U)
として埋め込むことができます。
String
クラスのラッパーにも使用できます。 また、他の様々なプロパティの定義も修正されています。 これは、Unicode Standard を追跡するようになりました。
RL1.2
と
RL1.2a
から
UTS#18: ユニコード正規表現
. これはエキサイティングで劇的な改善であり、開発チームのこの重要な努力は賞賛されるべきものです。
Java の正規表現のユニコード問題
Java の正規表現における問題は、Perl 1.0 の charclass エスケープ、つまり
\w
,
\b
,
\s
,
\d
およびその補集合は、JavaではUnicodeで動作するように拡張されていません。 これらの中で単独で
\b
はある種の拡張されたセマンティクスを享受していますが、これらはどちらも
\w
にも、また
ユニコード識別子
も、また
Unicodeの改行プロパティ
.
さらに、JavaのPOSIXプロパティはこのようにアクセスします。
POSIX syntax Java syntax
[[:Lower:]] \p{Lower}
[[:Upper:]] \p{Upper}
[[:ASCII:]] \p{ASCII}
[[:Alpha:]] \p{Alpha}
[[:Digit:]] \p{Digit}
[[:Alnum:]] \p{Alnum}
[[:Punct:]] \p{Punct}
[[:Graph:]] \p{Graph}
[[:Print:]] \p{Print}
[[:Blank:]] \p{Blank}
[[:Cntrl:]] \p{Cntrl}
[[:XDigit:]] \p{XDigit}
[[:Space:]] \p{Space}
これは本当に厄介なことで、例えば
Alpha
,
Lower
そして
Space
する
ではない
は、Javaではユニコードにマッピングされます。
Alphabetic
,
Lowercase
または
Whitespace
のようなプロパティがあります。これは非常に迷惑な話です。Java の Unicode プロパティサポートは
厳格な反時代的なもので
で、つまり、過去 10 年間に登場したどの Unicode プロパティもサポートしていません。
空白文字について適切に話すことができないのは、非常に腹立たしいことです。 次の表を見てください。これらのコード ポイントのそれぞれについて、Java の J-results 列と P-results 列の両方が存在します。 列があり、Perl やその他の PCRE ベースの正規表現エンジンの場合は P-結果列があります。
Regex 001A 0085 00A0 2029
J P J P J P J P
\s 1 1 0 1 0 1 0 1
\pZ 0 0 0 0 1 1 1 1
\p{Zs} 0 0 0 0 1 1 0 0
\p{Space} 1 1 0 1 0 1 0 1
\p{Blank} 0 0 0 0 0 1 0 0
\p{Whitespace} - 1 - 1 - 1 - 1
\p{javaWhitespace} 1 - 0 - 0 - 1 -
\p{javaSpaceChar} 0 - 0 - 1 - 1 -
ご覧ください。
Java のホワイトスペースの結果は、事実上すべて Unicode に従って ̲w̲r̲o̲n̲g̲ となっています。 になっているのです。 本当に大きな問題です。 Java はとにかくメチャクチャで、既存の慣習に従って、しかも Unicode に従って「間違っている」答えを出してしまうのです。 さらに、Java は本当の Unicode プロパティにアクセスすることさえできません! 実際、Javaは 任意の プロパティをサポートしていません。
これらの問題に対する解決策、およびその他
この問題やその他多くの関連する問題に対処するために、私は昨日、これら 14 の charclass エスケープを書き換えるパターン文字列を書き換える Java 関数を書きました。
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
を、予測可能で一貫した方法でUnicodeにマッチするように実際に動作するものに置き換えることによって、これを実現しました。これは、1 回のハッキング セッションでできたアルファ プロトタイプにすぎませんが、完全に機能します。
手短に言うと、私のコードはこれらの 14 を次のように書き直しました。
\s => [\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]
\S => [^\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]
\v => [\u000A-\u000D\u0085\u2028\u2029]
\V => [^\u000A-\u000D\u0085\u2028\u2029]
\h => [\u0009\u0020\u00A0\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]
\H => [^\u0009\u0020\u00A0\u1680\u180E\u2000\u2001-\u200A\u202F\u205F\u3000]
\w => [\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
\W => [^\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
\b => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))
\B => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))
\d => \p{Nd}
\D => \P{Nd}
\R => (?:(?>\u000D\u000A)|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])
\X => (?>\PM\pM*)
考慮すべきいくつかの点...
-
は、その
\X
定義 何 ユニコードは現在 として レガシー書記素クラスタ ではなく 拡張書記素クラスタ ではなく、後者の方がより複雑だからです。Perl 自身は現在、よりファンシーなバージョンを使っていますが、古いバージョンでも最も一般的な状況では完全に動作可能です。 EDITです。 一番下の追記をご覧ください。 -
についてどうするか
\d
はあなたの意図によりますが、デフォルトはUniodeの定義です。私は、人々が常に\p{Nd}
が必要なわけではなく、時には[0-9]
または\pN
. -
2つの境界の定義です。
\b
と\B
は、特に\w
の定義を使うように書かれています。 -
その
\w
の定義は広すぎます。なぜなら、丸で囲んだ文字だけでなく、括弧で囲んだ文字も捕捉してしまうからです。ユニコードのOther_Alphabetic
プロパティは JDK7 まで使用できないので、これが最善の方法です。
境界を探る
境界は、Larry Wall が最初に「境界」という言葉を作って以来、ずっと問題になっています。
\b
と
\B
の構文は、1987年にPerl 1.0のためにそれらについて話したものです。を理解する鍵は
\b
と
\B
の2つの作品は、それらにまつわる2つの神話を払拭するためのものです。
-
それらは
だけを見て
のために
\w
の単語文字だけを探しています。 は決して を使用します。 - 特に文字列の端を探すことはありません。
A
\b
の境界を意味する。
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
というように、これらはすべて完全に素直に定義されています。
-
単語に従う
は
(?<=\w)
. -
の前にある単語
は
(?=\w)
. -
は単語を追わない
は
(?<!\w)
. -
が単語の前にない場合
は
(?!\w)
.
したがって、以下のように
IF-THEN
としてエンコードされます。
and
ed-together
AB
は、正規表現では
or
は
X|Y
であるため、また
and
よりも優先順位が高いので
or
というのは、単に
AB|CD
. ですから,すべての
\b
で置き換えることができます。
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
を
\w
を適切な方法で定義してください。
(不思議に思うかもしれませんが
A
と
C
のコンポーネントは反対である。完璧な世界では、次のように書くことができるはずです。
AB|D
と書けるはずなのですが、しばらくの間、私は Unicode プロパティの相互排除の矛盾を追いかけていました - これは私が
と思う
と思っているのですが、念のため境界の二重条件を残しておきました。さらに、これは、後で余分なアイデアを得た場合に、より拡張可能なものになります)。
については
\B
の非境界では、ロジックは
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
のすべてのインスタンスを許可する。
\B
に置き換える。
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
これは本当にどのように
\b
と
\B
を動作させる。 これらに相当するパターンは
-
\b
を使用して((IF)THEN|ELSE)
の構成は(?(?<=\w)(?!\w)|(?=\w))
-
\B
を使用して((IF)THEN|ELSE)
の構成は(?(?=\w)(?<=\w)|(?<!\w))
しかし
AB|CD
のみのバージョンもありますが、特にJavaのような正規表現言語で条件付きパターンがない場合は、問題ありません。 ☹
私はすでに、1 回の実行で 110,385,408 件の一致をチェックするテスト スイートで、3 つの同等の定義すべてを使用して境界の動作を検証しており、それに従って 12 の異なるデータ構成で実行しました。
0 .. 7F the ASCII range
80 .. FF the non-ASCII Latin1 range
100 .. FFFF the non-Latin1 BMP (Basic Multilingual Plane) range
10000 .. 10FFFF the non-BMP portion of Unicode (the "astral" planes)
しかし、人々はしばしば別の種類の境界を求めます。彼らは、空白と文字列の端を認識するものを求めています。
-
左端
として
(?:(?<=^)|(?<=\s))
-
右端
として
(?=$|\s)
Java を Java で修正する
で投稿したコードは で投稿したコード で投稿したコードは、これとかなり多くの他の便利な機能を提供します。これには、自然言語の単語、ダッシュ、ハイフン、アポストロフィの定義と、もう少しのものが含まれています。
また、Unicode 文字をバカげた UTF-16 サロゲートではなく、論理的なコードポイントで指定することができます。 それがどれほど重要であるか、強調しすぎることはありません! そして、それは文字列展開のためだけです。
Java正規表現でcharclassを作る正規表現charclass置換のために 最終的に は Unicode で動作します。 で、正しく動作します。 つかむ ここから完全なソースを取得します。 . もちろん、あなたの好きなように使ってください。もし、あなたが修正したら、私はそれを聞きたいのですが、あなたはそうする必要はありません。かなり短いです。メインの正規表現書き換え関数のガワはシンプルです。
switch (code_point) {
case 'b': newstr.append(boundary);
break; /* switch */
case 'B': newstr.append(not_boundary);
break; /* switch */
case 'd': newstr.append(digits_charclass);
break; /* switch */
case 'D': newstr.append(not_digits_charclass);
break; /* switch */
case 'h': newstr.append(horizontal_whitespace_charclass);
break; /* switch */
case 'H': newstr.append(not_horizontal_whitespace_charclass);
break; /* switch */
case 'v': newstr.append(vertical_whitespace_charclass);
break; /* switch */
case 'V': newstr.append(not_vertical_whitespace_charclass);
break; /* switch */
case 'R': newstr.append(linebreak);
break; /* switch */
case 's': newstr.append(whitespace_charclass);
break; /* switch */
case 'S': newstr.append(not_whitespace_charclass);
break; /* switch */
case 'w': newstr.append(identifier_charclass);
break; /* switch */
case 'W': newstr.append(not_identifier_charclass);
break; /* switch */
case 'X': newstr.append(legacy_grapheme_cluster);
break; /* switch */
default: newstr.append('\\');
newstr.append(Character.toChars(code_point));
break; /* switch */
}
saw_backslash = false;
とにかく、このコードはアルファ版のリリースで、週末にハックしたものです。このままではありません。
ベータ版ではそうするつもりです。
-
重複するコードをまとめる
-
文字列エスケープの解除と正規表現エスケープの拡張について、より明確なインターフェイスを提供します。
-
での柔軟性を提供する。
\d
の拡張、そしておそらく\b
-
Pattern.compileやString.matchesなどを呼び出すための便利なメソッドを提供します。
製品版リリースには、javadocとJUnitテストスイートが必要です。私のgigatesterを入れるかもしれませんが、JUnitのテストとして書かれているわけではありません。
追記
良い知らせと悪い知らせがあります。
良いニュースは、今、私が
とても
に近いものができたということです。
拡張書記素クラスタ
を使用して、改良された
\X
.
悪い知らせ☺は、そのパターンが
(?:(?:\u000D\u000A)|(?:[\u0E40\u0E41\u0E42\u0E43\u0E44\u0EC0\u0EC1\u0EC2\u0EC3\u0EC4\uAAB5\uAAB6\uAAB9\uAABB\uAABC]*(?:[\u1100-\u115F\uA960-\uA97C]+|([\u1100-\u115F\uA960-\uA97C]*((?:[[\u1160-\u11A2\uD7B0-\uD7C6][\uAC00\uAC1C\uAC38]][\u1160-\u11A2\uD7B0-\uD7C6]*|[\uAC01\uAC02\uAC03\uAC04])[\u11A8-\u11F9\uD7CB-\uD7FB]*))|[\u11A8-\u11F9\uD7CB-\uD7FB]+|[^[\p{Zl}\p{Zp}\p{Cc}\p{Cf}&&[^\u000D\u000A\u200C\u200D]]\u000D\u000A])[[\p{Mn}\p{Me}\u200C\u200D\u0488\u0489\u20DD\u20DE\u20DF\u20E0\u20E2\u20E3\u20E4\uA670\uA671\uA672\uFF9E\uFF9F][\p{Mc}\u0E30\u0E32\u0E33\u0E45\u0EB0\u0EB2\u0EB3]]*)|(?s:.))
というように、Javaではこう書きます。
String extended_grapheme_cluster = "(?:(?:\\u000D\\u000A)|(?:[\\u0E40\\u0E41\\u0E42\\u0E43\\u0E44\\u0EC0\\u0EC1\\u0EC2\\u0EC3\\u0EC4\\uAAB5\\uAAB6\\uAAB9\\uAABB\\uAABC]*(?:[\\u1100-\\u115F\\uA960-\\uA97C]+|([\\u1100-\\u115F\\uA960-\\uA97C]*((?:[[\\u1160-\\u11A2\\uD7B0-\\uD7C6][\\uAC00\\uAC1C\\uAC38]][\\u1160-\\u11A2\\uD7B0-\\uD7C6]*|[\\uAC01\\uAC02\\uAC03\\uAC04])[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]*))|[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]+|[^[\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cf}&&[^\\u000D\\u000A\\u200C\\u200D]]\\u000D\\u000A])[[\\p{Mn}\\p{Me}\\u200C\\u200D\\u0488\\u0489\\u20DD\\u20DE\\u20DF\\u20E0\\u20E2\\u20E3\\u20E4\\uA670\\uA671\\uA672\\uFF9E\\uFF9F][\\p{Mc}\\u0E30\\u0E32\\u0E33\\u0E45\\u0EB0\\u0EB2\\u0EB3]]*)|(?s:.))";
ツッコミどころ満載!
関連
-
無効な文字定数
-
アクセス制限の解決方法: ---- in Java
-
[解決済み] JavaにおけるHashMapとHashtableの違いは何ですか?
-
[解決済み] Javaにおけるpublic、protected、package-private、privateの違いは何ですか?
-
[解決済み] Javaで配列を宣言し、初期化する方法は?
-
[解決済み] 正規表現における非捕捉グループとは何ですか?
-
[解決済み] Java内部クラスと静的ネストされたクラス
-
[解決済み] Javaの「for each」ループはどのように機能するのですか?
-
[解決済み] 特定のUnicode文字を含むコメントでのJavaコードの実行が許可されているのはなぜですか?
-
[解決済み] jQueryセレクタの正規表現
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Javaクラスが "Error occurred during initialization of boot layer "というエラーで実行される。
-
-bash: java: コマンドが見つからない 解決方法
-
Junitのユニットテストはjava.lang.Testを報告します。
-
keytool error: java.io.FileNotFoundException: cacerts (アクセス拒否されました。)
-
FindBugの使用概要
-
API の戻り値を処理するために ResponseEntity を使用する
-
代入の左辺は変数でなければならない 解答
-
Java Runtime Environmentを継続するためのメモリが不足しています。
-
Java の double データ型における 0.0 と -0.0 の問題
-
ApiModel と @ApiModelProperty の使用法