正規表現ユースケース(foo|bar): Notion Tips (158)

はじめに

Notion Tips の第158回目はどちらかの単語にマッチする | を解説します。

どちらかにマッチ

タイトルにあるどちらかの単語にマッチする正規表現は "foo|bar" のように記述します。この時、foo または bar のどちらかにマッチすることになります。ただし、前後にそれ以外の文字のマッチを使う時には、区切りがわからなくなるため、 "before(foo|bar)after" のように正規表現ユースケース(()によるキャプチャ) : Notion Tips (156) - hkob’s blogで解説したキャプチャ用のグループと組み合わせて使うことが多いです。

また、その回では以下のような [] を使ったテストをしていました。

name.test(".*오[전후].*"),
name.test(".*[上下]午.*"),

この部分は | を使うと以下のように記載できます。こっちの方が直接的でわかりやすいと思います。ただし、上で説明したように () によるグルーピングは必要です。

name.test(".*(오전|오후).*"),
name.test(".*(上午|下午).*"),

応用編

メンションされた文字列から日付を得る関数の前段部分を紹介します。今回の (AM|PM) の判定に使っていました。date string にはそれぞれの国のデフォルトの日付メンション文字列を記載しています。これを ISO8601 の順番にうまく正規化する部分がこの関数の前段部分です。本来は月の文字列が数値に変換されるはずなのですが、スクリーンショットは Finnish の言語の時に撮影したので、Finnish のところだけが 12 に変更されています。面倒なことに Finnish では単純に月文字列になるのではなく、 ta というのが接尾子に付くようです。また、12時間制の場合にはフラグとして一番最後に A をつけています。後段で 156 回に解説した時刻の正規化を実施します。

日付の正規化

これを実現する関数はこちらになります。かなり長いですが、これまでの正規表現の解説を読まれた方であれば、追えるのではないかと思います。先ほどの Finnish の問題もあり、月の文字列の置き換えはかなり力技になっています。

lets(
    name, prop("date string").replaceAll(" de ", " "),
    /* months */
    months, ".".repeat(12).split("").map(((202401+index)*100+1).parseDate().formatDate("MMMM")),
    /* timezone */
    timezone, "00" + today().formatDate("Z"),
    /* normalize name */
    normalizeName, 
        ifs(
            name.contains("년"),
                /* Korean */
                name
                    .replaceAll("오전 (\d+:\d+)", "$1 AM")
                    .replaceAll("오후 (\d+:\d+)", "$1 PM")
                    + " A",
            name.test(".*[上下]午.*"),
                /* 繁体中文 */
                name
                    .replaceAll("([年月])", "$1 ")
                    .replaceAll("上午(\d+:\d+)", "$1 AM")
                    .replaceAll("下午(\d+:\d+)", "$1 PM")
                    + " A",
            name.test("^\d\d\d\d"),
                /* 日本語、簡体中文 */
                name
                    .replaceAll("([年月])", "$1 "),
            name.test("^[A-Z]"),
                /* English */
                name
                    .replaceAll("([A-Z]\w+) (\d+), (\d\d\d\d)", "$3 $1 $2")
                    + (name.test(".*(AM|PM).*") ? " A" : ""),
            name
                .replaceAll("(\d+)\.? ([^ ]+) (\d\d\d\d)", "$3 $2 $1")
        )
        .replaceAll(months.at(0), "1")
        .replaceAll(months.at(1), "2")
        .replaceAll(months.at(2), "3")
        .replaceAll(months.at(3), "4")
        .replaceAll(months.at(4), "5")
        .replaceAll(months.at(5), "6")
        .replaceAll(months.at(6), "7")
        .replaceAll(months.at(7), "8")
        .replaceAll(months.at(8), "9")
        .replaceAll(months.at(9), "10")
        .replaceAll(months.at(10), "11")
        .replaceAll(months.at(11), "12"),
    normalizeName
)

おわりに

ここまで来てようやく ISO8601 フォーマットに近づいてきました。明日は最終的に関数全体を示します。後半も日付範囲や時刻範囲の判定などかなりボリュームがあります。

hkob.notion.site