【Python】Python-Markdownで拡張機能を追加する(その1)


投稿日 2021年2月20日 >> 更新日 2023年3月1日

今回はPython外部ライブラリのPython-Markdownを使用したマークダウン拡張機能の追加(その1)について実装していこうと思います。

デフォルト値で使用できるマークダウン記法については以下の記事をご参照ください。

この記事では「extra」拡張機能の実装となるので、それ以外の拡張機能の実装は以下をご参照ください。

実行環境&使用ライブラリ

実行環境
Windows Subsystem for Linux
Python 3.6.9
pip 9.0.1
使用ライブラリ ライセンス
Markdown==3.3.3 BSD

拡張機能を設定する方法

拡張機能を設定する場合は、「markdown.markdown()」関数もしくわ「markdown.Markdown()」クラスの引数「extensions」にリスト形式で機能を設定します。

例えば技術ブログでも使われているコードハイライトを使用するには、拡張機能の「fenced_code」を設定します。


import markdown

text = """
```html
<p>HTML Document</p>
<h1>Hello World</h1>
<div class="markdown">
    <p>...</p>
</div>
```
"""

html = markdown.markdown(text, extensions=['fenced_code']) # 追加
print(html)
<pre><code class="html">&lt;p&gt;HTML Document&lt;/p&gt;
&lt;h1&gt;Hello World&lt;/h1&gt;
&lt;div class=&quot;markdown&quot;&gt;
    &lt;p&gt;...&lt;/p&gt;
&lt;/div&gt;
</code></pre>

拡張機能はmarkdownモジュールのクラスオブジェクトに渡して初期化することもできます。


import markdown

text = """
```html
<p>HTML Document</p>
<h1>Hello World</h1>
<div class="markdown">
    <p>...</p>
</div>
```
"""

md = markdown.Markdown(extensions=['fenced_code'])
html = md.convert(text)
print(html)

このようにPython-Markdownでは拡張機能が幾つか備わっているので、幾つでもリストに機能を追加してマークダウン記法を増やすことができます。

モジュールに備わっている拡張機能一覧はこちらです。

拡張機能によるマークダウン一覧(その1)

Python-Markdownの拡張機能は、「extensions」引数に追加したい機能を設定する事でデフォルトでは使えなかったマークダウン記法を認識します。

デフォルトで使用できるマークダウン記法一覧と「extra」以外の拡張機能一覧はこちらです。

この記事では以下の一覧に記載されている拡張機能を実装していきます。

内容 マークダウン(半角記号) HTML(頭タグ例)
extra 以下参照 以下参照
abbr
(略語)
...
*[...]: Title
<abbr title="Title">...
attr_list
(属性リスト)
...
{#id名 .クラス名}
<p class="クラス名" id="id名">...
def_list
(定義リスト)
...
: ...
<dl>
<dt>...
<dd>...
fenced_code
(フェンスで囲まれたコードブロック)
```
...
```

~~~
...
~~~
<pre><code>
footnotes
(脚注拡張版)
...[^1]
[^1]: ...
<p>...<sup id="fnref:1">
<a class="footnote-ref" href="#fn:1">1</a>
</sup></p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>... 
<a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">&#8617;
md_in_html
(HTMLでのマークダウン)
<div markdown="..." <div>
tables
(テーブル)
H1|H2
---|---
C1|C2
<table>
<thead>
<tr>
<th>H1...

上記のマークダウン一覧は拡張機能「Extra」を設定するだけですべてのマークダウンを使用することができます。

extraによる拡張機能

Python-Markdownでは関数もしくはクラスオブジェクトの「extensions」引数にリストとして「extra」拡張機能を設定することで「略語」「属性リスト」「定義リスト」「フェンスで囲まれたコードブロック」「脚注拡張版」「HTMLでのマークダウン」「テーブル」の7つの拡張機能を使用することができます。


import markdown

text = """
...
"""

html = markdown.markdown(text, extensions=['extra'])

1つひとつ拡張機能を追加していく場合は、「extensions」引数にリストとして要素を追加していくだけです。


text = """
...
"""

# 拡張機能
extensions = [
    'abbr',
    'attr_list',
    'def_list',
    'fenced_code',
    'footnotes',
    'tables',
]

html = markdown.markdown(text, extensions=extensions) # 設定

「HTMLでのマークダウン」としての機能を果たす「md_in_html」ですが、個別で追加していくと「ModuleNotFoundError: No module named 'md_in_html'」というエラーとなってしまう為注意が必要です。

abbrによる略語

略語は、両端にスペースの空いた単語をターゲットにその単語を別の段落にて「*[単語]: 正式名称」などとすることで、「<abbr title="Markdown">」のタグで結果を表示します。


import markdown

text = """
MD を HTML に変換する


*[HTML]: Hyper Text Markup Language

*[MD]: Markdown
"""

html = markdown.markdown(text, extensions=['abbr'])
print(html)
<p><abbr title="Markdown">MD</abbr> を <abbr title="Hyper Text Markup Language">HTML</abbr> に変換する</p>

「<abbr title="Markdown">」の単語にカーソルを合わせると、その単語にまつわる文章を浮かび上がらせることができます。

attr_listによる属性リスト

属性リストでは、特定のHTMLタグに「id」または「clss」といった様々な属性を付与することができます。

属性を付与することによって特定の要素(タグの属性)を装飾したりCSSやJavaScripによるレイアウトを変更することができます。

エディターにおける通常の文章は「<p>」タグとしてHTMLに変換されますが、その2段落目に「{: #id名 .クラス名}」とすることで「<p class="クラス名" id="id名">」の結果として表示されます。

マークダウンで記された要素にidまたはclassなどの属性を付与する場合は、同じ段落でターゲットの文章にスペースを空けて記述することで要素に属性を付与することができます。


text = """
pタグにidと属性を追加
{: #id名 .クラス名}

# 見出し {: #header1}
"""

html = markdown.markdown(text, extensions=['attr_list'])
print(html)
<p class="クラス名" id="id名">pタグにidと属性を追加</p>
<h1 id="header1">見出し</h1>

例えば「style」属性に様々なフォントカラーを付与したマークダウンの記述は以下のようになります。


text = """
# Hello {: style=color:red;}
## Hello {: style=color:blue}
### Hello {: style=color:yellow}
#### Hello {: style=color:green}
"""

html = markdown.markdown(text, extensions=['attr_list'])
print(html)
<h1 style="color:red;">Hello</h1>
<h2 style="color:blue">Hello</h2>
<h3 style="color:yellow">Hello</h3>
<h4 style="color:green">Hello</h4>

def_listによる定義リスト

定義リストでは、単語とその単語の説明を一対にして表したい場合などに記述します。

目的の単語の下段落に、「: 説明」とすることで「<dl><dt>...</dt><dd>...」の結果で表示されます。


text = """
アップル
: バラ科のマルス属の植物のポマシーフルーツ。

オレンジ
: 柑橘類の常緑樹の果実。
"""

html = markdown.markdown(text, extensions=['def_list'])
print(html)
<dl>
<dt>アップル</dt>
<dd>バラ科のマルス属の植物のポマシーフルーツ。</dd>
<dt>オレンジ</dt>
<dd>柑橘類の常緑樹の果実。</dd>
</dl>

属性リストの「attr_list」拡張機能と合わせることで、説明欄を強調させることもできます。


text = """
アップル
: バラ科のマルス属の植物のポマシーフルーツ。
{: style=color:white;background-color:black;}


オレンジ
: 柑橘類の常緑樹の果実。
{: style=color:white;background-color:black;}
"""

html = markdown.markdown(text, extensions=['attr_list', 'def_list']) # 追記
print(html)
<dl>
<dt>アップル</dt>
<dd style="color:white;background-color:black;">バラ科のマルス属の植物のポマシーフルーツ。</dd>
<dt>オレンジ</dt>
<dd style="color:white;background-color:black;">柑橘類の常緑樹の果実。</dd>
</dl>

fenced_codeによるフェンスで囲まれたコードブロック

フェンスで囲まれたコードブロックは、ブロック内で記述されたプログラミング言語を構文通り表してくれます。

デフォルトでは強調表示したいコードをバッククォート「'code'」で囲むことにより「<code>」で結果を表示しますが、改行してしまうと認識されずに「<p>」タグなどで表示されてしまいます。

改行してもバッククォート内はコードブロックとして認識させるために「fenced_code」拡張機能を使用します。

因みにこれまで見てきた強調表示されているコードはfenced_codeにより認識され、こうしてブログとして公開することができています。

コードブロックはコードをバッククォート3つ以上(```)もしくはチルダ3つ以上(~~~)で上段・下段と囲うことにより「<pre><code>」のタグで結果を表示します。


text = """

```
import matplotlib.pyplot as plt
import pandas as pd

<p>HTML Document</p>
<h1>Hello World</h1>
<div class="markdown">
    <p>...</p>
</div>
```
"""

html = markdown.markdown(text, extensions=['fenced_code'])
print(html)
<pre><code>import matplotlib.pyplot as plt
import pandas as pd

&lt;p&gt;HTML Document&lt;/p&gt;
&lt;h1&gt;Hello World&lt;/h1&gt;
&lt;div class=&quot;markdown&quot;&gt;
    &lt;p&gt;...&lt;/p&gt;
&lt;/div&gt;
</code></pre>

上段に記述したバッククォート3つ「```」の直後に、任意の単語を追記すると「<code>」タグにclass属性を付与することができます。


text = """
```python
import matplotlib.pyplot as plt
import pandas as pd

df = pd.read_csv('sample.csv')
df.plot()
plt.show()
```
"""

html = markdown.markdown(text, extensions=['fenced_code'])
print(html)
<pre><code class="python">import matplotlib.pyplot as plt
import pandas as pd

df = pd.read_csv('sample.csv')
df.plot()
plt.show()
</code></pre>

コードブロックの中でコードブロックを使用する場合、コードブロック内のマークダウンが認識されてしまい文章中のマークダウンが崩れてしまう場合があります。

その場合は、バッククォートブロック(```...```)とチルダブロック(~~~...~~~)を上手く利用して整形するのが良いと思います。

チルダブロック
```
チルダブロック内にあるバッククォートブロック
```

footnotesによる脚注拡張版

ここでは脚注拡張版と呼びますが、ターゲットとなる文章の脚注をページ最下部で表示しお互いが内部リンクで繋がった状態で表示されます。

デフォルトで使用できる脚注記法は、ターゲットとなる文章に「...[脚注]」を加え、下の段落で同じラベルにコロンと参照先(「[脚注]:/path」)を記述することで脚注先のページへ移動できるリンクを結果として表示することができます。


text = """
python[脚注]でマークダウン。
[脚注]: /path/python
"""

html = markdown.markdown(text)
print(html)
<p>python<a href="/path/python">脚注</a>でマークダウン。</p>

脚注拡張版では、角括弧内にハット([^...])を加えることでページ内最下部に脚注を表示させることができます。

説明が小難しいので実際に行うと以下のようになります。


text = """
Matplotlib[^1]でグラフを描画する

Pandas[^2]でCSVファイルを読み込む

[^1]:
    MatplotlibとはPythonの外部ライブラリである。

    以下のように使用できる。

    ```
    import matplotlib.pyplot as plt
    ```

[^2]:
    PandasとはPythonの外部ライブラリである。

    > Pnadasはデータ分析や解析に使用されるライブラリで、
    > Matplotlibの機能も一部使用することができる。

"""

html = markdown.markdown(text, extensions=['footnotes'])
print(html)
<p>Matplotlib<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup>でグラフを描画する</p>
<p>Pandas<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup>でCSVファイルを読み込む</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>MatplotlibとはPythonの外部ライブラリである。</p>
<p>以下のように使用できる。</p>
<p><code>import matplotlib.pyplot as plt</code>&#160;<a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
<li id="fn:2">
<p>PandasとはPythonの外部ライブラリである。</p>
<blockquote>
<p>Pnadasはデータ分析や解析に使用されるライブラリで、
Matplotlibの機能も一部使用することができる。</p>
</blockquote>
<p><a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">&#8617;</a></p>
</li>
</ol>
</div>

<a>タグに注目してみると、相互に参照できるようリンク先が自動生成されているのがわかります。

md_in_htmlによるHTMLでのマークダウン

HTMLでのマークダウンは、マークダウンエディター内で記述されたHTMLブロック内でのマークダウンをHTMLに変換させます。

デフォルトの動作ではHTMLで記述されたブロック要素内のマークダウンは無視されてしまいます。


text = """
<div>
# 見出し

*太文字*

_斜体_
</div>
"""

html = markdown.markdown(text)
print(html)
<div>
# 見出し

*太文字*

_斜体_
</div>

拡張機能を追加し、HTMLタグに「markdown」属性を与えることによりそのブロック内のマークダウンは正常に処理されます。

拡張機能の「md_in_html」を設定すると「ModuleNotFoundError: No module named 'md_in_html'」というエラーとなってしまうので、「extensions」引数には「extra」を設定しています。


text = """
<div markdown="1">
# 見出し

*太文字*

_斜体_
</div>
"""

html = markdown.markdown(text, extensions=['extra']) # 'md_in_html'
print(html)
<div>
<h1>見出し</h1>
<p><em>太文字</em></p>
<p><em>斜体</em></p>
</div>

tablesによるテーブル

テーブルは、単語や数字などを線で区切った表形式として表示することができます。

パイプ(|)やハイフン(-)で単語や数字を囲むことにより「<table><thead><tr><th>...」の結果で表示されます。


text = """
Header1 | Header2
------- | -------
Cell    | Cell
Cell    | Cell
"""

html = markdown.markdown(text, extensions=['tables'])
print(html)
<table>
<thead>
<tr>
<th>Header1</th>
<th>Header2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell</td>
<td>Cell</td>
</tr>
<tr>
<td>Cell</td>
<td>Cell</td>
</tr>
</tbody>
</table>

その他の拡張機能によるマークダウン

拡張機能の「extra」を設定することで7つの機能を使用できるようになりました。

その他の拡張機能では個々に追加設定していく必要があり、より細かい設定が可能です。

詳しくは以下をご参照ください。

今回は以上となります。

最後までご覧いただきありがとうございました。