WordPressブログの修正ツールを作っていて、AIが生成した修正候補を確認するモーダルのUIを改善した。もともと元記事とAI修正候補を2カラムの <pre> で並べていたが、WordPressのraw HTMLには <!-- wp:heading --> といったブロックコメントやHTMLタグが混在しており、本文の差異がほとんど見えない状態だった。
この記事では、その改善のために実装した3つのコンポーネントを整理する。
- Python正規表現によるWordPressブロックHTMLのプレーンテキスト化
difflib.ndiffを使ったbefore/after行単位diff計算- Jinja2 + CSSによるgit diffライクなunified diff表示
対象環境
- Python 3.x(標準ライブラリ
difflib使用) - FastAPI(Webアプリフレームワーク)
- Jinja2(テンプレートエンジン)
- WordPress REST API(
content.raw取得元) - 実装対象ファイル:
webapp.py/post_edit.html/app.css
課題背景:修正確認モーダルのHTMLタグ混在問題
WordPress REST APIから取得した content.raw は、ブロックエディタ形式のHTMLで返ってくる。
<!-- wp:heading -->
<h2 class="wp-block-heading">見出しテキスト</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>本文テキスト</p>
<!-- /wp:paragraph -->
この形式のまま2カラム表示すると、比較したいのは「本文の言葉」なのに、視線はHTMLタグとブロックコメントに吸われ続ける。変更点を確認するUIがノイズだらけという本末転倒な状態だった。
解決方針:HTMLストリップ+difflibによるdiff生成
問題を2段階で解決した。
- HTMLストリップ:ブロックコメントとHTMLタグを正規表現で除去し、プレーンテキスト化する
- Unified diff表示:プレーンテキスト化した現在内容と修正候補をdifflibで比較し、git diffライクな1カラム表示に切り替える
ここがポイント: 並列比較からunified diff表示に切り替えることで、変更行だけにフォーカスできる。HTMLノイズを先に除いてからdiffを取ることで、本文の実際の変更点が浮き上がる。
実装①:_strip_wp_html() でWordPressブロックHTMLをプレーンテキスト化
webapp.py に追加した関数。正規表現で2ステップ処理する。
import re
def _strip_wp_html(html: str) -> str:
# WordPressブロックコメントを除去
text = re.sub(r'<!--\s*/?\s*wp:[^>]*-->', '', html)
# 残りのHTMLタグを除去
text = re.sub(r'<[^>]+>', '', text)
# 空行を正規化
lines = [line.strip() for line in text.splitlines()]
lines = [line for line in lines if line]
return '\n'.join(lines)
注意点:
- WordPressのブロックコメント形式(
<!-- wp:〜 -->)は将来変わる可能性があり、このパターンの保守が必要になることがある <[^>]+>でHTMLタグを除去するが、<script>や<style>内のテキストが残る構造の場合は別途処理が必要
実装②:_compute_diff_lines() でndiffによるdiff計算
difflib.ndiff() は行リストを受け取り、各行に以下のプレフィックスを付けて返す。
-:削除行+:追加行- :同一行
?:文字レベルの差異ヒント(表示には不要)
import difflib
def _compute_diff_lines(before: str, after: str) -> list[dict]:
before_lines = before.splitlines()
after_lines = after.splitlines()
diff = difflib.ndiff(before_lines, after_lines)
result = []
for line in diff:
if line.startswith('- '):
result.append({'type': 'remove', 'text': line[2:]})
elif line.startswith('+ '):
result.append({'type': 'add', 'text': line[2:]})
elif line.startswith(' '):
result.append({'type': 'same', 'text': line[2:]})
# '? ' プレフィックスのヒント行は無視
return result
? 行は文字単位ハイライトに活用もできるが、行単位の色分けで十分だったため除外している。
FastAPIのエンドポイント側では、HTMLストリップ後のテキストをdiff関数に渡す。
current_text = _strip_wp_html(post_content_raw)
diff_lines = _compute_diff_lines(current_text, ai_suggestion_text)
AI生成の修正候補はMarkdown形式で返ることが多い。HTMLストリップ後のプレーンテキストとMarkdownは構造が近いため、そのまま比較しやすくなるという実用上のメリットがある(すべてのケースで保証されるわけではない)。
テンプレート側:Jinja2でdiff_linesをレンダリング
post_edit.html のモーダル内で diff_lines を受け取り、type に応じたCSSクラスを付与して表示する。
<div class="diff-container">
{% for line in diff_lines %}
<div class="diff-line diff-{{ line.type }}">
{% if line.type == 'remove' %}-{% elif line.type == 'add' %}+{% else %} {% endif %}
{{ line.text }}
</div>
{% endfor %}
</div>
type が remove / add / same の3種類なので、CSSクラスも diff-remove / diff-add / diff-same の3パターンで対応できる。フロントエンドフレームワーク不要でシンプルに実装できる点がJinja2アプローチのメリット。
CSS:git diffライクな色分けスタイリング
app.css に追加。削除行は赤系、追加行は緑系で色分けする。
.diff-container {
font-family: monospace;
font-size: 0.875rem;
line-height: 1.6;
overflow-x: auto;
}
.diff-line {
padding: 2px 8px;
white-space: pre-wrap;
word-break: break-all;
}
.diff-remove {
background-color: color-mix(in srgb, red 15%, white);
color: #c0392b;
}
.diff-add {
background-color: color-mix(in srgb, green 15%, white);
color: #27ae60;
}
.diff-same {
background-color: transparent;
color: inherit;
}
注意:color-mix(in srgb, ...) は比較的新しいCSS構文のため、対応ブラウザを確認する必要がある(要外部確認)。古い環境への対応が必要な場合は rgba() や固定カラーコードへのフォールバックを検討する。
/* フォールバック例 */
.diff-remove { background-color: #fde8e8; color: #c0392b; }
.diff-add { background-color: #e8f8e8; color: #27ae60; }
マージ時の手順
作業中の変更がある状態でmainブランチの変更を取り込む必要があったため、stash → fast-forward merge → stash pop の手順を使った。コンフリクトなしで解決できた。
git stash
git merge main # fast-forward
git stash pop
変更前・変更後のUI比較
| 項目 | 変更前 | 変更後 |
|---|---|---|
| レイアウト | 2カラム並列 <pre> 表示 | 1カラム unified diff |
| 表示内容 | HTMLタグ・ブロックコメント含むraw | プレーンテキスト化済み |
| 差異の見え方 | 目視で探す必要あり | 赤/緑で変更行が明示 |
| 実装ファイル数 | — | 3ファイル(webapp.py / post_edit.html / app.css) |
まとめ・応用範囲
今回の改善で解決したのは「HTMLタグが混在するコンテンツをそのまま比較しようとしていた」というミスマッチ。HTMLをストリップしてからdiffを取るという順序が核心で、実装自体はシンプルだった。
他に使えるシーン:
- CMSやリッチテキストエディタのraw出力を比較するあらゆる場面
- 設定ファイルや構造化テキストのbefore/after確認UI
- フロントエンドフレームワークなしで動く管理ツールへのdiff機能追加
残る課題は2点。WordPressのブロックコメントパターンが将来変わった場合の正規表現保守と、color-mix() のブラウザ互換性確認。どちらも今すぐ問題になるわけではないが、定期的に見直したいポイントになる。

コメント