Как зафиксировать более 10 вещей с помощью Swift 5.7 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требуются детали , сообщение об ошибке становится немного более очевидным.

Неоднозначное использование 'buildPartialBlock(accumulated:next:)'

SwiftUI имеет аналогичную проблему, когда количество представлений в построителе представлений не может превышать 10, и в этом случае вы просто используетеGroupчтобы сделать некоторые из представлений одним представлением. Можете ли вы сделать что-то подобное в RegexBuilder? Сделать некоторые снимки одним? Кажется, это как-то связаноAnyRegexOutput, но я не уверен, как его использовать.

Как устранить эту ошибку компилятора?


Чтобы избежать проблемы XY:

У меня есть файл данных, в котором данные отформатированы очень бессистемно, то есть вообще не очень машиночитаемы, как CSV или JSON. Строки пишутся во всевозможных форматах. Случайные разделители используются в случайных местах.

Тогда другая строка в файле будет содержать ту же информацию, но отформатированную по-другому.

Что я хочу сделать, так это преобразовать этот странно отформатированный файл в удобный для работы формат, такой как CSV. Я решил сделать это с помощью Swift 5.7 RegexBuilder API. Я бы нашел строку в файле, написал регулярное выражение, соответствующее этой строке, преобразовал все строки файла, соответствующие этому регулярному выражению, в CSV, затем промыл и повторил.

Поэтому я хотел бы избежать использования нескольких регулярных выражений для анализа одной строки, так как это означало бы, что я буду писать намного больше регулярных выражений.

Я не уверен, что такой парсер, как ANTLR4, решит мою проблему. Учитывая, насколько случайным образом отформатирован файл, мне пришлось бы много менять синтаксический анализатор , заставляя файлы генерироваться снова и снова. Я не думаю, что это будет так же удобно, как использование RegexBuilder.

1 ответ

В качестве хака можно создать обобщенный CustomConsumingRegexComponentреализация, которая принимает

  • любойRegexComponentпостроенный от строителя, у которого всегда есть(Substring, A, B, C ...)кортеж в качестве вывода
  • преобразование, которое преобразует этот кортеж в желаемый тип

По сути, мы можем создать компонент регулярного выражения, который принимает некоторое регулярное выражение и выводит любой тип.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)
    }
}

Причина, по которой это всего лишь хак, заключается в том, что регулярное выражение внутри фактически не знает о вещах за пределами , поэтому квантификаторы внутри не будут возвращаться назад, чтобы попытаться сопоставить вещи за пределами .

Например, чтобы исправить код в вопросе, я могу поместить всю информацию, связанную с браком, в файл , но мне нужно добавить внутрь:

      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, возврата здесь не происходит, и полное совпадение не выполняется.

Другие вопросы по тегам