Python регулярное выражение для замены отдельных строк новой строки и игнорирования последовательностей двух или более строк новой строки - PullRequest
2 голосов
/ 21 января 2020

Я использую python 3,6 - 3,8.

Я пытаюсь заменить любой экземпляр одной новой строки на один пробел в тексте, прочитанном из файла. Моя цель - сжать абзацы в одну строку текста для повторного переноса на textwrap. Поскольку textwrap работает только с одним абзацем, мне нужен простой способ обнаружить / выделить абзацы, и сжатие их в одну строку текста представляется наиболее целесообразным. Чтобы это работало, любой экземпляр двух или более символов новой строки в последовательности определяет границу абзаца и должен быть оставлен в покое.

Моя первая попытка была с утверждениями lookahead / lookbehind, чтобы настаивать на том, что заменяемый мной символ новой строки не будет ограниченный другими символами новой строки:

re.sub(r'(?<!\n)\n(?!\n)', ' ', input_text)

В большинстве случаев это прекрасно работает. Однако я быстро наткнулся на случай, когда у кого-то был разделитель абзацев, содержащий другие пробелы.

Это пример текста, начинающийся с короткого абзаца. \ N \ nЭтот второй абзац достаточно длинный, чтобы разделить на две строки, поэтому он содержит \ n одну новую строку в середине. \ n \ nЭтот третий абзац имеет необычный разделитель перед ним; символ новой строки, за которым следует \ na, а затем еще одна строка. Это особый случай, который необходимо обработать \ nhandled.

Мое утверждение tacti c для прогнозирования / просмотра здесь не сработает, потому что требуемый просмотр должен иметь неопределенную длину (может быть пробел) есть, может быть, нет), и это не разрешено.

# this is an error
re.sub(r'(?<!\n\s*)\n(?!\s*\n)', ' ', input_text)

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

# this compresses "\n\n\n" or "\n\n \n" into "\n\n"
re.sub(r'(?<!\n)\n(?!\n)', ' ', re.sub(r'\n\s*\n', '\n\n', input_text))

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

Unicode-определение \s не задает c, что недостаточно для того, чтобы я мог создать набор символов «все пробелы, кроме новых строк», поэтому я не могу что-то сделать вот так:

# this only works for ASCII
re.sub(r'(?<!\n)\n(?!\n)', ' ', re.sub(r'\n[ \t\r\f\v]*\n', '\n\n', input_text))

Для этого мне нужен способ express "\s кроме \n" для Unicode, и я не думаю, что он существует. Я попробовал [\s!\n] на жаворонке и, как ни странно, кажется, что он делает правильные вещи в 3.6.5 и 3.8.0. Это, несмотря на тот факт, что ! не имеет документированного эффекта внутри набора символов для любой версии, и что в документации для re.escape() прямо говорится, что с 3.7, ! больше не экранируется методом, поскольку это не так. особый характер.

# this appears to work, but the docs say it shouldn't
re.sub(r'(?<!\n)\n(?!\n)', ' ', re.sub(r'\n[\s!\n]\n', '\n\n', input_text))

Даже если это работает, я не хочу полагаться на поведение по понятным причинам. Вероятно, я должен сообщить об этом как об ошибке в коде или документации.

Если предположить, что последний не поддерживается, какой еще подход я пропускаю?

1 Ответ

2 голосов
/ 21 января 2020

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

import re
text = "This is some sample text beginning with a short paragraph.\n\nThis second paragraph is long enough to be split across lines, so it contains\na single newline in the middle.\n \nThis third paragraph has an unusual separator before it; a newline followed by\na space followed by another newline. It's a special case that needs to be\nhandled."
print( re.sub(r'([^\S\n]*\n(?:[^\S\n]*\n)+[^\S\n]*)|[^\S\n]*\n[^\S\n]*', lambda x: x.group(1) or ' ', text) )

См. Демонстрационную версию Python

Подробности

  • ([^\S\n]*\n(?:[^\S\n]*\n)+[^\S\n]*) - Группа 1: 0+ пробелы, отличные от новой строки, новой строки, затем 1 или более (таким образом, по крайней мере два новых строки совпадают) появления 0+ пробелов, отличных от новой строки и новой строки, а затем снова 0+ пробелов, отличных от новой строки
  • | - или
  • [^\S\n]*\n[^\S\n]* - 0+ пробелов, отличных от символ новой строки, символ новой строки и снова 0+ пробелов, кроме символа новой строки

Замена lambda x: x.group(1) or ' ': если группа 1 соответствует, замена не должна производиться, в противном случае заменить пробелом.

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