簡體   English   中英

如何使用 Swift 5.7 的 RegexBuilder 捕獲 10 多個東西?

[英]How to capture more than 10 things using Swift 5.7's RegexBuilder?

假設我有一個存儲有關人員信息的文件,其中一行如下所示:

Sweeper 30 1992-09-22 China/Beijing - 0 2020-07-07 Mary/Linda - Pizza/Lemon

從左到右依次為姓名、年齡、出生日期、出生國家、出生城市、子女數量、結婚日期(可選)、妻子姓名(可選)、前妻姓名(可選)、喜歡的食物、最不喜歡的食物。

我想使用 Swift 5.7 RegexBuilder 模塊從行中獲取所有信息,我嘗試過:

let regex = Regex {
    /([a-zA-Z ]+)/ // Name
    " "
    TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Age
    " "
    Capture(.iso8601Date(timeZone: .gmt)) // Date of Birth
    " "
    /([a-zA-Z ]+)/ // Country of Birth
    "/"
    /([a-zA-Z ]+)/ // City of Birth
    " - "
    TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Children Count
    Optionally {
        " "
        Capture(.iso8601Date(timeZone: .gmt)) // Date of Marriage
        Optionally {
            " "
            /([a-zA-Z ]+)/ // Wife
            Optionally {
                "/"
                /([a-zA-Z ]+)/ // Ex-wife
            }
        }
    }
    " - "
    /([a-zA-Z ]+)/ // Favourite food
    "/"
    /([a-zA-Z ]+)/ // Least Favourite Food
}

但是,Swift 表示無法在合理的時間內進行類型檢查。

我知道發生這種情況的原因是因為RegexComponentBuilder (正則表達式組件的結果生成器)僅具有最多 10 個“C”或類似內容的重載(對細節不太確定):

static func buildPartialBlock<W0, W1, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, R0, R1>(
    accumulated: R0,
    next: R1) -> Regex<(Substring, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10)> where R0 : RegexComponent, R1 : RegexComponent, R0.RegexOutput == (W0, C1, C2, C3), R1.RegexOutput == (W1, C4, C5, C6, C7, C8, C9, C10
)

如果我將所有Optionally部分都設置為required ,則錯誤消息會變得更加明顯。

'buildPartialBlock(accumulated:next:)' 的模糊使用

SwiftUI 也有類似的問題,視圖構建器中的視圖數量不能超過 10,在這種情況下,您只需使用Group將某些視圖變成單個視圖。 你能在 RegexBuilder 中做類似的事情嗎? 使某些捕獲成為一次捕獲? 它似乎與AnyRegexOutput ,但我不確定如何使用它。

如何解決此編譯器錯誤?


為避免 XY 問題:

我有一個數據文件,其中數據的格式非常隨意,即根本不像 CSV 或 JSON 那樣機器可讀。 行以各種格式編寫。 隨機分隔符用於隨機位置。

然后文件中的另一行將具有相同的信息,但以不同的方式格式化。

我想做的是將這個格式怪異的文件轉換為易於使用的格式,例如 CSV。 我決定使用 Swift 5.7 RegexBuilder API 來執行此操作。 我會在文件中找到一行,編寫一個與該行匹配的正則表達式,將文件中與該正則表達式匹配的所有行轉換為 CSV,然后沖洗並重復。

因此,我想避免使用多個正則表達式來解析一行,因為這意味着我將編寫更多的正則表達式。

我不確定像 ANTLR4 這樣的解析器是否能解決我的問題。 鑒於文件格式的隨機性,我需要大量更改解析器,導致文件一次又一次地生成。 我認為這不會像使用 RegexBuilder 那樣方便。

作為 hack ,您可以創建一個通用CustomConsumingRegexComponent實現,它接受

  • 從構建器構建的任何RegexComponent ,它始終具有(Substring, A, B, C...)元組為 output
  • 將元組轉換為我們想要的類型T的轉換

我們基本上可以創建一個正則表達式組件,它接受一些正則表達式並輸出我們想要的任何類型T ,本質上是“分組”捕獲。

也可以不進行轉換,最終得到嵌套元組,但我不喜歡這樣。

struct Group<RegexOutput, Component: RegexComponent>: CustomConsumingRegexComponent {

    let component: () -> Component
    
    let transform: (Component.RegexOutput) -> RegexOutput
    
    init(@RegexComponentBuilder _ regexBuilder: @escaping () -> Component, transform: @escaping (Component.RegexOutput) -> RegexOutput) {
        component = regexBuilder
        self.transform = transform
    }
    
    func consuming(_ input: String, startingAt index: String.Index, in bounds: Range<String.Index>) throws -> (upperBound: String.Index, output: RegexOutput)? {
        let innerRegex = Regex(component)
        guard let match = input[index...].prefixMatch(of: innerRegex) else { return nil }
        let upperBound = match.range.upperBound
        let output = match.output
        let transformedOutput = transform(output)
        return (upperBound, transformedOutput)
    }
}

之所以這只是一個 hack,是因為Group內部的正則表達式實際上並不知道Group之外的東西,所以Group內部的量詞不會回溯以嘗試匹配Group之外的東西。

例如,要修復問題中的代碼,我可以將所有與婚姻相關的信息放入一個Group中,但我必須在Group中添加一個前瞻:

struct Marriage {
    let marriageDate: Date
    let wife: Substring?
    let exWife: Substring?
}

let r = Regex {
    /([a-zA-Z ]+)/ // Name
    " "
    TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Age
    " "
    Capture(.iso8601Date(timeZone: .gmt)) // Date of Birth
    " "
    /([a-zA-Z ]+)/ // Country of Birth
    "/"
    /([a-zA-Z ]+)/ // City of Birth
    " - "
    TryCapture { OneOrMore(.digit) } transform: { Int($0) } // Children Count

    Optionally {
        " "
        Capture(Group {
            Capture(.iso8601Date(timeZone: .gmt)) // Date of Marriage
            Optionally {
                " "
                /([a-zA-Z ]+)/ // Wife
                Optionally {
                    "/"
                    /([a-zA-Z ]+)/ // Ex-wife
                }
            }
            Lookahead(" - ")
        } transform: { (_, date, wife, exWife) in
            Marriage(marriageDate: date, wife: wife, exWife: exWife as? Substring) // unwrap the double optional
        })
    }
    " - "
    /([a-zA-Z ]+)/ // Favourite food
    "/"
    /([a-zA-Z ]+)/ // Least Favourite Food
}

如果沒有前瞻,會發生以下情況:

最里面[a-zA-Z ]+將匹配Linda ,以及它后面的空格,導致" - "不匹配。 通常情況下,這會導致回溯,但是由於Group內部的東西不知道Group外部的東西,所以這里不會發生回溯,整個匹配失敗。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM