【Python-Markdown】ソースコードを強調表示するための拡張機能について


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

概要

  • fenced_code拡張機能とcodehilite拡張機能の特徴について。

  • fenced_code拡張機能を使用したシンタックスハイライトのフレームワーク。

  • マークダウンで<pre>タグにクラス属性とID属性を付与する。

  • attr_list拡張機能と組み合わせてマークダウンから<code>タグにキー/バリューを付与する。

  • codehilite拡張機能を追加し、Pygmentsツールからシンタックスハイライトを使用する。

注意

ここでは一部の拡張機能について実装しているので、Python-Markdownのインストールから全体的な拡張機能の実装は以下の記事をご参照ください。

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

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

fenced_code拡張機能とcodehilite拡張機能の特徴について

fenced_code拡張機能とcodehilite拡張機能の使い方についてそれぞれ簡単に実装します。

詳しくは後の方で実装するということで、まずはfenced_code拡張機能を追加すると、3つのバッククォート(```)やチルダ(~~~)で文字列を囲むことによって<pre><code>としてHTMLに変換されます。

import markdown

md = markdown.Markdown(extensions=['fenced_code',])

text = """
```
source code
```
"""

html = md.convert(text)
print(html)
"""
<pre><code>source code
</code></pre>
"""

次にcodehilite拡張機能です。

codehilite拡張機能を使用するには、fenced_code拡張機能と合わせて使います。

md = markdown.Markdown(
    extensions=[
        'fenced_code',
        'codehilite',
    ]
)

マークダウンの書式自体は変わりませんが出力結果が異なります。

出力結果は<div class="codehilite"><pre>...というHTMLに変換されます。

text = """
```
source code
```
"""

html = md.convert(text)
print(html)
"""
<div class="codehilite"><pre><span></span><code>source code
</code></pre></div>
"""

通常のfeced_code拡張機能で囲ったHTMLの結果と違い、codehilite拡張機能ではPygmentsエンジンを使う為のHTMLの結果として出力されます。

補足

PygmentsはPythonに適したシンタックスハイライト(構文強調)です。

構成設定のextension_configsの内容を変えることで手間無くコードブロック内のテーマカラーの変更や行番号などを設置することができる。

codehilite拡張機能はPygmentsに依存しているので、外部のシンタックスハイライト(Prism.jsやhighlightjs)を使用する場合はfenced_code拡張機能だけを追加するようにします。

fenced_code

クラス属性とID属性を付与する

fenced_code拡張機能についてです。

1行目のバッククォートの後に波括弧を使ってコードブロック内の言語の設定、そしてクラス属性の場合はドット(.)、ID属性の場合はシャープ(#)を使って要素を渡すことができます。

注意

最初の文字列がソースコード内の言語として読み込まれる({ .言語 .class-name #id-name })。

md = markdown.Markdown(extensions=['fenced_code',])

text = """
```{ .lang .class-name #id-name }
source code
```
"""

html = md.convert(text)
print(html)
"""
<pre id="id-name" class="class-name"><code class="language-lang">source code
</code></pre>
"""

<code>タグにキーとバリューを設定したい場合は拡張機能のattr_listを追加します。

md = markdown.Markdown(extensions=['fenced_code', 'attr_list']) # 属性リスト拡張機能の追加

# キー&バリューであるtitleを追記
text = """
```{ .lang .class-name #id-name title="Source Code" }
source code
```
"""

html = md.convert(text)
print(html)
"""
<pre id="id-name" class="class-name"><code class="language-lang" title="Source Code">source code
</code></pre>
"""

fenced_code拡張機能でソースコードを強調表示させ、さらにシンタックスハイライトさせるには「Prism.js」や「highlightjs」といったフレームワークが必要です。

以下はPrism.jsによってシンタックスハイライトしたソースコードの例です。


md = markdown.Markdown(extensions=['fenced_code', 'attr_list']) # 属性リスト拡張機能の追加

# キー&バリューであるtitleを追記
text = """
```{ .python }
def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
```

```{ .python .line-numbers title="Python Code" }
def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
```
"""

html = md.convert(text)
print(html)
"""
<pre><code class="language-python">def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
</code></pre>
<pre class="line-numbers"><code class="language-python" title="Python Code">def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
</code></pre>
"""

codehilite

codehilite拡張機能です。

codehilite拡張機能では、Pygmentsというツールを使ってコードブロック内のコードをシンタックスハイライトします。

そのためには、別途Pygmentsをpipでインストールします。

pip install Pygments

extensionsにはfenced_code拡張機能を追加した上でcodehilite拡張機能を追加します。

構成設定のextension_configsパラメータに何も設定しないと以下のように動作します。

md = markdown.Markdown(extensions=['fenced_code', 'codehilite'])

text = """
```
def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
```
"""

html = md.convert(text)
print(html)
"""
<div class="codehilite"><pre><span></span><code><span class="k">def</span> <span class="nf">markdown_to_html</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">extensions</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;fenced_code&#39;</span><span class="p">,</span> <span class="s1">&#39;codehilite&#39;</span><span class="p">])</span>
    <span class="kn">import</span> <span class="nn">markdown</span>

    <span class="n">html</span> <span class="o">=</span> <span class="n">markdown</span><span class="o">.</span><span class="n">markdown</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">extensions</span><span class="o">=</span><span class="n">extensions</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">html</span>
</code></pre></div>
"""

上記のHTMLをWebサイトで表示した場合のイメージは以下のようになります。

Pygmentsツールにあるシンタックスハイライトを使用する最も簡単だと思われる設定は以下です。

# extension_configsパラメータに渡す辞書
config = {
    'codehilite':{
        'noclasses': True,
    }
}

# extension_configsパラメータの設定
md = markdown.Markdown(extensions=['fenced_code', 'codehilite'], extension_configs=config)

text = """
```
def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
```
"""

html = md.convert(text)
print(html)
"""
<div class="codehilite" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><code><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">markdown_to_html</span>(text, extensions<span style="color: #666666">=</span>[<span style="color: #BA2121">&#39;fenced_code&#39;</span>, <span style="color: #BA2121">&#39;codehilite&#39;</span>])
    <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">markdown</span>

    html <span style="color: #666666">=</span> markdown<span style="color: #666666">.</span>markdown(text, extensions<span style="color: #666666">=</span>extensions)
    <span style="color: #008000; font-weight: bold">return</span> html
</code></pre></div>
"""

上記のHTMLをWebサイトで表示した場合のイメージは以下のようになります。

さらに簡易的にシンタックスハイライトさせる方法は、バッククォート3つの後に波括弧を使って記述します。

補足

attr_list拡張機能とは関係なく動作します。

md = markdown.Markdown(extensions=['fenced_code', 'codehilite'])

text = """
```{ noclasses=True}
def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
```
"""

html = md.convert(text)
print(html)
"""
<div class="codehilite" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><code><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">markdown_to_html</span>(text, extensions<span style="color: #666666">=</span>[<span style="color: #BA2121">&#39;fenced_code&#39;</span>, <span style="color: #BA2121">&#39;codehilite&#39;</span>])
    <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">markdown</span>

    html <span style="color: #666666">=</span> markdown<span style="color: #666666">.</span>markdown(text, extensions<span style="color: #666666">=</span>extensions)
    <span style="color: #008000; font-weight: bold">return</span> html
</code></pre></div>
"""

codehilite拡張機能ではextension_configsによって構成設定の初期値を定めるか、マークダウンの波括弧を使用して構成設定を操作するかで柔軟にシンタックスハイライトすることができます。

以下はcodehilite拡張機能の構成設定の一部です。

codehilite拡張機能の構成設定の種類

Key Value
linenums デフォルト=False、行番号を付与するか
guess_lang デフォルト=True、自動で言語を構文強調させるか
css_class デフォルト='css_class.perldoc'
pygments_style デフォルト='defaul'、シンタックスハイライトのテーマを設定する。Pygments-Style一覧
noclasses デフォルト=False、CSSを使う場合はFalse。Trueだとpygments_styleどおりのスタイルになる
use_pygments デフォルト=True
lang_prefic デフォルト='language-'

その他はPygmentsのオプションをご参照下さい。

最後にマークダウンからシンタックスハイライトのテーマをそれぞれ設定してみます。

md = markdown.Markdown(extensions=['fenced_code', 'codehilite'])

text = """

- default

```{ noclasses=True pygments_style='default'}
def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
```

- monokai

```{ hl_lines="2" noclasses=True pygments_style='monokai'}
def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
```

- solarized-light

```{ hl_lines="4 5" noclasses=True pygments_style='solarized-light'}
def markdown_to_html(text, extensions=['fenced_code', 'codehilite'])
    import markdown

    html = markdown.markdown(text, extensions=extensions)
    return html
```
"""

html = md.convert(text)
print(html)
"""
<ul>
<li>default</li>
</ul>
<div class="codehilite" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><code><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">markdown_to_html</span>(text, extensions<span style="color: #666666">=</span>[<span style="color: #BA2121">&#39;fenced_code&#39;</span>, <span style="color: #BA2121">&#39;codehilite&#39;</span>])
    <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">markdown</span>

    html <span style="color: #666666">=</span> markdown<span style="color: #666666">.</span>markdown(text, extensions<span style="color: #666666">=</span>extensions)
    <span style="color: #008000; font-weight: bold">return</span> html
</code></pre></div>

<ul>
<li>monokai</li>
</ul>
<div class="codehilite" style="background: #272822"><pre style="line-height: 125%;"><span></span><code><span style="color: #66d9ef">def</span> <span style="color: #a6e22e">markdown_to_html</span><span style="color: #f8f8f2">(text,</span> <span style="color: #f8f8f2">extensions</span><span style="color: #f92672">=</span><span style="color: #f8f8f2">[</span><span style="color: #e6db74">&#39;fenced_code&#39;</span><span style="color: #f8f8f2">,</span> <span style="color: #e6db74">&#39;codehilite&#39;</span><span style="color: #f8f8f2">])</span>
<span style="background-color: #49483e">    <span style="color: #f92672">import</span> <span style="color: #f8f8f2">markdown</span>
</span>
    <span style="color: #f8f8f2">html</span> <span style="color: #f92672">=</span> <span style="color: #f8f8f2">markdown</span><span style="color: #f92672">.</span><span style="color: #f8f8f2">markdown(text,</span> <span style="color: #f8f8f2">extensions</span><span style="color: #f92672">=</span><span style="color: #f8f8f2">extensions)</span>
    <span style="color: #66d9ef">return</span> <span style="color: #f8f8f2">html</span>
</code></pre></div>

<ul>
<li>solarized-light</li>
</ul>
<div class="codehilite" style="background: #fdf6e3"><pre style="line-height: 125%;"><span></span><code><span style="color: #859900">def</span> <span style="color: #268bd2">markdown_to_html</span><span style="color: #657b83">(text,</span> <span style="color: #657b83">extensions</span><span style="color: #93a1a1">=</span><span style="color: #657b83">[</span><span style="color: #2aa198">&#39;fenced_code&#39;</span><span style="color: #657b83">,</span> <span style="color: #2aa198">&#39;codehilite&#39;</span><span style="color: #657b83">])</span>
    <span style="color: #cb4b16">import</span> <span style="color: #268bd2">markdown</span>

<span style="background-color: #eee8d5">    <span style="color: #657b83">html</span> <span style="color: #93a1a1">=</span> <span style="color: #657b83">markdown</span><span style="color: #93a1a1">.</span><span style="color: #657b83">markdown(text,</span> <span style="color: #657b83">extensions</span><span style="color: #93a1a1">=</span><span style="color: #657b83">extensions)</span>
</span><span style="background-color: #eee8d5">    <span style="color: #859900">return</span> <span style="color: #657b83">html</span>
</span></code></pre></div>
"""

上記のHTMLをWebサイトで表示した場合のイメージは以下のようになります。

PygmentsのCSSファイルを取得したい場合は、ターミナル上でコマンドを打ちます。

$ pygmentize -S default -f html -a .codehilite > default.css

するとカレントディレクトリに「default.css」がダウンロードされているはずです。

詳しくは【Python】Python-Markdownで拡張機能を追加する(その2)の中で説明しています。

それでは以上となります。

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