130

最近の正規表現の実装の多くは、\w文字クラスの省略形を「任意の文字、数字、または接続句読点」(通常はアンダースコア)として解釈します。このように、のような正規表現は、、、、またはの\w+ような単語に一致します。helloélèveGOÄ_432gefräßig

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

また、\b単語の区切り文字は、一致してはならない場所で一致しているように見えます。

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

4

3 に答える 3

243

ソースコード

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

Java7での更新

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


Javaの正規表現Unicodeの問題

Java正規表現の問題は、Perl 1.0のcharclassエスケープ(つまり、、、、\wおよびそれらの補数)が、Unicodeで動作するように拡張されたJavaではないことです\b。これらの中で単独で、特定の拡張セマンティクスを楽しんでいますが、これらは、 Unicode識別子にもUni​​code改行プロパティにもマップされていません。\s\d\b\w

さらに、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}

これは実際の混乱です。これは、、、などがJavaではUnicode 、、、またはプロパティにマップされないことを意味するためAlphaです。これは非常に迷惑です。JavaのUnicodeプロパティのサポートは、厳密に1年ごとです。つまり、過去10年間にリリースされたUnicodeプロパティはサポートされていません。 LowerSpaceAlphabeticLowercaseWhitespace

空白について適切に話すことができないことは非常に迷惑です。次の表を検討してください。これらのコードポイントごとに、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個の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*)

考慮すべきいくつかの事柄...

  • これは、Unicodeが現在拡張書記素クラスターではなく、レガシー書記素クラスター\Xと呼んでいるものを定義に使用します。後者はかなり複雑だからです。Perl自体は現在、より洗練されたバージョンを使用していますが、古いバージョンは、最も一般的な状況でも完全に機能します。編集:下部の補遺を参照してください。

  • 何をすべき\dかはあなたの意図に依存しますが、デフォルトはUniode定義です。私は人々が常に望ん\p{Nd}でいるわけではないが、時にはどちらか[0-9]またはを見ることができます\pN

  • 2つの境界定義\b\Bは、その定義を使用するために特別に作成されてい\wます。

  • その\w定義は、丸で囲まれた文字だけでなく、括弧で囲まれた文字を取得するため、非常に広範です。UnicodeOther_AlphabeticプロパティはJDK7まで使用できないため、これが最善の方法です。


境界の探索

\bLarryWallが1987年にPerl1.0で境界\Bについて話すための構文を最初に作成して以来、境界は問題になっています。境界がどのよう\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)

したがって、は正規表現で ed-togetherIF-THENとしてエンコードされるため、anはであり、は、よりも優先順位が高いため、単純にです。つまり、境界を次のように安全に置き換えることができるということです。andABorX|YandorAB|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のように、正規表現言語に条件付きパターンがない場合は特に、バージョンは問題ありません。☹</p>

実行ごとに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の修正

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

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

Java正規表現のcharclassを最終的にUnicodeで機能させ、正しく機能させるregex 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テストスイートが必要です。ギガテスターを含めることもできますが、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üß!

于 2010-11-29T19:27:04.007 に答える
15

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

[\p{L}]すべてのUnicode文字をキャッチしているようです。したがって、Unicodeに相当するもの\w[\p{L}\p{Digit}_]

于 2010-11-29T15:18:44.953 に答える
7

Javaでは、\wUnicode\dに対応していません。それらは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で参照してください)。

于 2010-11-29T16:43:39.423 に答える