Пропустить обработку изолированных блоков кода при построчной обработке файлов Markdown - PullRequest
0 голосов
/ 16 июня 2020

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

У меня есть Python, который последовательно просматривает файл Markdown и перезаписывает [[wikilinks]] как стандартные ссылки в стиле Markdown [wikilink](wikilink). Я делаю это, используя два регулярных выражения в одной функции, как показано ниже:

def modify_links(file_obj):
"""
Function will parse file contents (opened in utf-8 mode) and modify standalone [[wikilinks]] and in-line
[[wikilinks]](wikilinks) into traditional Markdown link syntax.

:param file_obj: Path to file
:return: List object containing modified text. Newlines will be returned as '\n' strings.
"""

file = file_obj
linelist = []
logging.debug("Going to open file %s for processing now.", file)
try:
    with open(file, encoding="utf8") as infile:
        for line in infile:
            linelist.append(re.sub(r"(\[\[)((?<=\[\[).*(?=\]\]))(\]\])(?!\()", r"[\2](\2.md)", line))
            # Finds  references that are in style [[foo]] only by excluding links in style [[foo]](bar).
            # Capture group $2 returns just foo
            linelist_final = [re.sub(r"(\[\[)((?<=\[\[)\d+(?=\]\]))(\]\])(\()((?!=\().*(?=\)))(\))",
                                     r"[\2](\2 \5.md)", line) for line in linelist]
            # Finds only references in style [[foo]](bar). Capture group $2 returns foo and capture group $5
            # returns bar
except EnvironmentError:
    logging.exception("Unable to open file %s for reading", file)
logging.debug("Finished processing file %s", file)
return linelist_final

Это отлично работает для большинства файлов Markdown. Однако иногда я могу получить файл Markdown, который содержит [[wikilinks]] внутри изолированных блоков кода, например:

# Reference

Here is a reference to “the Reactome Project” using smart quotes.

Here is an image: ![](./images/Screenshot.png)


[[201802150808]](Product discovery)

```
[[201802150808 Product Prioritization]]

def foo():
    print("bar")

```

В приведенном выше случае я должен пропустить обработку [[201802150808 Product Prioritization]] внутри изолированного блока кода. У меня есть регулярное выражение, которое правильно идентифицирует огороженный блок кода, а именно:

(?<=```)(.*?)(?=```)

Однако, поскольку существующая функция работает построчно, я не смог найти способ пропустить весь раздел в для l oop. Как мне go об этом?

Ответы [ 2 ]

0 голосов
/ 22 июня 2020

Мне удалось создать достаточно полное решение этой проблемы, внеся несколько изменений в мою исходную функцию, а именно:

  • Заменить python re встроенным * на * Модуль 1005 * доступен в PyPi.
  • Измените функцию для чтения всего файла в одну переменную вместо чтения его построчно.

Обновленная функция выглядит следующим образом:

import regex 

def modify_links(file_obj):
"""
Function will parse file contents (opened in utf-8 mode) and modify standalone [[wikilinks]] and in-line
[[wikilinks]](wikilinks) into traditional Markdown link syntax.

:param file_obj: Path to file
:return: String containing modified text. Newlines will be returned as '\\n' in the string.
"""

file = file_obj
try:
    with open(file, encoding="utf8") as infile:
        line = infile.read()
        # Read the entire file as a single string
        linelist = regex.sub(r"(?V1)"
                             r"(?s)```.*?```(*SKIP)(*FAIL)(?-s)|(?s)`.*?`(*SKIP)(*FAIL)(?-s)"
        #                    Ignore fenced & inline code blocks. V1 engine allows in-line flags so 
        #                    we enable newline matching only here.
                             r"|(\ {4}|\t).*(*SKIP)(*FAIL)"
        #                    Ignore code blocks beginning with 4 spaces/1 tab
                             r"|(\[\[(.*)\]\](?!\s\(|\())", r"[\3](\3.md)", line)
        # Finds  references that are in style [[foo]] only by excluding links in style [[foo]](bar) or
        # [[foo]] (bar). Capture group $3 returns just foo
        linelist_final = regex.sub(r"(?V1)"
                                   r"(?s)```.*?```(*SKIP)(*FAIL)(?-s)|(?s)`.*?`(*SKIP)(*FAIL)(?-s)"
                                   r"|(\ {4}|\t).*(*SKIP)(*FAIL)"
        #                          Refer comments above for this portion.
                                   r"|(\[\[(\d+)\]\](\s\(|\()(.*)(?=\))\))", r"[\3](\3 \5.md)", linelist)
        # Finds only references in style [[123]](bar) or [[123]] (bar). Capture group $3 returns 123 and capture
        # group $5 returns bar
except EnvironmentError:
    logging.exception("Unable to open file %s for reading", file)
return linelist_final

Вышеупомянутая функция обрабатывает [[wikilinks]] во встроенных кодовых блоках, изолированных кодовых блоках и кодовых блоках с отступом в 4 пробела. В настоящее время существует один ложноположительный сценарий, при котором он игнорирует действительный [[wiklink]], когда ссылка появляется на 3-м уровне или глубже в списке Markdown, то есть:

* Level 1
  * Level 2
    * [[wikilink]] #Not recognized
      * [[wikilink]] #Not recognized.

Однако в моих документах нет вики-ссылок на этом уровне вложены в списки, поэтому для меня это не проблема.

0 голосов
/ 16 июня 2020

Вам необходимо использовать полный анализатор Markdown, чтобы иметь возможность охватить все крайние случаи. Конечно, большинство парсеров Markdown конвертируют Markdown напрямую в HTML. Однако некоторые будут использовать двухэтапный процесс, в котором на первом этапе необработанный текст преобразуется в абстрактное синтаксическое дерево (AST), а на втором этапе AST преобразуется в выходной формат. Нередко можно найти средство рендеринга Markdown (выводит Markdown), которое может заменить средство рендеринга по умолчанию HTML.

Вам просто нужно будет изменить шаг парсера (используя плагин для добавления поддержки синтаксиса wikilink ) или измените AST напрямую. Затем передайте AST модулю рендеринга Markdown, который предоставит вам хорошо отформатированный и нормализованный документ Markdown. Если вы ищете решение Python, mistune Pando c Filters может быть хорошим местом для начала.

Но зачем go через все это, когда несколько хорошо продуманных регулярных выражений можно запустить в исходном тексте? Потому что разбор Markdown сложен. Я знаю, сначала это кажется простым. В конце концов, Markdown легко читается человеком (что было одной из определяющих целей дизайна). Однако синтаксический анализ на самом деле очень сложен, поскольку части синтаксического анализатора зависят от предыдущих шагов.

Например, в дополнение к изолированным блокам кода, как насчет блоков кода с отступом? Но вы не можете просто проверить отступ в начале строки, потому что одна строка вложенного списка может выглядеть идентично блоку кода с отступом. Вы хотите пропустить блок кода, но не абзац, вложенный в список. А что, если ваша викилинк разбита на две строки? Обычно при разборе встроенной разметки парсеры Markdown обрабатывают одиночный разрыв строки не иначе, как пробел. Смысл всего этого в том, что прежде чем вы сможете начать синтаксический анализ встроенных элементов, весь документ необходимо сначала проанализировать на различные элементы уровня блока. Только после этого вы сможете проходить по ним и анализировать встроенные элементы, такие как ссылки.

Я уверен, что есть другие крайние случаи, о которых я не думал. Единственный способ охватить их все - использовать полноценный парсер Markdown.

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