この記事は 「Movable Type Advent Calendar 2024」、3 日目の記事です。
以前にも書いたことがありますが、最近は、Movable Type などの CMS を使用しても、ヘッドレス的に使用するケースが多く、結果として API を使用するだけか、テンプレートを書いたとしても、要件にあわせた JSON を出力するための JSON 用テンプレートしか書いていないことが多いです。
そんなこんなで、アドベントカレンダーに参加表明はしたものの、書くネタがねぇ...... とちょっと焦ったのですが、何とかネタをひねり出すために、JSON データをテンプレートで作るときに (作ったことがある人ならわかると思いますが)、ちょっとだけ面倒くさい、いわゆる「末尾のカンマ」の処理をほんのちょっとだけ楽にする Movable Type プラグインを書いてみたのでそれを紹介してみようと思います。
プラグイン自体は下記で公開しています。
プラグインの概要
ひと言でいうと、<$mt:JsonComma$> というファンクションタグを追加します。このタグは任意の場所にカンマ(,)を出力します。
例えば、<mt:Entries> や <mt:Categories>、<mt:Pages> などのブロックタグで JSON を作るとき、下記のようにループの最後だけカンマが出力されないようにする必要があります。
<mt:Entries>
  {
    "title": "<$mt:EntryTitle encode_json="1"$>",
    "author": "<$mt:EntryAuthor encode_json="1"$>"
  }<mt:Unless name="__last__">,</mt:Unless>
</mt:Entries>
<$mt:JsonComma$> ファンクションタグを使用することで下記のように書けます。
<mt:Entries>
  {
    "title": "<$mt:EntryTitle encode_json="1"$>",
    "author": "<$mt:EntryAuthor encode_json="1"$>"
  }<$mt:JsonComma$>
</mt:Entries>
また、<mt:TopLevelCategories> (<mt:SubCategories top="1"> も同じ)に関しては、__last__ などの予約変数が取得できない(なんで?)ため、下記のように <mt:SubCatIsLast> タグなどを使う必要があります。
<mt:TopLevelCategories>
<mt:SetVar name="comma" value=",">
<mt:SubCatIsLast><mt:SetVar name="comma" value=""></mt:SubCatIsLast>
  {
    "label": "<$mt:CategoryLabel encode_json="1"$>",
    "path": "<$mt:CategoryBasename encode_json="1"$>",
    "link": "<$mt:CategoryArchiveLink encode_json="1"$>"
  }<$mt:Var name="comma"$>
</mt:TopLevelCategories>
<$mt:JsonComma$> ファンクションタグを使用することで下記のようにシンプルに書けます。
<mt:TopLevelCategories>
  {
    "label": "<$mt:CategoryLabel encode_json="1"$>",
    "path": "<$mt:CategoryBasename encode_json="1"$>",
    "link": "<$mt:CategoryArchiveLink encode_json="1"$>"
  }<$mt:JsonComma$>
</mt:TopLevelCategories>
下記のように、<mt:TopLevelCategories> (<mt:SubCategories top="1"> も同じ)内で、<mt:Entries> をネストして使用した場合でも <$mt:JsonComma$> ファンクションタグが動作するようにはしています。
<mt:SubCategories top="1">
  {
    "label": "<$mt:CategoryLabel encode_json="1"$>",
    "path": "<$mt:CategoryBasename encode_json="1"$>",
    "link": "<$mt:CategoryArchiveLink encode_json="1"$>",
    "entry": [
    <mt:Entries lastn="0">
      {
        "title": "<$mt:EntryTitle encode_json="1"$>",
        "author": "<$mt:EntryAuthor encode_json="1"$>"
      }<$mt:JsonComma$>
    </mt:Entries>
    ]
  }<$mt:JsonComma$>
</mt:SubCategories>
なんか、ほんのちょっとだけ楽になったと思います。あくまでほんのちょっとだけです。
既知の問題点
これはひとつのファンクションタグで処理しようとしていることや、Movable Type におけるループの処理の関係だと思うんですが、例えば、<mt:Entries> と <mt:EntryCategories> を下記のようにネストしているときに、ネストされた <mt:EntryCategories> 内で <$mt:JsonComma$> を使うとうまく行かない(<mt:Entries> の最後のループ内は、<mt:EntryCategories> 内にカンマが全く出力されない)です。
原因はわかってるんですが、直すの面倒くさいのと、下記の例の様に glue="," を使用すれば解決するので、もしうまく行かなかった場合は参考にしてください。
<mt:Entries>
  {
    "title": "<$mt:EntryTitle encode_json="1"$>",
    "author": "<$mt:EntryAuthor encode_json="1"$>",
    "category": [
      <mt:EntryCategories glue=",">
      {
        "label": "<$mt:CategoryLabel encode_json="1"$>",
        "path": "<$mt:CategoryBasename encode_json="1"$>",
        "link": "<$mt:CategoryArchiveLink encode_json="1"$>"
      }
      </mt:EntryCategories>
    ]
  }<$mt:JsonComma$>
</mt:Entries>
その他、すべてのブロックタグの入れ子の組み合わせで試していないので、うまくいったりいかなかったりする場合があると思います。そういう時は別の方法を考えてください。
全部のブロックタグの入れ子関係を調べて何とかしようかなと思ったんですが、さすがにこのプラグインにそこまでの労力をかけたくなかったのと、プログラム自体はなるべくシンプルにしたかったので今回はパス。
個人的にそこまで Movable Type プラグインの仕組みに詳しくないというのもありますので、もしもっとよい実装があるよという方は教えてください。
関連エントリー
過去に Movable Type Advent Calendar で書いた記事。
- Astro と Movable Type Data API でページネーション (ページ分割) を実装する
 - Astro.js と Movable Type Data API を使用して Jamstack な Blog を作ってみる
 - MovableType.net サイトサーチを Google カスタム検索エンジン使用中サイトに 5 分で導入する
 - Movable Type の XMLRPC API における OS コマンドインジェクションの脆弱性 (CVE-2021-20837) を悪用した攻撃被害に関する対応メモ
 - Next.js と Movable Type Data API を使用して Jamstack な Blog を作ってみる