😆

Djangoで使いたいからPythonでmarkdownをHTML変換する

2023/10/22に公開

目的

markdownをHTML変換

ブログみたいなWEBアプリを開発する際にmarkdownで記事を書いてそれをHTMLに変換して表示という風にやりたい。
やることは以下です。

  1. DBにcontentsにmarkdown形式で記事を保存
  2. markdownをHTMLに変換
  3. 実際に表示

1.の部分は本記事では触れません。

今回やること

https://qiita.com/y-tsutsu/items/1424ce70171765d86316
上記のQiitaの記事を参考にします。
やることとしては以下になります。

  1. MarkdownからHTMLへの変換
  2. テンプレートで使うためにタグを作成
  3. テンプレートに埋め込み

1.MarkdownからHTMLへの変換

必要なライブラリをインストールします(まだインストールしていない場合)

pip install markdown pygments

サンプルコード

import markdown
from markdown.extensions.codehilite import CodeHiliteExtension
from markdown.extensions.toc import TocExtension
from markdown.extensions.sane_lists import SaneListExtension
from markdown.extensions.nl2br import Nl2BrExtension
from markdown.extensions.fenced_code import FencedCodeExtension
from markdown.extensions.wikilinks import WikiLinkExtension
from markdown.extensions.footnotes import FootnoteExtension
from markdown.extensions.tables import TableExtension

text = """
[TOC]
# Title
Here's a brief introduction about the topic.

## Subheading 2
Here's some content for subheading 2. This is a footnote[^1].

### Sub-subheading 3
Some additional details for this sub-subheading.

Here's a simple list:

1. Item 1
3. Item 2

```python
print("This is a code snippet using fenced code.")
```

New lines
will be 
converted to breaks.

[[WikiLink]]

Here's a table:

| Header 1 | Header 2 |
| -------- | -------- |
| Row1Col1 | Row1Col2 |
| Row2Col1 | Row2Col2 |

## Subheading 2
Another subheading with different content.

[^1]: This is the footnote content.
"""

# 使用する拡張のリスト
extensions = [
    CodeHiliteExtension(),
    TocExtension(marker="[TOC]"),
    SaneListExtension(),
    Nl2BrExtension(),
    FencedCodeExtension(),
    WikiLinkExtension(base_url="/wiki/", end_url=".html"),
    FootnoteExtension(),
    TableExtension()
]

# Markdown を HTML に変換
html = markdown.markdown(text, extensions=extensions)
print(html)

補足:拡張が必要なタグ

普通のhタグpタグは変換されますがtableなどは拡張しないといけないみたいです。
以下に拡張が必要なタグの例を示します。
詳しく知りたい方は公式ドキュメントを見てください。

了解しました。まず、上記の拡張の特徴を表形式でまとめます。

拡張名 特徴
tables Markdown のテーブル構文をサポート。
nl2br 通常の改行 (\n) を HTML の <br> に変換。
fenced_code 3つのバックティック (```) で囲まれたコードブロックをサポート。
codehilite Pygments を使用してコードのハイライトをサポート。
wikilinks Wiki スタイルのリンク [[]] をサポート。
toc マークダウン文書の目次 (Table of Contents) の生成をサポート。
sane_lists 一貫したリストのスタイルをサポート。
footnotes 文書内の脚注のサポート。

次に、それぞれの拡張の使い方を説明します。

1. tables

Markdown のテーブル構文を使って、簡単にテーブルを作成できます。

| Header1 | Header2 |
| ------- | ------- |
| Row1C1  | Row1C2  |
| Row2C1  | Row2C2  |

2. nl2br

この拡張を有効にすると、Markdown の中での改行が、HTML の <br> タグとして出力されます。
これがあるとTable内の改行とかもできるんで自分は便利だと感じました。

This is a line.
This is another line right after.

3. fenced_code

3つのバックティックを使用してコードブロックを作成できます。

```
This is a code block.
```

また、言語のハイライトを指定することも可能です。
ただ、codehiliteもですがそのままでは特にクラス名が入るだけなので色もつきません。
先ほどのQiitaの記事のように少し手を加えないとダメです。
コードのシンタックスハイライトのやり方はcodehiliteの部分で書きます。

```python
def hello():
    print("Hello, World!")
```

4. codehilite

fenced_code と組み合わせて使用すると、Pygments でのコードのハイライトが可能になります。

```python
def hello():
    print("Hello, World!")
```

3, 4の補足「コードのシンタックスハイライトのやり方」

Pygments というのプログラミングコードのシンタックスハイライトを行うためのライブラリを使用します。
Pygments を使ってマークダウンの codehilite 拡張と組み合わせてシンタックスハイライトを行っていきます。

Pygments のインストール

pip install Pygments

codehilite 拡張の使用

次に、マークダウンを変換するときに、codehilite 拡張を使って、
コードブロックのシンタックスハイライトを有効にします。

import markdown

md_text = """
```python
def hello():
    print("Hello, World!")
```
"""

html_output = markdown.markdown(md_text, extensions=['fenced_code', 'codehilite'])
print(html_output)

md_textに含まれるPythonコードブロックをシンタックスハイライトします。

CSSスタイルの適用

Pygments はハイライトのスタイルを変更可能です。
好みのスタイルのCSSを生成して、HTMLページに適用することでできます。

例として、monokai スタイルのCSSを生成する方法は以下です。

pygmentize -S monokai -f html -a .highlight > monokai.css

monokai.css というファイルに monokai スタイルのCSSを出力します。
そして、CSS内の全ての .highlight クラス名を .codehilite に変えます。
このCSSファイルをHTMLページにリンクすることで、シンタックスハイライトのスタイルを適用できます。
カラースキームの一覧は以下で見れます。

pygmentize -L styles

また、公式の Pygments ウェブサイトにも、様々なスタイルのプレビューとともにスタイルの一覧が掲載されていて、
ウェブサイト上で具体的なコードとともにそれぞれのスタイルがどのように見えるかを確認可能みたいです。

Pygments official website の「Demo」セクションや「Styles gallery」見ることができます。

HTMLページに適用

生成した monokai.css ファイルをHTMLページに組み込みます。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Highlighted Code</title>
    <link rel="stylesheet" href="monokai.css">
</head>
<body>
    <!-- ここに markdown から変換された HTML 出力を挿入 -->
</body>
</html>

以上で、 Pygments を使ったシンタックスハイライトを適用することができます。

Wiki のような内部リンクを作成することができます。

See [[PageName]] for more details.

6. toc

文書の冒頭や任意の位置に [TOC] を追加すると、目次が自動生成されます。
これも以下のようにカスタマイズできるようになっています。

オプション 説明
title 目次のタイトルを設定します。
baselevel どのヘッダレベルから目次を開始するかを設定します。たとえば、baselevel=2 とすると、h2 からのヘッダが目次として考慮されます。
permalink 各ヘッダにパーマリンク(アンカーリンク)を追加します。
slugify ヘッダテキストからURLフレンドリーなスラッグを生成する方法をカスタマイズします。

以下がコード例です。

import markdown
from markdown.extensions.toc import TocExtension

text = """
# Title
## Subheading 1
### Sub-subheading 1
## Subheading 2
"""

# `toc` 拡張をカスタマイズして使用
toc_extension = TocExtension(title="Table of Contents", baselevel=2, permalink=True)
html = markdown.markdown(text, extensions=[toc_extension])
print(html)

7. sane_lists

sane_lists という名前の拡張機能は、一般的なマークダウンパーサによく見られるリストの振る舞いの問題を修正するものです。

通常のマークダウンのパーサでは、リスト項目の番号は実際の値に関係なく、連続した数字を使用することが要求されることが多いです。
例えば

1. Item 1
1. Item 2
1. Item 3

上記のマークダウンは、以下のように正常に解析されます。

1. Item 1
2. Item 2
3. Item 3

しかし、中断がある場合、例えば

1. Item 1
3. Item 2

通常のマークダウンのパーサは、これを以下のように解析することがあります:

1. Item 1
2. Item 2

sane_lists 拡張を使用すると、この振る舞いが修正され、実際のリストの数字がそのまま使用されます。
この拡張を有効にすると、中断があるリストも正確に解析されます。
したがって、上記の例は次のようになります:

1. Item 1
3. Item 2

この拡張は、文書内でリストの順序が特定の意味や目的を持つ場合に特に役立ちます。

8. footnotes

文書の任意の位置に脚注を追加できます。

This is a statement[^1].

[^1]: This is the footnote.

2. テンプレートで使うためにタグを作成

ここは先ほどのQiitaの記事ほぼまんまですね。
変更ない部分はコピペにしています。
templatetagsディレクトリを用意して、その中で自作フィルタを実装します.今回は例としてmyfilter.pyとしています。

app/
    __init__.py
    models.py
    templatetags/
        __init__.py
        myfilter.py
    views.py

Qiitaの記事には書かれていませんでしたが、settings.pyにも変更が必要です。
DIRSの部分ですね。

settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

では、次にmyfilter.pyでカスタムタグを作成します。
先ほどのpythonのコードコピペですね。

myfilter.py
from django import template
from django.template.defaultfilters import stringfilter
import markdown
from markdown.extensions.codehilite import CodeHiliteExtension
from markdown.extensions.toc import TocExtension
from markdown.extensions.sane_lists import SaneListExtension
from markdown.extensions.nl2br import Nl2BrExtension
from markdown.extensions.fenced_code import FencedCodeExtension
from markdown.extensions.wikilinks import WikiLinkExtension
from markdown.extensions.footnotes import FootnoteExtension
from markdown.extensions.tables import TableExtension

register = template.Library()

@register.filter
@stringfilter
def markdown2html(value):
    extensions = [
        CodeHiliteExtension(),
        TocExtension(marker="[TOC]"),
        SaneListExtension(),
        Nl2BrExtension(),
        FencedCodeExtension(),
        WikiLinkExtension(base_url="/wiki/", end_url=".html"),
        FootnoteExtension(),
        TableExtension()
    ]

    # Markdown を HTML に変換
    return markdown.markdown(value, extensions=extensions)

3. テンプレートに埋め込み

最後にtemplates内で使得るようにします。
以下でカスタムタグを読み込むことができます。

{% load myfilter %}

次に以下でメソッドを呼び出します。
ここにあるsafeHTMLの自動エスケープへの対策です。
これがないとHTMLって認識してくれない程度の理解で大丈夫だと思います。

{{ markdowntxt | markdown2html | safe }}

終わりに

PythonでmarkdownをHTML変換することは出来ました。
あとはCSSなどで見た目を整えるだけですね。
また、コードブロックなど標準ではカスタマイズできない部分もあるのでそこらへんも考えないといけませんね。クリックでコピペとか。
Zennのようなものを作ろうとするとかなり大変なことがわかりました。
Noteのように直感的にわかりやすいエディタでHTML保存の方がもしかしたら良いのかもしれませんね。
いうてあれもマークダウン出保存してるかもだけど。
そこらへんは疎いのでわかりません。

ここまで長々と読んでいただきありがとうございました。

Discussion