こんにちは、パレイド技術部です。
前回は画像のアップロードと退避の仕組みを整えました。今回は、WordPress でよく使われる「ブログカード」の Markdown 対応について書きます。
本記事はローカル LLM による自動執筆パイプラインで生成されました。現段階ではクラウド AI(Claude 等)の補助や人間の編集が介在していますが、pareido.jp では最終的に AI が自律的にコンテンツを制作できる仕組みの構築を目指しています。
ブログカードとは
WordPress のブログカードは、URL を貼るだけでリンク先のタイトルやサムネイルをカード形式で表示する機能です。pareido.jp では Cocoon テーマのブログカードブロック(wp:cocoon-blocks/blogcard)を利用しています。
Gutenberg の内部表現は、こんな形をしています。
<!-- wp:embed {"url":"https://example.com/","type":"wp-embed"} -->
<figure class="wp-block-embed">
<div class="wp-block-embed__wrapper">
https://example.com/
</div></figure>
<!-- /wp:embed -->
これを Markdown ファイルにそのまま書くのは現実的ではありません。もっとシンプルな記法が欲しい。
Markdown 側の記法
色々悩みましたが、シンプルに次の形にしました。
[blogcard](https://pareido.jp/some-article/)
通常の Markdown リンクと同じ構文で、テキスト部分を blogcard とするだけです。特別な拡張構文を覚える必要がなく、Markdown のプレビューでもリンクとして表示されるので、執筆中の視認性も悪くありません。
URL が未確定の場合は、プレースホルダとして書いておけます。「なし」や「none」と書くと、アップロード時に自動で削除されます。
アップロード時の変換
Markdown → WordPress の変換では、以下の流れで処理しています。
- `(url)` を正規表現で検出
- URL を `(https://pareido.jp/slug)` のようなプレースホルダに置換
- Markdown → HTML 変換を実行(プレースホルダはそのまま通過)
- HTML → Gutenberg ブロック変換を実行
- 最後にプレースホルダを
<!-- wp:embed -->ブロックに復元
なぜプレースホルダを経由するかというと、Markdown の変換エンジンが を通常のリンク <a> タグに変換してしまうためです。先にプレースホルダに退避することで、変換エンジンの干渉を避けています。
# blogcard をプレースホルダに退避
def _extract(m):
url = m.group(1).strip()
if not url or url.lower() in ("なし", "none"):
return "" # 削除
key = f"WPEMBEDPH{len(placeholders):04d}"
placeholders[key] = make_embed_html(url)
return key
content = re.sub(r"\[blogcard\]\(([^)]+)\)", _extract, content)
連続する blogcard の落とし穴
細かいですが、実装当初、blogcard が2つ連続すると変換に失敗する問題がありました。
[blogcard](https://pareido.jp/some-article1/)
[blogcard](https://another.com/some-article2/)
Markdown 変換ライブラリ(Python-Markdown)がこの2行を1つの <p> タグにまとめてしまい、プレースホルダの復元パターンがマッチしなくなるのです。
<!-- 期待 -->
<p></p>
<p></p>
<!-- 実際 -->
<p>
</p>
対策として、復元処理を2段階にしました。まず「プレースホルダだけで構成された <p> ブロック」を検出して個別のブロックに展開し、それでも残ったプレースホルダはフォールバックで直接置換します。空行を挟むことで対処は可能ですが、トラブルを減らすよう実装しました。
ダウンロード時の変換
WordPress → Markdown の方向では、逆の変換を行います。
<!-- wp:embed --> ブロックから JSON 属性の url を抽出し、 に戻します。
# wp:embed ブロックの JSON から URL を抽出
embed_re = re.compile(
r'<!--\s*wp:embed\s+\{[^>]*?"url"\s*:\s*"([^"]+)"[^>]*?-->'
r'.*?'
r'<!--\s*/wp:embed\s*-->',
re.DOTALL
)
text = embed_re.sub(lambda m: f'[blogcard]({m.group(1)})', text)
ポイントは re.DOTALL フラグです。embed ブロックは複数行にまたがるため、これがないとマッチしません。
まとめ
ブログカードは WordPress の便利な機能ですが、Gutenberg の内部表現は冗長です。 というシンプルな記法を定義し、アップロード・ダウンロードの両方向で自動変換することで、Markdown ファイルの可読性を保ったまま WordPress の機能を活かせるようになりました。
次回は、Cocoon のハイライトやタブボックスなど、プラグイン固有ブロックの取り扱いについて書きます。



