[Swift][Swift5.7] RegexBuilder の transform の使い方

     
Swift5.7 で追加された Regex/RegexBuilder の使い方を説明していきます。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterey 12.5 Beta
  • Xcode 14.0 beta
  • iOS 16.0 beta

Regex 型

Swift5.7 で導入された Regex 型の続きの説明です。

前回の記事は、以下です。
[Swift][Swift5.7] RegexBuilder の transform の使い方

名前付きキャプチャする時に、なぜ、 Reference を定義しなければいけないのか 疑問でした。
String で名前をつけるだけで十分ではないかと。

ですが、今回紹介する transform を使用する時に、Reference を定義することの意味がわかりました。

キャプチャ したものを transform する

ここからが、RegexBuilder の本領発揮です。

前回 Capture で取得したものは、Substring 型でした。与えた文字列の 部分文字列を返すので、そのままキャプチャするということです。

正規表現で処理する対象は、通常の文字列だけではありません。数字であれば、最終的に 数値表現(Int,Double,…) にすることが多いですし、日付についても 文字列のままではなく、Date にして処理を行いたいはずです。

transform

キャプチャするときに指定する Capture に transform を与えることで、変換処理を Regex 型内に持たせることができます。

例題がないと説明が難しいので、前回同様 Markdown フォーマットの Header を処理する例で説明します。

Markdown Header の例

前回は、以下のような Regex で Header 文字列の # の部分と その後に続く文字列を それぞれ Substring として取得していました。


func parseHeaderWithRegexBuilder(_ str: S) -> (headerLevel: Substring,
                                                                  headerString: Substring)? {
    let headerMarks = /#{1,6}/
    let refMark = Reference(Substring.self)
    let refText = Reference(Substring.self)

    let pattern = Regex {
        Capture(headerMarks, as: refMark)
        CharacterClass.whitespace
        Capture(ZeroOrMore(.any), as: refText)
    }
    guard let match = String(str).wholeMatch(of: pattern) else { return nil }
    return (match[refMark], match[refText])
}

Header の文字列 headerString は、そのまま文字列で処理を行なっていきたいですが、Header を意味する # については、文字列そのものよりも、# の数に変換しておく方が便利です。

変換しておくとその後 Level 1 の Header 、Level 2 の Header というように直接数値を見て処理していけます。

ですので、headerLevel については、Substring ではなく、# の数 を Int で表現するように変換します。
RegexBuilder を使用することで、Regex 定義時に キャプチャした文字列についての変換は、Capture の transform 引数に、closure を渡します。

実際に、# の数を数える関数は、lengthOfBytes を使用しています。


        let refMark = Reference(Int.self)
        
        let pattern = Regex {
            Capture(headerMarks, as: refMark, transform: { subString in
                subString.lengthOfBytes(using: .utf8)
            })
            .....omit.....
        }
        guard let match = String(str).wholeMatch(of: pattern) else { return nil }
        //return (match.output.1, match.output.2)
        return (match[refMark], match[refText])
    }

Referece についても型が Int に変わっていることに注意してください。
closure が返す型と一致していることが必要です。

このように定義することで、キャプチャしたものの型が 指定されることになります。
関数全体は以下のようになります。


    func parseHeaderWithRegexBuilder(_ str: S) -> (headerLevel: Int,
                                                                      headerString: Substring)? {
        let headerMarks = /#{1,6}/
        
        let refMark = Reference(Int.self)
        let refText = Reference(Substring.self)
        
        let pattern = Regex {
            Capture(headerMarks, as: refMark) { subString in
                subString.lengthOfBytes(using: .utf8)
            }
            CharacterClass.whitespace
            Capture( ZeroOrMore(.any), as: refText)
        }
        guard let match = String(str).wholeMatch(of: pattern) else { return nil }
        return (match[refMark], match[refText])
    }

上記関数は、Playground でも実行できます。実行結果は、以下のようになります。


print(parseHeaderWithRegexBuilder("# TopTitle"))
// print-out
Optional((headerLevel: 1, headerString: "TopTitle"))


print(parseHeaderWithRegexBuilder("## MidTitle"))
// print-out
Optional((headerLevel: 2, headerString: "MidTitle"))

headerLevel には、Int が返され、headerString には、該当部分の文字列が返っていることがわかります。

まとめ

Swift5.7 で導入された RegexBuilder の transformを説明しました。

Regex と RegexBuilder
  • Capture に transform を指定することで、キャプチャ部分の変換を行うことができる

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

Swift ポケットリファレンス

Swift を学んでも、プログラミング言語の文法を全て記憶しておくことは無理なので、ちょっとした文法の確認をするために、リファレンス本を手元に置いておくと便利です。

注意

Swift4 までしか対応していないので、相違点を理解して参照する必要があります。

そろそろ Swift5 に対応した版が欲しいですね・・・

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です