Regex для разделения на новые строки при игнорировании новых строк внутри текста, окруженного произвольным количеством кавычек - PullRequest
2 голосов
/ 19 апреля 2020

В Python мне нужно разбить строку на новые строки, игнорируя при этом новые строки внутри текстовых частей, которые окружены произвольным количеством кавычек (например, "" "Это текст в тройных кавычках" "", с тем же количество кавычек в начале и в конце).

Пример строки:

Line outside quotes
Another line
"Two lines inside
normal quotes"
""Two lines inside
fancy "dual" quotes""
"""Three lines inside
"even fancier"
triple quotes"""
Last line

Должен создать список:

['Line outside quotes', 'Another line', '"Two lines inside\nnormal quotes"', 
 '""Two lines inside\nfancy "dual" quotes""', 
 '"""Three lines inside\n"even fancier"\ntriple quotes"""', 
 'Last line']

Вдохновлен ответом от Veedra c, я придумал следующее регулярное выражение для сопоставления групп:

(?:("+)[\s\S]+?\1|.)+

с частью ("+)[\s\S]+?\1, означающей «найти количество кавычек (соответствующая группа), затем число что-нибудь (не жадное), и, наконец, снова совпадающая группа (то же количество кавычек) ".

Согласно тесту на RegExr.com, это регулярное выражение работает так, как я ожидал: https://regexr.com/52qla

Однако, если я реализую это в Python, я получу неожиданный результат. Мой тестовый код:

import re

input = '''Line outside quotes
Another line
"Two lines inside
normal quotes"
""Two lines inside
fancy "dual" quotes""
"""Three lines inside
"even fancier"
triple quotes"""
Last line'''

matcher = re.compile(r'(?:("+)[\s\S]+?\1|.)+')
result = matcher.findall(input)

print(str(result))

Выводит:

['', '', '"', '""', '"""', '']

, что не то, что я ожидаю.

Кажется, не имеет значения, если я используйте встроенный модуль re или модуль regex.

Я надеюсь, у кого-то есть идея. Спасибо!

Ответы [ 4 ]

3 голосов
/ 19 апреля 2020
import re

input = '''Line outside quotes
Another line
"Two lines inside
normal quotes"
""Two lines inside
fancy "dual" quotes""
"""Three lines inside
"even fancier"
triple quotes"""
Last line'''

matcher = re.compile(r'(?:("+)([\s\S]+?)\1|(.+))', re.MULTILINE)
result = matcher.findall(input)
print(["".join(x) for x in result])

Я сделал то, что вы хотите с верхним кодом. В python необходимо добавить re.MULTILINE для многострочной обработки. А для экспорта контента "[\s\S]+?" должен быть капсулирован.

1 голос
/ 19 апреля 2020

Кажется, это работает:

# [...]
matcher = re.compile(r'(?:("+)([\s\S]+?))(\1)|(.+)')
# [...]

производит:

[('', '', '', 'Line outside quotes'), ('', '', '', 'Another line'), ('"', 'Two lines inside\nnormal quotes', '"', ''), ('""', 'Two lines inside\nfancy "dual" quotes', '""', ''), ('"""', 'Three lines inside\n"even fancier"\ntriple quotes', '"""', ''), ('', '', '', 'Last line')]

Я заключил в кавычки строку в их собственных группах. Предложение «else», если вы можете так его назвать, имеет вид |(.+).

Так что, если первое поле пустое, оно является строкой без кавычек и содержится в последнем поле. Иначе, первые три поля содержат кавычки (перед + зад) и внутреннюю строку. Простого "".join(single_result_tuple) для каждого результата должно быть достаточно:

# [...]
result = ["".join(r) for r in result]
# [...]
['Line outside quotes', 'Another line', '"Two lines inside\nnormal quotes"', '""Two lines inside\nfancy "dual" quotes""', '"""Three lines inside\n"even fancier"\ntriple quotes"""', 'Last line']

(С именованными группами вы можете лучше точно извлечь ваш правильный контент.)


И с перестановка групп: (оборачивая все в группу)

matcher = re.compile(r'(("+)[\s\S]+?\2|.+)')

вы можете получить:

[('Line outside quotes', ''), ('Another line', ''), ('"Two lines inside\nnormal quotes"', '"'), ('""Two lines inside\nfancy "dual" quotes""', '""'), ('"""Three lines inside\n"even fancier"\ntriple quotes"""', '"""'), ('Last line', '')]

Таким образом, вы можете проверить, какой стиль цитирования использовался. Содержимое находится в первом поле:

# [...]
result = [r[0] for r in result]
# [...]

Чтобы полностью получить только строку, необходимо выполнить некоторую постобработку. Ссылке \2 нужна группа (...), поэтому вы не можете исключить ее из результата с помощью ?:. (Группа без захвата, если я правильно помню)

1 голос
/ 19 апреля 2020

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

Вместо этого поместите создать реальную группу вокруг полного совпадения и извлечь правильный кортеж с помощью списка-понимания:

import re

input = '''Line outside quotes
Another line
"Two lines inside
normal quotes"
""Two lines inside
fancy "dual" quotes""
"""Three lines inside
"even fancier"
triple quotes"""
Last line'''

result = [x[0] for x in re.findall(r'((\"+)[\s\S]+?\2|.+)', input)]

print(str(result))

Вызов findall здесь вернет список:

[('Line outside quotes', ''), ('Another line', ''), ('"Two lines inside\nnormal quotes"', '"'), ('""Two lines inside\nfancy "dual" quotes""', '""'), ('"""Three lines inside\n"even fancier"\ntriple quotes"""', '"""'), ('Last line', '')]

Вы можете увидеть что первый элемент каждого кортежа содержит нужную вам строку, а второй элемент является (необязательным) соответствием кавычек, а список-понимание извлечет первый элемент каждого кортежа в списке, генерируя правильный результат:

['Line outside quotes', 'Another line', '"Two lines inside\nnormal quotes"', '""Two lines inside\nfancy "dual" quotes""', '"""Three lines inside\n"even fancier"\ntriple quotes"""', 'Last line']

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

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

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

import re

input = '''Line outside quotes
Another line
"Two lines inside
normal quotes"
""Two lines inside
fancy "dual" quotes""
"""Three lines inside
"even fancier"
triple quotes"""
Line that "quotes" something
"quotes" something
Line that "quotes"
Line that "quotes
with newline" something
'''

matches = [x[0] for x in re.findall(r'((?:(\"+)[\s\S]+?\2|[^"\n]+)+)', input)]

for match in matches:
    print("---")
    print(str(match))

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

1 голос
/ 19 апреля 2020

Я попробовал следующее:

import re

input = '''Line outside quotes
Another line
"Two lines inside
normal quotes"
""Two lines inside
fancy "dual" quotes""
"""Three lines inside
"even fancier"
triple quotes"""
Last line'''

matchers = [
    '(\n""")([A-Za-z].*?[A-Za-z])("""\n)', # 3 quotes
    '(\n"")([A-Za-z].*?[A-Za-z])(""\n)',   # 2 quotes
    '(\n")([A-Za-z].*?[A-Za-z])("\n)',     # single quote
]

allResults = []

for m in matchers:
    matcher = re.compile(m, re.MULTILINE|re.DOTALL)
    result = matcher.findall(input)

    allResults += [r[1] for r in result]
    input = matcher.subn("\n", input)[0]


allResults += input.split('\n')
print(allResults)

В принципе, я не знаю, возможно ли отделить одинарную кавычку от мульти кавычек. Итак, идея состоит в том, чтобы go поэтапно извлекать тройные кавычки, двойные кавычки и т. Д. c., По одной за раз.

Этот метод выглядит очень хакерским. Может быть, кто-то еще вдохновится сделать что-нибудь интересное.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...