Markdown+yaml からの HTML 生成

表題の通り、Markdown+yaml の元データから Jinja2 テンプレートに色々ぶちこんでブログ記事用 HTML ファイルを作成するコードを用意しました。この記事前後で HTML ファイルの書き方が何か変わったなとなるかもしれませんが、Python の Markdown モジュールの変換におまかせしている現状なのでその辺はご容赦……。

Markdown は後述する 1 箇所を除いて通常の形式で、基本的にメインの記事部分のみ記しています。一方で yaml はメインテキスト外の記事に関連するデータを記していて、例えばこの記事だと

articledata:
  htmlname: autoarticle
  title: Markdown+yamlからのArticle生成
  category: tech
  publisheddate: 2021-06-10
  updateddate: 2021-06-10

みたいな感じで、HTML ファイルの名前とか日付とかですね。ちなみに↑のソースコード表示は Pygments を import して、 codehilite エクステンションを Markdown → HTML 変換時に呼ぶことで解決しました(ググればそれっぽい技術記事がいっぱい出てきて助かるね)。いや地味に解決してない部分もあるんですが……。

で、通常の Markdown と一箇所違う点として、Jinja2 のテンプレート内にあるマクロを直接書いて最終的な HTML ファイルに反映されるようにしてます。何を言ってるのかというと、例えば次のような表記を Markdown で書き込むとして、


{{ macro.singlephotography("../../../", "20210602_DSC00849", "hoge") }}

これを含んだ状態で Jinja2 の render を経るとマクロがいい感じに呼ばれて次のような HTML の要素ができます。

<p class="singlephotography">
    <a href="../../../images/photography/2048px/20210602_DSC00849_blog.JPG">
        <img loading="lazy" src="../../../images/photography/1080px/20210602_DSC00849_blog_1080px.JPG" alt="hoge">
    </a>
</p>

これを実際に表示したのが↓の画像です(Markdown ではここに最初の Jinja2 Expression をそのまま書いてます)。

hoge

(この記事の最初の写真です。)

我ながら頓珍漢な事をしてる気しかしませんが、つまるところ Markdown から変換した HTML Strings 内に Jinja2 の Expression をそのまま書いてテンプレートに突っ込んで render すると、再帰的な Expression のチェックが発生せず生のテキストとして HTML ファイル上に書き込まれるっぽいんですね。
なのでそれを悪用(?)して、 最初のテンプレート上では import しなかったマクロテンプレートを一度 render した HTML ファイルにねじ込んでやった上で、 Markdown から展開されたマクロ関係の Expression をチェックさせるためにもう一度 render をかけてやることによって Markdown から Jinja2 形式のマクロを直接呼ぶことができる(ように見える)ということです。

もっと他にやり様があるだろこれ……という気がするのはさておき、これのデメリットとして Markdown ファイル内の"ソースコードとして記したつもりの Jinja2 の Expression や Statement"までチェックされてしまうという点があります。
当然といえば当然の話で、例外処理も何もやってない状態(マジで1回 render した HTML ファイルにちょいちょいと追記してもう一回 render してるだけ)なのでそりゃソースコードかどうかなんて識別つかないんですよね。
一方でマクロが直接呼べると何が嬉しいのかという話ですが、例えば下の写真の組のように写真の配置自体を Markdown の時点でサクッと指定できることでしょうか。

hoge
  • hoge
  • hoge

最終的な HTML ファイルの上だと次のような感じ。この要素を一々書いたり修正したりするのだるいよ~~~~~と思ってたので、マクロで呼べるならまあ楽は楽です。

    <div class="table l1r2">
      <div class="left1">
        <a href="../../../images/photography/2048px/20210602_DSC00849_blog.JPG">
        <img loading="lazy" src="../../../images/photography/1080px/20210602_DSC00849_blog_1080px.JPG" alt="hoge">
        </a>
      </div>
      <ul class="right2">
        <li><a href="../../../images/photography/2048px/20210522_DSC08228_blog.JPG">
        <img loading="lazy" src="../../../images/photography/1080px/20210522_DSC08228_blog_1080px.JPG" alt="hoge">
        </a></li>
        <li><a href="../../../images/photography/2048px/20210528_DSC09077_blog.JPG">
        <img loading="lazy" src="../../../images/photography/1080px/20210528_DSC09077_blog_1080px.JPG" alt="hoge">
        </a></li>
      </ul>
    </div>

ただこの程度の使い方だと、例えば Markdown → HTML 変換時に自作のエクステンションを読み込ませてみたり、変換後の HTML に対して BeautifulSoup とかで画像配置を指定した特定要素だけぶっこ抜いて上記の形に置換したり、デメリット無しで実装する方法は普通にあると思います。……後者のそれなら意外とサクッとできるのでは?どうなんでしょうかね。
そもそも2回 render するとこんな動きになるんだ~って気づきが先にあって、それを無理矢理使ってみたくなっただけという部分は否めません。
というか上のような画像の二次元配置を自作の table クラスとか作って管理すること自体筋が悪いような…… Photography ページと同様に grid で管理したほうが良いのかな……

と、いろいろと考えることは尽きませんが、とりあえず各記事の HTML ファイルはこれで生成できる状態になります。後は記事の Category に合わせてディレクトリを指定してファイル生成、細かな修正が必要であれば行って( Jinja2 マクロコードのベタ書きとか写真以外の単発でしか使わない画像の再配置とか)作成完了というわけですね。

変換についてはそんな感じです。最近はやる気も少しずつ出てきて、夜はこれ含めて色々SSGもどきみたいなコードをちびちび弄っては直し弄っては直しという生活になってきました。
まあ客観的に見たら車輪の再発明の再発明の再発明みたいなことやってシコり散らしてるだとは思いますが……どうせ規模がでかくなる前に飽きてぶん投げるか、でかくなった後独自仕様すら把握できなくなり辛くなってぶん投げるとは思うので勘弁してください。

次は Category 単位に記事を纏めた HTML ファイルを自動生成する部分でも作ろうと思います。多分それができれば他のもなし崩し的にできそうな気はするので。がんばるぞー。

追記(2023/02/05)

ここで書いたデメリット(ソースとして表示したいマクロが展開してしまう)ですが、マクロと同様にMarkdownの段階からエスケープを仕込んでおけばいいっぽいですね。この記事を書いた当時はこれじゃ動かないと勘違いしていましたが、マクロ同様展開回数を考えれば普通に動くのはそりゃそうだという話でした。恥ずかしー。