Java正規表現の\ wおよび\ bに相当するUnicodeですか?


126

多くの最近の正規表現の実装では、\w文字クラスの省略形を「任意の文字、数字、または接続句読点」(通常はアンダースコア)として解釈します。そのように、のような正規表現\w+の言葉が好きな試合helloélèveGOÄ_432またはgefräßig

残念ながら、Javaにはありません。Javaでは、\wに制限されてい[A-Za-z0-9_]ます。これにより、上記のような単語のマッチングが困難になります。

また、 \b単語セパレーターが一致してはならない場所で一致しているように見えます。

.NETのような、Unicode対応の、\wまたは\bJava の正しい同等物は何ですか?Unicode対応にするために「書き換え」が必要な他のショートカットはどれですか。


3
短い話、Timは、Unicodeに合わせるためにすべての人が書く必要があるということです。Java 1.7がUnicodeプロパティを使用してスクリプトのサポートを最終的に追加する以上のことを行う兆候はまだありませんが、それだけです。Unicodeプロパティの完全な補完機能へのより良いアクセスなしに、あなたが本当にできないいくつかのことがあります。私のunipropsunicharsスクリプト(およびuninames)をまだ持っていない場合は、これらすべて驚かされます。
tchrist

単語クラスにマークを追加することを検討するかもしれません。たとえばä \ u0061 \ u0308または\ u00E4としてUnicodeで表すことができます。
モストウスキー崩壊

3
ティムさん、アップデートをチェックしてください。彼らはそれをすべて機能させるためにフラグを追加しました。ばんざーい!
tchrist

回答:


240

ソースコード

以下説明する書き換え関数のソースコードは、こちらから入手できます

Java 7での更新

PatternJDK7用のSunの更新されたクラスには、驚くべき新しいフラグがありUNICODE_CHARACTER_CLASS、すべてが再び正常に機能します。(?U)パターンの内部に埋め込むことができるので、Stringクラスのラッパーと一緒に使用することもできます。また、他のさまざまなプロパティの定義も修正されています。現在、UTS#18:Unicode Regular ExpressionsのRL1.2RL1.2aの両方で、Unicode標準を追跡しています。これはエキサイティングで劇的な改善であり、開発チームはこの重要な取り組みに対して表彰されます。


Javaの正規表現Unicodeの問題

意味は- Javaの持つ問題の正規表現は、Perl 1.0 charclassエスケープということで\w\b\s\dJavaでのUnicodeで動作するように拡張されていない-とそれらの相補体。これらの中で単独で、\b特定の拡張されたセマンティクスを享受しますが、これら\wはにも、Unicode識別子にも、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}

それは物事が好きなことを意味するので、これは本当の混乱でAlphaLowerSpaceやるではない UnicodeへのJavaマップにAlphabeticLowercaseまたはWhitespaceプロパティ。これは非常に迷惑です。JavaのUnicodeプロパティのサポートは厳密に先駆的ですです。つまり、過去10年間に登場したUnicodeプロパティはサポートされていません。

ホワイトスペースについて適切に話すことができないことは、非常に迷惑です。次の表を検討してください。これらのコードポイントごとに、JavaのJ-results列と、Perlまたはその他のPCREベースの正規表現エンジンのP-results列の両方があります。

             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  -

あれ?

Unicodeによると、Javaホワイトスペースの結果のほぼすべてが「w̲r̲o̲n̲g」です。それは本当に大きな問題です。 Javaはめちゃくちゃになっていて、既存の慣習やUnicodeに従って「間違った」答えを出します。さらに、Javaでは実際のUnicodeプロパティにアクセスすることさえできません!実際には、Javaがサポートされていない任意の Unicodeの空白文字に対応することをプロパティを。


これらすべての問題の解決策など

これと他の多くの関連する問題に対処するために、昨日、これらの14の文字クラスエスケープを書き換えるパターン文字列を書き換えるJava関数を書きました。

\w \W \s \S \v \V \h \H \d \D \b \B \X \R

それらを、予測可能で一貫性のある方法でUnicodeに一致するように実際に機能するものに置き換えることによって。単一のハックセッションからのアルファプロトタイプにすぎませんが、完全に機能します。

短い話は、私のコードはこれらの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ものを定義Unicodeは今を参照する、従来の書記素クラスタではなく、拡張書記素クラスタ後者はむしろ、より複雑になっているとして、。Perl自体は現在、より洗練されたバージョンを使用していますが、古いバージョンは、最も一般的な状況でも完全に機能します。編集:下部の補遺を参照してください。

  • 何をする\dかはあなたの意図によって異なりますが、デフォルトはUniodeの定義です。いつも欲しがっているわけではないのです\p{Nd}が、時々、[0-9]あるいはそうしている人もいます\pN

  • 2つの境界定義、\bおよび\Bは、\w定義を使用するように特別に記述されています。

  • その\w定義は、丸いものだけでなく括弧付きの文字をつかむため、過度に広範です。Unicode Other_AlphabeticプロパティはJDK7まで利用できないため、これが最善の方法です。


境界を探索する

境界は、Larry Wall氏は、最初の造語以来、問題となっている\b\Bどのように理解する1987年のキーでのPerl 1.0バックのためにそれらの話をするための構文を\bし、\Bそれらについて2つの普及の神話を払拭するためにある仕事の両方:

  1. 彼らは常に\w単語の文字を探しているだけで、単語以外の文字は探していません
  2. 彼らは文字列の端を特に探しません。

\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-一緒にAB正規表現で、orでありX|Y、そしてので、andより優先順位が高いor単純です、AB|CD。つまり\b、境界は安全に次のように置き換えることができます。

    (?:(?<=\w)(?!\w)|(?<!\w)(?=\w))

\w適切な方法で定義されました。

ACコンポーネントが逆であるのは奇妙だと思うかもしれません。完璧な世界では、それを書くことができるはずです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のような正規表現言語に条件付きパターンがない場合は特に、問題ありません。☹

実行ごとに110,385,408の一致をチェックするテストスイートを使用して、3つの同等の定義すべてを使用して境界の動作を既に確認しました。

     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の修正

私の他の回答で投稿したコードは、これと他のいくつかの便利な機能を提供します。これには、自然言語の単語、ダッシュ、ハイフン、アポストロフィの定義に加えて、もう少し多くの定義が含まれます。

また、ばかばかしいUTF-16サロゲートではなく、論理コードポイントでUnicode文字を指定することもできます。それがどれほど重要であるかを強調するのは難しいです!そしてそれは文字列の拡張のためだけです。

Java正規表現のcharclassが最終的にUnicodeで機能し、正しく機能するようする正規表現のcharclass置換については、ここから完全なソースを 取得してください もちろん、好きなように使ってもかまいません。それを修正する場合は、ぜひ聞いてみたいですが、そうする必要はありません。かなり短いです。メインの正規表現書き換え関数の本質は簡単です。

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:.))";

¡Tschüß!


10
これは素晴らしいです。どうもありがとう。
Tim Pietzcker、2010年

9
キリスト、それは賢明な答えです。Jon Skeetのリファレンスしか取得できません。彼はこれと何をしているのですか?
BalusC 2010年

12
@BalusC:それはジョンが彼が私に質問をフィールドに入れることを許したと言った以前のジョンへの参照です。ただしt、@ tchristにドロップしないでください。それは私の頭に行くかもしれません。:)
tchrist

3
これをOpenJDKに追加することを考えましたか?
Martijn Verburg 2010年

2
@Martijn:いいえ、いませんでした。それが「オープン」であるとは知りませんでした。:)しかし、私はそれをより正式な意味でリリースすることを考えました。私の部門の他の人は、それが完了したことを望んでいます(ある種のオープンソースライセンス、おそらくBSDまたはASL)。私はおそらくなど、コードをクリーンアップ、それはこのアルファプロトタイプであるものからAPIを変更するつもりです。しかし、それは助け我々を途方もなく出て、そして我々はそれがあまりにも、他人を助けるだろう把握します。Sunが彼らのライブラリについて何かしてくれることを本当に望みますが、Oracleは自信を刺激しません。
tchrist 2010年

15

\wうまくいかないのは本当に残念です。提案された解決策\p{Alpha}も私にはうまくいきません。

[\p{L}]すべてのUnicode文字をキャッチしているようです。したがって、と同等のUnicode \wはになります[\p{L}\p{Digit}_]


ただし\w、数字などにも一致します。私は文字だけで\p{L}うまくいくと思います。
Tim Pietzcker、2010年

あなたが正しい。\p{L}十分です また、文字だけが問題だと思いました。[\p{L}\p{Digit}_]アンダースコアを含むすべての英数字をキャッチする必要があります。
musiKk 2010年

@MusicKk:パターンを通常どおりに書き込むことができる完全なソリューションについては私の回答を参照してください。その後、Javaのギャップの欠落を修正してUnicodeで正しく機能する関数に渡します。
tchrist、

いいえ、すべてのばかげたものの中で、\wUnicodeによって定義されているのは、単なる\pLASCII桁よりもはるかに広いということです。Java [\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]対応のUnicode対応が必要な場合は、記述する必要があります\wまたはunicode_charclassここから関数を使用することもできます。ごめんなさい!
tchrist、

1
@ティム、はい、手紙\pLは機能します(1文字の小道具を採用する必要はありません)。ただし、データがUnicode正規化形式D(別名NFD、正準分解を意味する)であるからといってNFC(NFDの後に正準化が続く)であるからといって、一致が異なる答えを返さないように注意する必要があるので、それを望むことはめったにありません。構成)。例として、コードポイントU + E9("é")は\pLNFC形式であるが、そのNFD形式はU + 65.301になるため、と一致し\pL\pMます。あなたはできるちょっとでこれを周りを取得\X(?:(?=\pL)\X)、しかし、あなたは、Javaのそれの私のバージョンが必要です。:(
tchrist

7

Javaでは\w\dUnicode対応ではありません。彼らは、ASCII文字のみに一致する、[A-Za-z0-9_][0-9]。同じことが\p{Alpha}友達にも言えます(それらが基づいているPOSIXの「文字クラス」はロケール依存であるはずですが、JavaではASCII文字にしか一致していません)。Unicodeの「単語文字」と一致させたい場合は、スペルアウトする必要があります。たとえば、[\pL\p{Mn}\p{Nd}\p{Pc}]、文字、非スペース修飾子(アクセント)、10進数字、および接続句読点のあります。

ただし、Java \b Unicodeに対応しています。Character.isLetterOrDigit(ch)アクセント付き文字も使用してチェックしますが、認識される唯一の「接続句読点」文字はアンダースコアです。 編集:私はあなたのサンプルコードをしようとすると、それが印刷さ""élève"、それは(必要があるとしてideone.comにそれを参照してください)。


アランさん、申し訳ありませんが、Java \bがUnicodeに対応しているとは言えません。それはたくさんの間違いを犯します。 "\u2163=""\u24e7="、および"\u0301="すべてのマッチしたパターンに失敗"\\b="Javaではなく、されているはずに-とperl -le 'print /\b=/ || 0 for "\x{2163}=", "\x{24e7}=", "\x{301}="'明らかにしました。ただし、Java のネイティブではなく、私のバージョンの単語境界を入れ替えた場合(かつその場合のみ)\b、それらもすべてJavaで機能します。
tchrist

@tchrist:私は\bの正確さについてコメントしていませんでした。それは、ASCIIのようなもの\wや友人だけでなく、(Javaで実装されている)Unicode文字で動作することを指摘しているだけです。ただし、のように\u0301、その文字がベース文字とペアになっている場合は正しく機能しe\u0301=ます。そして、この例ではJavaが間違っているとは思いません。文字を含む書記素クラスターの一部でない限り、結合マークはどのように単語文字と見なすことができますか?
アランムーア

3
@Alan、これは、Unicodeが拡張対従来の書記素クラスターを議論することによって書記素クラスターを明確化したときに明らかにされたものです。\Xすべてのファイルを一致するものとして説明できるはずなので、非マークとそれに続く任意の数のマークを表す書記素クラスターの古い定義は問題/^(\X*\R)*\R?$/がありますが\pM、最初にファイル、または行の。そのため、少なくとも1文字と常に一致するように拡張されています。いつもそうでしたが、今では上記のパターンが機能します。[…続き…]
tchrist

2
@ Alan、Javaのネイティブ\bが部分的にUnicode対応であることは、良いことよりも害を及ぼします。文字列"élève"をパターンと照合することを検討してください\b(\w+)\b。問題がわかりましたか?
tchrist 2010年

1
@tchrist:はい、単語の境界なしで、\w+2つの一致を見つけます:land ve、これは十分に悪いです。しかし、あるため、単語の境界には、何も見つけられない\b認識éè単語文字として。最低でも、\b\w単語文字とものではありませんが、何に同意する必要があります。
Alan Moore
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.