pandoc filters – Hamel’s Blog - Hamel Husain
Заметка 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-пакета
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": [ "
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
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
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
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("
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"
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") "
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") "