Action Text の `to_markdown` における Trix HTML 変換精度の向上

rails/rails

Action Text の ActionText::MarkdownConversion が Trix 生成 HTML をより正確にMarkdown変換できるよう、3つの改善が加えられました。<del> タグのサポート追加、<div> ブロックの透過処理、そしてプリティプリント用ホワイトスペースの除去です。

背景

Trix エディタが出力する HTML には、汎用的な HTML パーサーが想定しない3つのクセがあり、to_markdown の変換結果に乱れが生じていました。

第一に、Trix は打ち消し線に <s> ではなく <del> を使います。これは既存の visit_s ハンドラでは処理されなかったため、打ち消し線が変換結果に反映されませんでした。第二に、Trix はデフォルトのブロック要素として <p> ではなく <div> を使用し、改行を <br> タグで表現します(Trix ソースの piece_view.js / block_view.js の実装がこの規約に基づいています)。<div> に対するハンドラが存在しなかったため、ブロックコンテンツが意図したとおりに変換されませんでした。第三に、シリアライザやテンプレートがプリティプリント済みの HTML を生成する場合、テキストノード間のインデント用ホワイトスペースが Markdown 出力に漏れ出す問題がありました。

技術的な変更

この PR では actiontext/lib/action_text/markdown_conversion.rb に対して3か所の変更が加えられています。

<del> のサポート追加

<del>INLINE_ELEMENTS リストに追加したうえで、visit_s への alias として visit_del を定義しています。

# 変更前
INLINE_ELEMENTS = %w[
  action-text-markdown
  a abbr b bdi bdo cite code data dfn em i kbd mark q
  rp rt ruby s samp small span strong sub sup time u var
].freeze

# 変更後
INLINE_ELEMENTS = %w[
  action-text-markdown
  a abbr b bdi bdo cite code data del dfn em i kbd mark q
  rp rt ruby s samp small span strong sub sup time u var
].freeze

alias_method :visit_del, :visit_s

これにより <del>hello</del>~~hello~~ に変換されます。

visit_div ハンドラの追加

Trix が <div> をブロック要素として使い、改行を <br> で表現する規約に合わせ、visit_div<p> と異なり末尾に \n\n を付加しません。

# Trix uses <div> as its default block element and represents newlines as <br> tags
# (see piece_view.js and block_view.js in the Trix source). Unlike <p>, we don't append
# paragraph-separating newlines here because the <br> children already provide spacing.
def visit_div(_node, child_values)
  join_children(child_values)
end

既存の visit_p"#{join_children(child_values)}\n\n" を返すのに対し、visit_div<br> による改行をそのまま活かすため余分な改行を加えません。

③ プリティプリント用ホワイトスペースの除去

2つの正規表現定数を追加し、テキストノードの前後に付くインデント用ホワイトスペース(改行+空白の組み合わせ)を除去する strip_pretty_print_indentation を導入しています。

LEADING_PRETTY_PRINT_WHITESPACE  = /\A\s*\n\s*/
TRAILING_PRETTY_PRINT_WHITESPACE = /\s*\n\s*\z/

テキストノードの処理は escape_markdown_text(node.content) から escape_markdown_text(strip_pretty_print_indentation(node)) に変更されました。ただし、インラインの兄弟要素に隣接するテキストノード境界では、単語の区切りを保つために空白は完全除去ではなく1スペースに縮退します。これにより **before** ~~middle~~ *after* のような変換が正しく行われます。

設計判断

visit_divvisit_p と別実装になっている点 が設計上の核心です。両者は「ブロック要素のコンテンツをMarkdownに変換する」という目的を共有しますが、<p> はそれ自身が段落の区切りを意味するのに対し、Trix の <div> + <br> の組み合わせでは <br> が改行の責務を担います。末尾改行の付加責務を <div> から切り離すことで、Trix の規約を既存の <br> 変換ロジックを変えずに収容しています。

またプリティプリントホワイトスペースの除去は、インライン要素境界での完全除去ではなく「1スペースへの縮退」という方針を採用しています。これは Markdown トークンの融合(**bold**~~struck~~ のように空白なしで並んでしまう問題)を防ぎつつ、to_markdown の出力が入力 HTML のインデントスタイルに依存しないようにするための判断です。

まとめ

本PRは Trix の3つの出力特性(<del> の使用・<div> ベースのブロック・プリティプリントHTML)に対して、それぞれ最小限のハンドラと正規表現で対処した変更です。変換ロジックの核心部には手を加えず、Trix 固有の規約を既存フレームワークの自然な拡張として取り込んだ設計は、将来的に他のエディタ固有の HTML 規約にも同様のアプローチが取れることを示しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「総論→各論→結論」の構成が記事全体と各セクションに適用されており、非常に分かりやすいです。リード文、背景、技術詳細、設計判断、まとめの必須・任意要素が全て含まれています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```言語:ファイルパス)とGitHubのPRリンク記法([#番号](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Action TextやTrixの内部実装に言及しており、専門知識を持つエンジニアという対象読者に完全に適合しています。冗長な説明がなく、的確です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論から始まり、各段落の冒頭にトピックセンテンスが置かれているため、非常に読みやすいです。1段落1トピックの原則も守られています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されている3つのコードブロックは、提供されたDiffの内容と完全に一致しています。ファイル名も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「ハンドラ」「シリアライザ」「テキストノード」などの技術用語が文脈に応じて正確に使われています。PR内の用語とも一致しています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

TrixのHTML出力の特性(<del>の使用、<div>と<br>の規約)に関する説明は、PRのDescriptionと一致しており、技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内の全ての主張は、PRのタイトル、Description、Diffの内容によって裏付けられています。「設計判断」セクションもPRのコメントやコードの意図を正確に汲み取っており、ハルシネーションは見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#56896)が正確に記載され、正しいURLにリンクされています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRのタイトル(Improve Trix support for Action Text `to_markdown` conversion)を正確に和訳・要約しており、内容と完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PRに記載のないバージョン情報(例: 8.2.pre)やサポート状況などの外部知識を持ち込んでおらず、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「既に」「将来」といった時間表現の歪曲はなく、PRで行われた変更内容を事実として正確に記述しています。