Module: Vivlio::Starter::CLI::PostProcessCommands::HtmlReplacer
- Defined in:
- lib/vivlio/starter/cli/post_process/html_replacer.rb
Overview
YAML 置換ルール適用モジュール
Constant Summary collapse
- CODE_AWARE_PATTERN_MARKER =
Prism ハイライト済みコードブロック内に適用したいルールの目印。パターン文字列に ‘class=“token` を含むルールのみ、<pre>…</pre> の内側も対象とする(C 言語の `/*← */` や HTML コメント強調など)。
'class="token'- 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
-
.apply_rule(content, regex, replacement_str, pattern_str) ⇒ Object
単一のルールを適用する。ルールのパターン文字列からモードを判定し、 対象領域を絞り込んでから gsub を実行する。.
-
.process_html_file(html_file, replace_rules) ⇒ Object
YAML置換ルールを適用してHTMLファイルを更新.
- .replace_with_captures(content, regex, replacement_str) ⇒ Object
- .rule_mode(pattern_str) ⇒ Object
-
.with_text_scope_protected(content) ⇒ Object
テキスト専用ルール向けに、HTML 構造を全て退避したビューをブロックへ渡す。 以下の順で退避する(後続のマッチが先のプレースホルダを巻き込まないよう 大きい構造から順に退避): 1.
Class Method Details
.apply_rule(content, regex, replacement_str, pattern_str) ⇒ Object
単一のルールを適用する。ルールのパターン文字列からモードを判定し、対象領域を絞り込んでから gsub を実行する。
モード:
-
:code_aware … パターンに ‘class=“token` を含む。<pre> 内も含めて全体に適用
(Prism ハイライト強調ルールを意図)。 -
:text_only … パターンに ‘<` を含まない。HTML 構造(<pre>/<code> の本体、
および全てのタグ定義 `<...>`)を退避し、テキストノード部分 だけに適用する。これにより `@clear` などのマクロが `data-heading="..."` のような属性値の中で置換されて HTML が壊れる事故を防ぐ。 -
:tag_aware … 上記以外。‘<p>` `<li …>` `<span …>` など HTML 構造を
対象にするルール。<pre>/<code> 内にはそもそもこれらの リテラルタグが存在しない(実体参照化される)ため、 追加の保護なしで全体に適用する。
88 89 90 91 92 93 94 95 96 97 98 99 100 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 |
# File 'lib/vivlio/starter/cli/post_process/html_replacer.rb', line 88 def apply_rule(content, regex, replacement_str, pattern_str) case rule_mode(pattern_str) when :code_aware # language-markdown 内のネストされたコードブロックは退避して # [!] 等の強調ルールが記法説明用コードに適用されるのを防ぐ md_blocks = [] protected = content.gsub(%r{<pre\b[^>]*\bclass="[^"]*\blanguage-markdown\b[^"]*"[^>]*>.*?</pre>}m) do |block| md_blocks << block "#{PRE_PLACEHOLDER_PREFIX}MD#{md_blocks.size - 1}#{PLACEHOLDER_SUFFIX}" end result, applied = replace_with_captures(protected, regex, replacement_str) result = result.gsub(/#{Regexp.escape(PRE_PLACEHOLDER_PREFIX)}MD(\d+)#{Regexp.escape(PLACEHOLDER_SUFFIX)}/) do md_blocks[Regexp.last_match(1).to_i] end [result, applied] when :text_only with_text_scope_protected(content) do |stashed| replace_with_captures(stashed, regex, replacement_str) 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, regex, replacement_str) 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
YAML置換ルールを適用してHTMLファイルを更新
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/vivlio/starter/cli/post_process/html_replacer.rb', line 42 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| pattern_str = rule['f'] replacement_str = rule['r'] next unless pattern_str && replacement_str begin regex = Regexp.new(pattern_str, Regexp::MULTILINE) content, applied = apply_rule(content, regex, replacement_str, pattern_str) replacements += applied rescue RegexpError => e Common.log_warn("不正な正規表現: #{pattern_str} - #{e.}") end 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.}") { changed: false, replacements: 0 } end |
.replace_with_captures(content, regex, replacement_str) ⇒ Object
174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/vivlio/starter/cli/post_process/html_replacer.rb', line 174 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 |
.rule_mode(pattern_str) ⇒ Object
127 128 129 130 131 132 |
# File 'lib/vivlio/starter/cli/post_process/html_replacer.rb', line 127 def rule_mode(pattern_str) return :code_aware if pattern_str.include?(CODE_AWARE_PATTERN_MARKER) return :text_only unless pattern_str.include?('<') :tag_aware end |
.with_text_scope_protected(content) ⇒ Object
テキスト専用ルール向けに、HTML 構造を全て退避したビューをブロックへ渡す。以下の順で退避する(後続のマッチが先のプレースホルダを巻き込まないよう大きい構造から順に退避):
1. <pre>...</pre> (フェンス付きコードブロック全体)
2. <code>...</code> (インラインコード/pre 内に残存するものも含む)
3. <...> (開始・終了タグ、自己閉じタグ、コメント)
終了後は逆順で復元する。
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/vivlio/starter/cli/post_process/html_replacer.rb', line 141 def with_text_scope_protected(content) pre_blocks = [] code_blocks = [] = [] 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| << tag "#{TAG_PLACEHOLDER_PREFIX}#{.size - 1}#{PLACEHOLDER_SUFFIX}" end result, applied = yield(stashed) result = result.gsub(/#{Regexp.escape(TAG_PLACEHOLDER_PREFIX)}(\d+)#{Regexp.escape(PLACEHOLDER_SUFFIX)}/) do [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 |