Module: VivlioStarter::CLI::PostProcessCommands::ReplacementRules

Defined in:
lib/vivlio_starter/cli/post_process/replacement_rules.rb

Overview

旧 config/post_replace_list.yml の組み込み置換ルール。 適用エンジンは HtmlReplacer(保護モード・退避機構)を共用する。

各ルールの正規表現は旧 YAML の f 文字列と source が一致するよう %r...m で記述している(/m は旧実装の Regexp.new(str, MULTILINE) 相当)。 置換文字列の $1$9 はエンジン側 replace_with_captures が手動展開するため そのまま維持する(Ruby の \1 へ書き換えない)。

Defined Under Namespace

Classes: Rule

Constant Summary collapse

CONTAINER_RULES =
[
  # :::{.class} 記法(Pandoc 拡張風)を <div class="..."> に変換(開始)。
  # 複数クラス {.a .b .c} にも対応(先頭の . は任意、以降の . は後段ルールで除去)。
  Rule.new(%r{:{3,}\s*\{\.?([a-z0-9.\-_\s]+)\}}m, '<div class="$1">', :text_only),
  # 複数クラス指定で残った " ." を " " に整理(最大 4 クラスまで対応するため 3 回)。
  Rule.new(%r{(<div class="[^"]*?) \.}m, '$1 ', :tag_aware),
  Rule.new(%r{(<div class="[^"]*?) \.}m, '$1 ', :tag_aware),
  Rule.new(%r{(<div class="[^"]*?) \.}m, '$1 ', :tag_aware),
  # ::: ブロック終端を </div> に変換(終了)。
  Rule.new(%r{:{3,}}m, '</div>', :text_only)
].freeze
PAGEBREAK_RULES =

を改ページ用の
に。
[
  Rule.new(%r{<hr>}m, '<hr class="pagebreak">', :tag_aware)
].freeze
SPACING_MACRO_RULES =

余白・改ページのプラグマ系マクロ。単位付きを先に適用する (単位なしルールが先に走ると数値だけ拾って単位が残るため)。 単位なしの既定は @vspace が mm・@hspace が em(水平方向は文字数感覚が自然なため 意図的に非対称。at-directive-tier1-spec.md §1.5)。

[
  Rule.new(%r{@vspace:(-?[\d.]+(?:lh|rem|em|mm|cm|pt|px))}m, '<div style="margin-top:$1"></div>', :text_only),
  Rule.new(%r{@vspace:(-?[\d.]+)}m, '<div style="margin-top:$1mm"></div>', :text_only),
  Rule.new(%r{@hspace:(-?[\d.]+(?:lh|rem|em|mm|cm|pt|px))}m,
           '<span class="vs-hspace" style="margin-left:$1"></span>', :text_only),
  Rule.new(%r{@hspace:(-?[\d.]+)}m, '<span class="vs-hspace" style="margin-left:$1em"></span>', :text_only),
  # 引数付きを先に。裸形の否定先読み (?!:) は必須——これが無いと不正引数
  # (@pagebreak:left 等)の @pagebreak 部分だけが置換され ":left" が紙面に残る。
  # 不正形は丸ごと素通しし、前処理(MarkdownPreprocessor)の検知に委ねる。
  Rule.new(%r{@pagebreak:(recto|verso)\b}m, '<div class="vs-break-$1"></div>', :text_only),
  Rule.new(%r{@pagebreak\b(?!:)}m, '<div class="vs-break-page"></div>', :text_only)
].freeze
LIST_DECORATION_RULES =

リストの先頭記号を装飾クラスに(▶=青コメ / 囲み数字=赤コメ)。

[
  Rule.new(%r{<li[^>]*>▶}m, '<li class="aokome">', :tag_aware),
  Rule.new(%r{<li[^>]*>([❶-❿⓫-⓴]+)}m, '<li class="akakome"><span>$1</span>', :tag_aware)
].freeze
CODE_HEADING_RULES =

コード見出し(h6)を装飾クラス付きに(span は Nokogiri の自動補完で閉じる)。

[
  Rule.new(%r{<h6([^>]*)>}m, '<h6$1 class="codetitle"><span>', :tag_aware)
].freeze
KBD_RULES =

〘〙 を ... に。

[
  Rule.new(%r{}m, '<kbd>', :text_only),
  Rule.new(%r{}m, '</kbd>', :text_only)
].freeze
PARAGRAPH_CLEANUP_RULES =

p/div/svg/image/hr のねじれ修正と空段落除去(順序厳守・15 ルール)。 後段ルールは前段の結果に依存するため並びを変えないこと。

[
  # p が余分に div を囲っているのを除去(div 先頭を裸に)。
  Rule.new(%r{<p>(<div class="[^"]+">)</p>}m, '$1', :tag_aware),
  # p と div の並び修正。
  Rule.new(%r{<p></div></p>}m, '</div>', :tag_aware),
  Rule.new(%r{<p></div>}m, '</div>', :tag_aware),
  Rule.new(%r{</div></p>}m, '</div>', :tag_aware),
  # div の直後に余分な </p> が来た場合の修正。
  Rule.new(%r{(<div class="[^"]+">)</p>}m, '$1', :tag_aware),
  # <p>(<div ...>) の余分な p を除去(前後の空白・改行を許容)。
  Rule.new(%r{<p>\s*(<div class="[^"]+">)}m, '$1', :tag_aware),
  # hr の前後の余分な p / </p> を除去。
  Rule.new(%r{(<hr class="[^"]+">)</p>}m, '$1', :tag_aware),
  Rule.new(%r{<p>(<hr class="[^"]+">)}m, '$1', :tag_aware),
  # <svg> / <image> を p が囲っているだけの場合、p を除去。
  Rule.new(%r{<p>(<svg [^>]+>)</p>}m, '$1', :tag_aware),
  Rule.new(%r{<p>(<image [^>]+>)}m, '$1', :tag_aware),
  Rule.new(%r{(<image [^>]+>)</p>}m, '$1', :tag_aware),
  Rule.new(%r{<p>(</svg>)}m, '$1', :tag_aware),
  Rule.new(%r{(</svg>)</p>}m, '$1', :tag_aware),
  # 空段落を除去(素 / 空白 / nbsp・ゼロ幅・双方向制御記号のみ)。
  Rule.new(%r{<p></p>}m, '', :tag_aware),
  Rule.new(%r{<p>\s*</p>}m, '', :tag_aware),
  Rule.new(
    %r{<p>(?:\s|\u{00A0}|\u{200B}|\u{200C}|\u{200D}|\u{2060}|\u{FEFF}|\u{180E}|\u{200E}|\u{200F}|&nbsp;|&#160;|&#xA0;)*</p>}m,
    '', :tag_aware
  )
].freeze
SPACING_CLASS_RULES =

段落末の aki / aki2 をクラス化。

[
  Rule.new(%r{<p>((?:(?!</p>).)*?)\{\.aki\}\s*</p>}m, '<p class="aki">$1</p>', :tag_aware),
  Rule.new(%r{<p>((?:(?!</p>).)*?)\{\.aki2\}\s*</p>}m, '<p class="aki2">$1</p>', :tag_aware)
].freeze
ALL =

旧 yml の記載順そのまま(順序変更禁止: 後段ルールは前段の結果に依存する)。

(CONTAINER_RULES + PAGEBREAK_RULES + SPACING_MACRO_RULES +
LIST_DECORATION_RULES + CODE_HEADING_RULES +
KBD_RULES + PARAGRAPH_CLEANUP_RULES + SPACING_CLASS_RULES).freeze

Class Method Summary collapse

Class Method Details

.apply_builtin!(html_file) ⇒ Hash

組み込みルール一式を適用する(旧 process_html_file(file, yaml_rules) 相当)。

Returns:

  • (Hash)

    { changed: Boolean, replacements: Integer }



140
# File 'lib/vivlio_starter/cli/post_process/replacement_rules.rb', line 140

def apply_builtin!(html_file) = HtmlReplacer.process_html_file(html_file, ALL + value_macro_rules)

.sanitize_value(value) ⇒ Object

置換エンジンは $1〜$9 を手動展開するため、値中の $ を無害化しつつ HTML エスケープする。



136
# File 'lib/vivlio_starter/cli/post_process/replacement_rules.rb', line 136

def sanitize_value(value) = CGI.escapeHTML(value.to_s).gsub('$', '&#36;')

.value_macro_rules(cfg = Common.configured? ? Common::CONFIG : nil) ⇒ Object

ビルド時定数マクロ(at-directive-tier1-spec.md §2.3)。 値が CONFIG・ビルド時刻に依存するため frozen 定数 ALL には入れず、適用時に組み立てる (定数にすると reload_configuration! 後に stale な値を差し込んでしまう)。 \b により @titlepage のような続き文字には反応しない。

Parameters:

  • cfg (Object, nil) (defaults to: Common.configured? ? Common::CONFIG : nil)

    設定オブジェクト(省略時は Common::CONFIG。テストは DI で差し替える)



124
125
126
127
128
129
130
131
132
133
# File 'lib/vivlio_starter/cli/post_process/replacement_rules.rb', line 124

def value_macro_rules(cfg = Common.configured? ? Common::CONFIG : nil)
  # 日付だけは CONFIG に依らないので、プロジェクト外(CONFIG 未ロード)でも展開する。
  rules = [Rule.new(%r{@today\b}m, Time.now.strftime('%Y年%-m月%-d日'), :text_only)]
  return rules unless cfg

  rules.unshift(
    Rule.new(%r{@version\b}m, sanitize_value(cfg.project.version), :text_only),
    Rule.new(%r{@title\b}m,   sanitize_value(cfg.book.main_title), :text_only)
  )
end