newsmode
search
Меню
arrow_back Назад

pandoc filters – Hamel’s Blog - Hamel Husain

auto_awesomeКраткое саммари

Заметка Hamel Husain посвящена фильтрам pandoc и их применению при конвертации Jupyter-ноутбуков (ipynb) в Markdown. Автор упоминает два Python-пакета — panflute (рекомендуется) и pandocfilters — и советует изучить AST с помощью флага `-t native`. На примере минимального ноутбука показано, чем отличается вывод pandoc (добавляет div-элементы) от quarto, который эти div убирает. Далее приводится фильтр на panflute (flute.py), который оборачивает вывод кода в блоки `CodeOutput` и подменяет ограждение кода на `file=script.py`. Демонстрируется схема ноутбука в формате native до и после применения фильтра.

Two python packages

Два python-пакета

  • panflute - recommended
  • pandocfilters
  • panflute — рекомендуется pandocfilters

    The tutorial on pandoc filters can help you get oriented to the general idea. If rolling your own filters, you probably want to use the JSON filters. Furthermore you can understand the pandoc AST by using the -t native flag (examples of this are shown later).

    Руководство по фильтрам pandoc поможет вам разобраться с общей идеей. Если вы пишете собственные фильтры, вам, скорее всего, стоит использовать JSON-фильтры. Кроме того, понять AST pandoc можно с помощью флага -t native (примеры этого показаны далее).

    The minimal notebook

    Минимальный ноутбук

    Here is minimal notebook we are working with:

    Вот минимальный ноутбук, с которым мы работаем:

    json title="minimal.ipynb" { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## A minimal notebook" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<MyTag></MyTag>" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "# Do some arithmetic\n", "print(1+1)" ] } ], "metadata": { "interpreter": { "hash": "42fd40e048e0585f88ec242f050f7ef0895cf845a8dd1159352394e5826cd102" }, "kernelspec": { "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.7" } }, "nbformat": 4, "nbformat_minor": 4 }

    json title="minimal.ipynb" { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## A minimal notebook" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "# Do some arithmetic\n", "print(1+1)" ] } ], "metadata": { "interpreter": { "hash": "42fd40e048e0585f88ec242f050f7ef0895cf845a8dd1159352394e5826cd102" }, "kernelspec": { "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.7" } }, "nbformat": 4, "nbformat_minor": 4 }

    Minimal ipynb to md converstion with pandoc

    Минимальная конвертация ipynb в md с помощью pandoc

    $ pandoc --to gfm minimal.ipynb <div class="cell markdown"> ## A minimal notebook </div> <div class="cell markdown"> <MyTag></MyTag> </div> <div class="cell code" execution_count="1"> ``` python # Do some arithmetic print(1+1) ``` <div class="output stream stdout"> 2 </div> </div>

    $ pandoc --to gfm minimal.ipynb

    ## A minimal notebook
    ``` python # Do some arithmetic print(1+1) ```
    2

    Minimal ipynb to md converstion with quarto

    Минимальная конвертация ipynb в md с помощью quarto

    $ quarto render minimal.ipynb --to gfm pandoc to: gfm+footnotes+tex_math_dollars-yaml_metadata_block output-file: minimal.md standalone: true default-image-extension: png filters: - crossref Output created: minimal.md

    $ quarto render minimal.ipynb --to gfm pandoc to: gfm+footnotes+tex_math_dollars-yaml_metadata_block output-file: minimal.md standalone: true default-image-extension: png filters: - crossref Output created: minimal.md

    This creates

    Это создаёт

    ## A minimal notebook <MyTag></MyTag> ``` python # Do some arithmetic print(1+1) ``` 2

    ## A minimal notebook ``` python # Do some arithmetic print(1+1) ``` 2

    Running Pandoc With those Extensions

    Запуск Pandoc с этими расширениями

    running pandoc with --standalone --to gfm+footnotes+tex_math_dollars-yaml_metadata_block still adds the divs and looks different than quarto. Somewhere, maybe quarto is removing the divs. We can see the Div elements in the AST when we explore panflute in the sections below.

    запуск pandoc с --standalone --to gfm+footnotes+tex_math_dollars-yaml_metadata_block всё равно добавляет div-элементы и выглядит иначе, чем у quarto. Где-то, возможно, quarto удаляет эти div-элементы. Мы можем увидеть элементы Div в AST, когда будем исследовать panflute в разделах ниже.

    How to use panflute

    Как использовать panflute

    The examples are helpful.

    Примеры могут быть полезны.

    This filter places CodeOutput blocks around code as well as changes the codefence to have file=script.py in order to hack the code fence.

    Этот фильтр размещает блоки CodeOutput вокруг кода, а также меняет ограждение кода так, чтобы в нём было file=script.py, чтобы хакнуть ограждение кода.

    #!/Users/hamel/opt/anaconda3/bin/python #flute.py from typing import Text from panflute import * from logging import warning def increase_header_level(elem, doc): if type(elem) == CodeBlock and type(elem.parent.prev) == CodeBlock: return ([RawBlock("<CodeOutput>"), elem, RawBlock("</CodeOutput>")]) elif type(elem) == CodeBlock: elem.classes = ['file=script.py'] def main(doc=None): return run_filter(increase_header_level, doc=doc) if __name__ == "__main__": main()

    #!/Users/hamel/opt/anaconda3/bin/python #flute.py from typing import Text from panflute import * from logging import warning def increase_header_level(elem, doc): if type(elem) == CodeBlock and type(elem.parent.prev) == CodeBlock: return ([RawBlock(""), elem, RawBlock("")]) elif type(elem) == CodeBlock: elem.classes = ['file=script.py'] def main(doc=None): return run_filter(increase_header_level, doc=doc) if __name__ == "__main__": main()

    This is how we can use this filter and see the rendered output:

    Вот как мы можем использовать этот фильтр и увидеть отрендеренный вывод:

    $ pandoc --to gfm minimal.ipynb --filter "flute.py" <div class="cell markdown"> ## A minimal notebook </div> <div class="cell markdown"> <MyTag></MyTag> </div> <div class="cell code" execution_count="1"> ``` file=script.py # Do some arithmetic print(1+1) ``` <div class="output stream stdout"> <CodeOutput> 2 </CodeOutput> </div> </div>

    $ pandoc --to gfm minimal.ipynb --filter "flute.py"

    ## A minimal notebook
    ``` file=script.py # Do some arithmetic print(1+1) ```
    2

    Note: we could probably replace the inner div with the output class with <CodeOutput> tag

    Примечание: мы, вероятно, могли бы заменить внутренний div с классом output на тег

    Just for completeness, this is the schema of the minimal notebook using the --to native flag prior to applying the filter:

    Просто для полноты — это схема минимального ноутбука с использованием флага --to native до применения фильтра:

    $pandoc --to native minimal.ipynb [ Div ( "" , [ "cell" , "markdown" ] , [] ) [ Header 2 ( "a-minimal-notebook" , [] , [] ) [ Str "A" , Space , Str "minimal" , Space , Str "notebook" ] ] , Div ( "" , [ "cell" , "markdown" ] , [] ) [ Para [ RawInline (Format "html") "<MyTag>" , RawInline (Format "html") "</MyTag>" ] ] , Div ( "" , [ "cell" , "code" ] , [ ( "execution_count" , "1" ) ] ) [ CodeBlock ( "" , [ "python" ] , [] ) "# Do some arithmetic\nprint(1+1)" , Div ( "" , [ "output" , "stream" , "stdout" ] , [] ) [ CodeBlock ( "" , [] , [] ) "2\n" ] ] ]

    $pandoc --to native minimal.ipynb [ Div ( "" , [ "cell" , "markdown" ] , [] ) [ Header 2 ( "a-minimal-notebook" , [] , [] ) [ Str "A" , Space , Str "minimal" , Space , Str "notebook" ] ] , Div ( "" , [ "cell" , "markdown" ] , [] ) [ Para [ RawInline (Format "html") "" , RawInline (Format "html") "" ] ] , Div ( "" , [ "cell" , "code" ] , [ ( "execution_count" , "1" ) ] ) [ CodeBlock ( "" , [ "python" ] , [] ) "# Do some arithmetic\nprint(1+1)" , Div ( "" , [ "output" , "stream" , "stdout" ] , [] ) [ CodeBlock ( "" , [] , [] ) "2\n" ] ] ]

    And after applying the filter:

    И после применения фильтра:

    $pandoc --to native minimal.ipynb --filter flute.py [ Div ( "" , [ "cell" , "markdown" ] , [] ) [ Header 2 ( "a-minimal-notebook" , [] , [] ) [ Str "A" , Space , Str "minimal" , Space , Str "notebook" ] ] , Div ( "" , [ "cell" , "markdown" ] , [] ) [ Para [ RawInline (Format "html") "<MyTag>" , RawInline (Format "html") "</MyTag>" ] ] , Div ( "" , [ "cell" , "code" ] , [ ( "execution_count" , "1" ) ] ) [ CodeBlock ( "" , [ "file=script.py" ] , [] ) "# Do some arithmetic\nprint(1+1)" , Div ( "" , [ "output" , "stream" , "stdout" ] , [] ) [ RawBlock (Format "html") "<CodeOutput>" , CodeBlock ( "" , [] , [] ) "2\n" , RawBlock (Format "html") "</CodeOutput>" ] ] ]

    $pandoc --to native minimal.ipynb --filter flute.py [ Div ( "" , [ "cell" , "markdown" ] , [] ) [ Header 2 ( "a-minimal-notebook" , [] , [] ) [ Str "A" , Space , Str "minimal" , Space , Str "notebook" ] ] , Div ( "" , [ "cell" , "markdown" ] , [] ) [ Para [ RawInline (Format "html") "" , RawInline (Format "html") "" ] ] , Div ( "" , [ "cell" , "code" ] , [ ( "execution_count" , "1" ) ] ) [ CodeBlock ( "" , [ "file=script.py" ] , [] ) "# Do some arithmetic\nprint(1+1)" , Div ( "" , [ "output" , "stream" , "stdout" ] , [] ) [ RawBlock (Format "html") "" , CodeBlock ( "" , [] , [] ) "2\n" , RawBlock (Format "html") "" ] ] ]