何度やっても同じ

ただの日記

Javaの正規表現で \p{Han} がPatternSyntaxException

Java8と9で確認した。それ以降のバージョンでは確認していない。

Pattern p = Pattern.compile("\\p{Han}+");
Matcher m = p.matcher("吾輩は猫である");

while (m.find()) {
  System.out.println("matched: " + m.group());
}

Exception in thread "main" java.util.regex.PatternSyntaxException: Unknown character property name {Han} near index 6
\p{Han}+

こうなる。

https://github.com/Engelberg/instaparse/issues/106#issuecomment-127065635 のコメントによると、次のように書けばOK。UnicodeブロックとUnicodeスクリプトとで書き方が違うらしい。

Pattern p = Pattern.compile("\\p{script=Han}+");

結果。

matched: 吾輩
matched: 猫

エラーはこれで解決なのだけど、先のコメントと同じページに次のような記述があるので、\{script=Han} が、これらのブロックの文字すべてにマッチするのかどうか、確認してみた。

    (* Same as Han but without IDC *)
    L = CJK | CJKA | CJKB | CJKC | CJKD | CJKE | CJKRS | KR | CJKSP | CJKS | ECJKLM | CJKCo | CJKCI | CJKCF | CJKCIS
    (* IDC are excluded from this *)
    Han = L | IDC

    (* Blocks follow *)
    CJK =     #'[\u4e00-\u9fff]'
    CJKA =    #'[\u3400-\u4dbf]'
    CJKB =    #'[\u20000-\u2a6df]'
    CJKC =    #'[\u2a700-\u2b73f]'
    CJKD =    #'[\u2b740-\u2B81F]'
    CJKE =    #'[\u2b820-\u2ceaf]'
    CJKRS =   #'[\u2e80-\u2eff]'
    KR =      #'[\u2f00-\u2fdf]'
    IDC =     #'[\u2ff0-\u2fff]'
    CJKSP =   #'[\u3000-\u303f]'
    CJKS =    #'[\u31c0-\u31ef]'
    ECJKLM =  #'[\u3200-\u32ff]'
    CJKCo =   #'[\u3300-\u33ff]'
    CJKCI =   #'[\uf900-\ufaff]'
    CJKCF =   #'[\ufe30-\ufe4f]'
    CJKCIS =  #'[\u2f800-\u2fa1f]'

各ブロックから表示できそうな文字を選んで並べてマッチさせてみる。文字はCyber Librarianで探した。

    String chars = String.join("", new String[] {
      "\u4e00",
      "\u3400",
      "\ud840", "\udc0b", // "\u2000b",
      "\ud869", "\udfdd", // "\u2a7dd",
      "\ud86d", "\udf46", // "\u2b746",
      "\ud86e", "\udcb8", // "\u2b8b8",
      "\u2e80",
      "\u2f00",
      "\u2ff0",
      "\u3000",
      "\u31c0",
      "\u3200",
      "\u3300",
      "\uf900",
      "\ufe30",
      "\ud87e", "\udc04", // "\u2f804",
    });
    System.out.println(chars);

    Pattern p = Pattern.compile("\\p{script=Han}+");
    Matcher m = p.matcher(chars);

    while (m.find()) {
      System.out.println("matched: " + m.group());
    }

その結果。

一㐀𠀋𪟝𫝆𫢸⺀⼀⿰ ㇀㈀㌀豈︰你
matched: 一㐀𠀋𪟝𫝆
matched: ⺀⼀
matched: 豈
matched: 你

CJK統合漢字拡張E(CJKE)、漢字構成記述文字(IDC)、CJKの記号及び句読点(CJKSP)、CJKの筆画(CJKS)、囲みCJK文字・月(ECJKLM)、CJK互換用文字(CJKCo)、CJK互換形(CJKCF)はマッチしない模様。見た感じ、普通に考えて漢字でないブロックを除外しているようではある。CJK統合漢字拡張Eは漢字だけど、このブロック自体がJava8ではサポートされていないようなので、仕方ない。

一応、自分で正規表現にすべてのUnicodeブロック(拡張Eを除く)からなる文字クラスを定義してマッチさせてみた。

    String[] props = {
      "\\p{InCJKUnifiedIdeographs}",                // CJK    [\u4e00-\u9fff]
      "\\p{InCJKUnifiedIdeographsExtensionA}",      // CJKA   [\u3400-\u4dbf]
      "\\p{InCJKUnifiedIdeographsExtensionB}",      // CJKB   [\u20000-\u2a6df]
      "\\p{InCJKUnifiedIdeographsExtensionC}",      // CJKC   [\u2a700-\u2b73f]
      "\\p{InCJKUnifiedIdeographsExtensionD}",      // CJKD   [\u2b740-\u2B81F]
      // "\\p{InCJKUnifiedIdeographsExtensionE}",   // CJKE   [\u2b820-\u2ceaf] ない
      "\\p{InCJKRadicalsSupplement}",               // CJKRS  [\u2e80-\u2eff]
      "\\p{InKangxiRadicals}",                      // KR     [\u2f00-\u2fdf]
      "\\p{InIdeographicDescriptionCharacters}",    // IDC    [\u2ff0-\u2fff]
      "\\p{InCJKSymbolsandPunctuation}",            // CJKSP  [\u3000-\u303f]
      "\\p{InCJKStrokes}",                          // CJKS   [\u31c0-\u31ef]
      "\\p{InEnclosedCJKLettersandMonths}",         // ECJKLM [\u3200-\u32ff]
      "\\p{InCJKCompatibility}",                    // CJKCo  [\u3300-\u33ff]
      "\\p{InCJKCompatibilityIdeographs}",          // CJKCI  [\uf900-\ufaff]
      "\\p{InCJKCompatibilityForms}",               // CJKCF  [\ufe30-\ufe4f]
      "\\p{InCJKCompatibilityIdeographsSupplement}" // CJKCIS [\u2f800-\u2fa1f]
    };

    String chars = String.join("", new String[] {
      "\u4e00",
      "\u3400",
      "\ud840", "\udc0b", // "\u2000b",
      "\ud869", "\udfdd", // "\u2a7dd",
      "\ud86d", "\udf46", // "\u2b746",
      "\ud86e", "\udcb8", // "\u2b8b8",
      "\u2e80",
      "\u2f00",
      "\u2ff0",
      "\u3000",
      "\u31c0",
      "\u3200",
      "\u3300",
      "\uf900",
      "\ufe30",
      "\ud87e", "\udc04", // "\u2f804",
    });
    System.out.println(chars);

    Pattern p = Pattern.compile("[" + String.join("", props) + "]+");
    Matcher m = p.matcher(chars);

    while (m.find()) {
      System.out.println("matched: " + m.group());
    }

結果。拡張E以外マッチ。

一㐀𠀋𪟝𫝆𫢸⺀⼀⿰ ㇀㈀㌀豈︰你
matched: 一㐀𠀋𪟝𫝆
matched: ⺀⼀⿰ ㇀㈀㌀豈︰你

サロゲートペアはJavaScriptで雑なコード書いてつくりました。

const toSurrogatePair = function(ch) {
  const x = ch - 0x10000
  return [
    Math.floor(x / 0x400) + 0xD800,
    x % 0x400 + 0xDC00
  ].map(c => `\\u${c.toString(16)}`)
}

[
  0x2000b,
  0x2a7dd,
  0x2b746,
  0x2b8b8,
  0x2f802,
].forEach(c => {
  const p = toSurrogatePair(c)
  console.log(`"${p[0]}", "${p[1]}", // ${c.toString(16)}`)
})