Маркировка строки с разделителем из нескольких символов - PullRequest
0 голосов
/ 24 июня 2018

Я пытаюсь токенизировать выражение по следующим правилам:

  • Разделителями являются '}}' и '{{'

  • Строки между разделителями должны быть неповрежденными (исключая отдельные пробелы, которые отбрасываются (может быть сделано в парсере)

  • Разделители могут быть встроены, и порядок должен быть сохранен

  • Отдельные вхождения '{' и '}' должны быть оставлены нетронутыми и не использоваться в качестве разделителей (см. Последнее испытание).

  • В результате не должно быть пустых строк (это можно сделать в парсере)

Пара исключений (помеченных в скобках) может быть сделана последующей обработкой (правильного) результата в разборе. Результат будет передан парсеру рекурсивного спуска.

Вот несколько испытаний, которые не проходят модульные тесты, которые я включил. Функция find_all наиболее близка к совпадению, но все же удается отбросить некоторые части. Я не использую re.split() в приведенном ниже коде (он будет содержать пустые строки), но я попробовал его без удачи. Я надеюсь, что регулярное выражение будет работать, чтобы избежать сканирования строк за символом в моем коде.

def tokenize_search(line):
    token_pat = re.compile(r'({{)([^{(?!{)]*|[^}(?!})]*)(}})')
    tokens = re.search(token_pat, line).groups()
    return list (tokens)

def tokenize_findall(line):
    token_pat = re.compile(r'({{)([^{(?!{)]*|[^}(?!})]*)(}})')
    tokens = re.findall(token_pat, line)
    return tokens

def check(a1, a2):
    print(a1 == a2)

def main():
    check(tokenize_search('{{}}'), ['{{', '}}'])
    check(tokenize_search('aaa {{}}'), ['{{', '}}'])
    check(tokenize_search('{{aaa}}'), ['{{', 'aaa', '}}'])
    check(tokenize_search('{{aa}} {{bbb}}'), ['{{', 'aa', '}}', '{{', 'bbb', '}}'])
    check(tokenize_search('{{aaa {{ bb }} }}'), ['{{', 'aaa ', '{{', ' bb ', '}}', '}}'])
    check(tokenize_search('{{aa {{ bbb {{ c }} }} }}'), ['{{', 'aa ', '{{', ' bbb ', '{{', ' c ', '}}', '}}', '}}'])
    check(tokenize_search('{{a{a}{{ b{b}b {{ c }} }} }}'), ['{{', 'a{a}', '{{', ' b{b}b ', '{{', ' c ', '}}', '}}', '}}'])

UPDATE

Спасибо Оливье за ​​предоставленное решение. Я все еще надеюсь, что решение для регулярных выражений могло бы работать, если бы я мог лучше понять поиск регулярных выражений. Если я использую метод tokenize_finditer, приведенный ниже, он проходит тесты, и все, что он делает, - заполняет группы skipped промежуточными элементами (за исключением пространства, которое я мог бы постобработать, чтобы сделать код проще). Поэтому я надеюсь, что я смогу добавить выражение or к регулярному выражению '({{)|(}})', которое говорит: `или получить любой символ, за которым следует любой символ, который не соответствует '}}' или '{{'. К сожалению, я не могу написать этот матч. Я видел примеры регулярных выражений, способных даже выполнять рекурсивное сопоставление, и поскольку это не рекурсивно, это звучит даже более выполнимо.

def tokenize_finditer(line):
    token_pat = re.compile(r'({{)|(}})')
    result = []
    if re.search(token_pat, line):
        prev = len(line)
        for match in re.finditer(token_pat, line):
            start, end = match.span()
            if start > prev:
                expr = line[prev:start]
                if not expr.isspace():
                    result.append(expr)
            prev = end
            result.append(match.group())

    return result

Ответы [ 3 ]

0 голосов
/ 25 июня 2018

Я считаю, что подход разделения Оливье Мелансона - это путь.Тем не менее, по-прежнему есть некоторое применение для регулярных выражений, например, проверка правильности сбалансированного рассматриваемого шаблона или извлечение сбалансированного из большой строки (как показано во втором примере).

Для этого требуется рекурсивное регулярное выражениевот так:

{{((?>(?:(?!{{|}}).)++|(?R))*+)}}

Демо

Поскольку модуль re Python не поддерживает рекурсию regex, вам нужно будет полагаться на альтернативу модуль regex , чтобы использовать его.

Для дальнейшей обработки результата матча вам нужно будет взглянуть на внутреннюю часть в $1 и углубиться на один уровень за раз, например, \w+|{{((?>(?:(?!(?:{{|}})).)++|(?R))*+)}} но это громоздко.

0 голосов
/ 30 августа 2018

только что получил твое сообщение в твиттере :) Я знаю, что я опоздал на вечеринку на 2 месяца, но у меня есть пара новых идей на случай, если тебе интересно.

Я просмотрел примеры изаметил, что вы можете в значительной степени избежать неприятностей с сопоставлением и захватом всех "{{" или "}}" или "токена, который находится в середине пары {{}}".К счастью, это довольно просто выразить:

/({{|}}|(?:(?!{{|}})[^ ])+(?!({{(?2)*?}}|(?:(?!{{|}}).)*)*$))/g

В regex101, используя ваши примеры

"в середине пары {{}}" является единственнымсложная часть.Для этого я использовал этот негативный взгляд, чтобы убедиться, что мы НЕ находимся в позиции, за которой следует сбалансированное количество (потенциально вложенных) пар {{}}, а затем конец строки.Это для хорошо сбалансированного ввода гарантирует, что все совпадающие токены находятся внутри {{}} пар.

Теперь вы спросите, а как насчет части "хорошо сбалансированный ввод"?Если ввод неверен, то пример, такой как "aaa}}", даст в результате ["aaa", "}}"].Не идеально.Вы можете проверить ввод отдельно;или, если вы хотите превратить это в неукротимого монстра, то вы можете пойти на что-то вроде этого:

/(?:^(?!({{(?1)*?}}|(?:(?!{{|}}).)*)*+$)(*COMMIT)(*F))?({{|}}|(?:(?!{{|}})[^ ])+(?!({{(?3)*?}}|(?:(?!{{|}}).)*)*+$))/g

Unleashed на regex101

Это действительно простодля видимости.Я согласен с другими предложениями, рекомендующими синтаксический анализатор или какой-либо другой более поддерживаемый инструмент.Но если вы видели мой блог, то понимаете, что у меня есть сходство с этими монстрами:)

0 голосов
/ 24 июня 2018

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

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

import re

def partition(s, sep):
    tokens = s.split(sep)

    # Intersperse the separator betweem found tokens
    partition = [sep] * (2 * len(tokens) - 1)
    partition[::2] = tokens

    # We remove empty and whitespace-only tokens
    return [tk for tk in partition if tk and not tk.isspace()]


def tokenize_search(line):
    # Only keep what is inside brackets
    line = re.search(r'{{.*}}', line).group() or ''

    return [tk for sub in partition(line, '{{') for tk in partition(sub, '}}')]

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

...