ソースコード
以下で説明する書き換え関数のソースコードは、こちらから入手できます。
Java7での更新
SunのPattern
JDK7用に更新されたクラスには、UNICODE_CHARACTER_CLASS
すべてが正常に機能するようにするすばらしい新しいフラグがあります。(?U)
パターン内に埋め込み可能として利用できるため、String
クラスのラッパーと一緒に使用することもできます。また、他のさまざまなプロパティの修正された定義も提供します。UTS#18:Unicode正規表現のRL1.2とRL1.2aの両方でUnicode標準を追跡するようになりました。これはエキサイティングで劇的な改善であり、開発チームはこの重要な取り組みを称賛されます。
Javaの正規表現Unicodeの問題
Java正規表現の問題は、Perl 1.0のcharclassエスケープ(つまり、、、、\w
およびそれらの補数)が、Unicodeで動作するように拡張されたJavaではないことです\b
。これらの中で単独で、特定の拡張セマンティクスを楽しんでいますが、これらは、 Unicode識別子にもUnicode改行プロパティにもマップされていません。\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プロパティはサポートされていません。 Lower
Space
Alphabetic
Lowercase
Whitespace
空白について適切に話すことができないことは非常に迷惑です。次の表を検討してください。これらのコードポイントごとに、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まで使用できないため、これが最善の方法です。
境界の探索
\b
LarryWallが1987年にPerl1.0で境界\B
について話すための構文を最初に作成して以来、境界は問題になっています。境界がどのよう\b
に機能するかを理解するための鍵は、境界\B
に関する2つの一般的な神話を払拭することです。
- 彼らは単語の文字だけを探しており、単語以外の文字を探していること
\w
はありません。
- 文字列の端を具体的に探すわけではありません。
\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はであり、は、よりも優先順位が高いため、単純にです。つまり、境界を次のように安全に置き換えることができるということです。and
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のように、正規表現言語に条件付きパターンがない場合は特に、バージョンは問題ありません。☹</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;
とにかく、そのコードは単なるアルファリリースであり、週末にハックしたものです。それはそのようにとどまりません。
ベータ版の場合、次のことを行います。
本番リリースの場合、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üß!