Module: VivlioStarter::CLI::PostProcessCommands::HtmlReplacer

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

Overview

組み込み置換ルール適用エンジン

Constant Summary collapse

PRE_PLACEHOLDER_PREFIX =

退避プレースホルダのマーカー。Unicode 制御文字 (U+0000) を両端に置くので 原稿内やルールパターンに出現することはない。

"\u0000__VS_PRE__"
CODE_PLACEHOLDER_PREFIX =
"\u0000__VS_CODE__"
TAG_PLACEHOLDER_PREFIX =
"\u0000__VS_TAG__"
PLACEHOLDER_SUFFIX =
"__\u0000"

Class Method Summary collapse

Class Method Details

.apply_rule(content, rule) ⇒ Object

単一のルールを適用する。rule.mode で対象領域を絞り込んでから gsub を実行する。

モード:

  • :text_only … HTML 構造(

    / の本体、および全てのタグ定義
    <...>)を退避し、テキストノード部分だけに適用する。
    これにより @vspace などのマクロが data-heading="..." の
    ような属性値の中で置換されて HTML が壊れる事故を防ぐ。

  • :tag_aware … <p> <li ...> <span ...> など HTML 構造を対象にする ルール。

    / 内にはそもそもこれらのリテラルタグが
    存在しない(実体参照化される)ため、
     ブロックのみ退避
    して全体に適用する。



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/vivlio_starter/cli/post_process/html_replacer.rb', line 70

def apply_rule(content, rule)
  case rule.mode
  when :text_only
    with_text_scope_protected(content) do |stashed|
      replace_with_captures(stashed, rule.pattern, rule.replacement)
    end
  else # :tag_aware
    # <pre> ブロック内のテキストは置換対象外とする
    # コードブロック内の ::: 等が <div> に変換されるのを防ぐ
    pre_blocks = []
    protected = content.gsub(%r{<pre\b[^>]*>.*?</pre>}m) do |block|
      pre_blocks << block
      "#{PRE_PLACEHOLDER_PREFIX}TA#{pre_blocks.size - 1}#{PLACEHOLDER_SUFFIX}"
    end

    result, applied = replace_with_captures(protected, rule.pattern, rule.replacement)

    result = result.gsub(/#{Regexp.escape(PRE_PLACEHOLDER_PREFIX)}TA(\d+)#{Regexp.escape(PLACEHOLDER_SUFFIX)}/) do
      pre_blocks[Regexp.last_match(1).to_i]
    end
    [result, applied]
  end
end

.process_html_file(html_file, replace_rules) ⇒ Object

ReplacementRules::Rule の配列を適用して HTML ファイルを更新



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/vivlio_starter/cli/post_process/html_replacer.rb', line 37

def process_html_file(html_file, replace_rules)
  return { changed: false, replacements: 0 } unless replace_rules&.any?

  content = File.read(html_file, encoding: 'utf-8')
  replacements = 0

  replace_rules.each do |rule|
    content, applied = apply_rule(content, rule)
    replacements += applied
  end

  if replacements.positive?
    File.write(html_file, content, encoding: 'utf-8')
    { changed: true, replacements: replacements }
  else
    { changed: false, replacements: 0 }
  end
rescue StandardError => e
  Common.log_error("置換処理に失敗: #{html_file} - #{e.message}")
  { changed: false, replacements: 0 }
end

.replace_with_captures(content, regex, replacement_str) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
# File 'lib/vivlio_starter/cli/post_process/html_replacer.rb', line 134

def replace_with_captures(content, regex, replacement_str)
  applied = 0
  new_content = content.gsub(regex) do
    match_data = ::Regexp.last_match
    result = replacement_str.dup
    (1..9).each { |i| result.gsub!("$#{i}", match_data[i].to_s) }
    applied += 1
    result
  end
  [new_content, applied]
end

.with_text_scope_protected(content) ⇒ Object

テキスト専用ルール向けに、HTML 構造を全て退避したビューをブロックへ渡す。 以下の順で退避する(後続のマッチが先のプレースホルダを巻き込まないよう 大きい構造から順に退避):

1. <pre>...</pre>             (フェンス付きコードブロック全体)
2. <code>...</code>           (インラインコード/pre 内に残存するものも含む)
3. <...>                      (開始・終了タグ、自己閉じタグ、コメント)

終了後は逆順で復元する。



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/vivlio_starter/cli/post_process/html_replacer.rb', line 101

def with_text_scope_protected(content)
  pre_blocks  = []
  code_blocks = []
  tags        = []

  stashed = content.gsub(%r{<pre\b[^>]*>.*?</pre>}m) do |block|
    pre_blocks << block
    "#{PRE_PLACEHOLDER_PREFIX}#{pre_blocks.size - 1}#{PLACEHOLDER_SUFFIX}"
  end
  stashed = stashed.gsub(%r{<code\b[^>]*>.*?</code>}m) do |block|
    code_blocks << block
    "#{CODE_PLACEHOLDER_PREFIX}#{code_blocks.size - 1}#{PLACEHOLDER_SUFFIX}"
  end
  stashed = stashed.gsub(/<[^>]*>/m) do |tag|
    tags << tag
    "#{TAG_PLACEHOLDER_PREFIX}#{tags.size - 1}#{PLACEHOLDER_SUFFIX}"
  end

  result, applied = yield(stashed)

  result = result.gsub(/#{Regexp.escape(TAG_PLACEHOLDER_PREFIX)}(\d+)#{Regexp.escape(PLACEHOLDER_SUFFIX)}/) do
    tags[Regexp.last_match(1).to_i]
  end
  result = result.gsub(/#{Regexp.escape(CODE_PLACEHOLDER_PREFIX)}(\d+)#{Regexp.escape(PLACEHOLDER_SUFFIX)}/) do
    code_blocks[Regexp.last_match(1).to_i]
  end
  result = result.gsub(/#{Regexp.escape(PRE_PLACEHOLDER_PREFIX)}(\d+)#{Regexp.escape(PLACEHOLDER_SUFFIX)}/) do
    pre_blocks[Regexp.last_match(1).to_i]
  end

  [result, applied]
end