Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

はじめての正規表現とベストプラクティス#7: Unicode文字ポイントとUnicode文字クラスのプロパティ

主にRubyを中心としながらも、なるべく一般的な形で正規表現を解説しています。誤りやお気づきの点がありましたら@hachi8833までどうぞ🙇。

Unicode文字コードポイント

Unicode文字コードポイントの表記方法も、正規表現ライブラリによってまちまちです。そもそもUnicode文字コードポイントで表せない正規表現ライブラリもたまにあります。困ったことです。

その前に: Unicode文字コードポイントとは

まず、Unicodeという規格本体に文字集合(character set)というものがあります。その文字集合内の位置を表すのがコードポイント(code point: 符号点)です。

手元のかな漢字変換にある「文字パレット」を開くと、Unicodeの文字集合とそのコードポイントを見ることができます。U+00F9みたいなのがコードポイントで、16進数です。

ここで重要なのは、コードポイントはUnicodeそのものの規格なので、エンコードがUTF-8だろうと16だろうと32だろうとコードポイントは変わらないという点です。U+00F900F9はUnicodeのどのエンコードでも同じです。

Unicode文字コードポイントはほとんどが4桁です。
しかし、後から足された文字の中にはコードポイントが5桁や6桁のものがあります。かつてこれがMySQLの有名な「寿司ビール問題」を引き起こしました。特に最近はUnicodeに絵文字が大量に追加されているので、これまでこの種の問題に関心が薄かった英語圏も絵文字の普及とともにやっと真剣に考えてくれるようになったのはありがたいことです。

MySQLのencodingをutf8からutf8mb4に変更して寿司ビール問題に対応する

正規表現でのコードポイント記法

ここからが本題です。

Unicodeの規格ではU+00F9というような表記が用いられていますが、正規表現ライブラリ(というよりライブラリを使うプログラミング言語)によって表記方法はまちまちです。以下はほんの一例です。

基本形
\uFFFF
Rubyなど
[\u{FFFF}][\uFFFF]\uFFFF\u{FFFF}
JavaScript、Pythonなど
[\uFFFF]\uFFFF
PCRE、Perl、Golangなど
[\x{FFFF}]\x{FFFF}
POSIX基本
(機能なし)
HTMLやXML(参考)

  • Rubyは{}があってもなくても許容します
  • JavaScriptやPythonは{}を許容しません
  • PCRE/Perl/Golangは{}が必須です

つまり、たとえばPythonとPerlの両方で同じ正規表現を動かそうとするとここが最も移植の障害になります😭。

コードポイントは正規表現の中に直接書くこともできますが、私は個人的に[\uFFFF]などのように文字セット[ ]の中に入れて書くのが好みです。次に説明するUnicode文字クラスと書式が揃って気持ちいいという理由です。

コードポイント記法を無理して使うことはない

そして意外に思われるかもしれませんが、私はそもそも[\uFFFF]のようなコードポイント記法をほぼまったく用いません。主な理由は上のように言語/ライブラリでの非互換性が大きいということと、コメントなしでは意味不明になりがちな視認性の悪さです。私は正規表現のメンテナンス性を重視するので、一度書いたら忘れてもいい正規表現ならともかく、後で細かく調整する正規表現は未来の自分が読んでくじけない書き方を常に目指しています。

駅の名前を全部覚えるみたいにコードポイントを覚えるくらいなら、文字セット[ ]の中に文字を直接書きます。

円周率なら3.1415926535897932384626までは覚えちゃいましたが。

以下の記事をご覧いただければおわかりいただけると思いますが、文字セット[ ]の中は別世界なので、相当特殊な文字であっても[ ]の中に安心して書けます。

正規表現: 文字クラス [ ] 内でエスケープしなくてもよい記号

ただ、特に[-ー-─―‐]のような「ハイフンと紛らわしい文字のセット」のような正規表現では、本来の-(U+002D、ASCIIだと2D)を文字セットの冒頭または末尾以外には置かないよう心がけています。-を途中に置くと「両側の文字の範囲」になってしまうので。

また、一部の結合文字のように、エディタやIDEやターミナルで入力すると挙動がおかしくなる特殊な文字があります。そういう場合には、仕方なくコードポイントで書くこともあります。

Unicode文字クラスのプロパティ

Unicode文字クラスのプロパティは、\p{なんちゃら}という形で表記します(pはプロパティのことでしょうね)。マルチリンガルな正規表現で非常に有用性が高いのが特徴です。これが使えるライブラリは高機能と認めてよいと思います。

  • 利用できる: Ruby、Perl、PCRE、Python、.NET Frameworkなど
  • 利用できない: JavaScriptなど😢(ただしついにES2018で導入される予定❤️)

\p{なんちゃら}は比較的最近の機能であるためか、文字コードポイントなんかと違って、主要な言語での記法のブレが少ないのがありがたい点です🙏。

\p{なんちゃら}の「なんちゃら」の部分には、以下の3種類を指定できます。

General categories(一般カテゴリとも)
[\p{L}](記号/約物/スペースでない)あらゆる文字(letter)、[\p{Zs}](スペース)、[\p{N}](あらゆる数字)、[\p{No}](漢数字や①などの数字)など
Scripts
Han(漢字)、Katanaka(長音を除くカタカナ)、Latin(ラテン文字: 要するにアルファベット)など
Blocks
In_CJK_Unified_Ideographsなど

上の表のリンクをクリックすると、それぞれの全リストを参照できます。

  • 一般カテゴリは、上のリンク先の表では大文字1文字(カテゴリ)と小文字1文字(サブカテゴリ)の組み合わせがリストアップされています。
    • たとえば大文字の[\p{N}]とすると、[\p{Nd}](数値用の数字)と[\p{Nl}](ローマ字などの数字)と[\p{No}](漢数字や分数などの数字)といったサブカテゴリを一発で表せるので便利です。
  • BlockなどはリストでCJK Unified Ideographsなどと書かれていたりするので、自分でIn_を付けたりスペースを_に置き換えるなどしてIn_CJK_Unified_Ideographsのような形にする必要があります。面倒です😇。

なお、Unicode文字クラスで言う文字(letter)は「記号やスペースといった約物(punctuation)や数値(number)などでないもの」という補集合的な意味で捉える必要があります。

注意

  • 特に[\p{L}][\p{N}]のような一般カテゴリは、マッチする範囲が非常に大きいので注意が必要です。余分なマッチを呼び込んでいないか、使う前によーくチェックしましょう。
    • [\p{L}]は日本語だろうがトンパ文字だろうがあらゆる言語のあらゆる文字(letter)にマッチします。
    • [\p{N}]は全角数字だろうがローマ字のⅥだろうが分数¼だろうが数字という数字にマッチします。
      • ただし漢数字は「〇」以外はマッチしませんRubular)。漢数字は数字ではなく文字として扱われます。
  • 逆に、[\p{Zs}]ありとあらゆるスペース文字にマッチするので、ゼロ幅スペースのようないたずらにしか使えないようなスペース文字すら捉えられます。うまく使えば非常に有用です。

  • カタカナにマッチさせたい場合、たとえば[\p{Katakana}]+だけでは長音が含まれないので注意が必要です。[\p{Katakana}ー]+などのように長音も忘れず含めましょう。

    • なお、厳密にやるなら[\p{Katakana}][\p{Katakana}ー]+のように冒頭の長音を排除したいところです。
  • 通常の全角カタカナの他に半角カタカナにもマッチさせたいなら、/[\p{Katakana}ーー゙゚]+/のように半角の濁点と半濁点と長音ー゙゚も加える必要があります(Rubular)。
    • なお、半角の濁音と半濁音は、前の文字に貼り付く特殊な文字につき単独ではコピペ困難な可能性があります。以下をお使いください。
半角長音
U+FF70
半角濁音
U+FF9E
半角半濁音
U+FF9F

参考: ハイフンに似てる文字の文字コード - Qiita

利用例

私は日本語を相手にする正規表現をよく使います。その中で多用している有用な文字クラスは、[\p{Han}]+(漢字にマッチ)、[\p{Hiragana}]+(ひらがなにマッチ)[\p{Katakana}ー]+(カタカナにマッチ)の3つです。

  • 例: /[\p{Han}]+/で漢字にマッチ(Rubular

  • 例: /[\p{Hiragana}]+/でひらがなにマッチ(Rubular

  • 例: /[\p{Katakana}ー]+/でカタカナにマッチ(Rubular

これらは誤動作の心配もなく、#3で紹介したような[一-龠][ぁ-ん]といった文字セットよりずっと洗練されています。私にとって、ないと生きていけない機能であり、これらが使えない正規表現ライブラリに用はありません。

おまけ: 文字セットの演算

文字セット同士の差分を取れると、正規表現を簡潔に書くうえで非常に有用です。詳しくは以下の記事をご覧ください。

Rubyの正規表現は`&&`のintersectで文字セットの「差分」を取れる

おたより発掘

関連記事

JavaScript: Chrome V8なら正規表現で後読み(look behind)がフル機能で使える

正規表現をチョムスキー言語学まで遡って理解する(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。