Class: VivlioStarter::CLI::IndexCommands::IndexMatchScanner::LineMask

Inherits:
Object
  • Object
show all
Defined in:
lib/vivlio_starter/cli/index/index_match_scanner.rb

Overview

用語を当ててはいけない領域(既にタグ付けされた要素・HTML タグ・振り仮名・ インラインコード)を退避してから用語を順に当て、最後にまとめて戻す。

退避を用語ごとにやり直さないのが要点。従来は 1 行あたり用語数(153 語)だけ 4 本の保護 gsub と Regexp 生成を繰り返しており、これがスキャン時間の大半を 占めていた(地の文 7,421 行 × 153 語 = 113 万反復)。

生成したタグは即座に退避するため、後続の用語が先行タグの中身や属性 (class="index-term" 等)へ食い込むことはない——従来の「用語ごとに保護し直す」 方式と遮蔽の効果は同じで、結果は 1 バイトも変わらない。

Constant Summary collapse

SENTINEL =

退避トークンは NUL 区切り。原稿に現れず、用語パターン(/\bTERM\b/)が 退避内容へ食い込む余地もない(従来の [[HTML_TOKEN_0]] 形式は文字列 "HTML" を含み、直後の _ が単語文字であるおかげで偶然無事だった)。

"\u0000VSIDX"

Instance Method Summary collapse

Constructor Details

#initialize(text) ⇒ LineMask

Returns a new instance of LineMask.



434
435
436
437
# File 'lib/vivlio_starter/cli/index/index_match_scanner.rb', line 434

def initialize(text)
  @text = text
  @stash = []
end

Instance Method Details

#protect!(pattern) ⇒ Object

パターンに一致する箇所を退避する



440
441
442
# File 'lib/vivlio_starter/cli/index/index_match_scanner.rb', line 440

def protect!(pattern)
  @text = @text.gsub(pattern) { |match| stash(match) }
end

#restoreObject

退避を戻して完成した行を返す。 入れ子(インラインコード vs build <章名> は HTML タグを内包する)を正しく 巻き戻すため挿入の逆順(LIFO)で展開する。順方向だと内側のトークンが 未展開のまま表面化して残留する。 置換文字列中の \\\1 を特殊扱いさせないため、必ずブロック形式で戻す。



469
470
471
472
# File 'lib/vivlio_starter/cli/index/index_match_scanner.rb', line 469

def restore
  @stash.reverse_each { |token, content| @text = @text.gsub(token) { content } }
  @text
end

#stash(content) ⇒ Object

文字列を退避してトークンを返す(生成したタグを後続の用語から隠すため)



456
457
458
459
460
# File 'lib/vivlio_starter/cli/index/index_match_scanner.rb', line 456

def stash(content)
  token = "#{SENTINEL}#{@stash.size}\u0000"
  @stash << [token, content]
  token
end

#substitute!(pattern) ⇒ Object

退避済みテキストに対して用語を置換する(ブロックはマッチ文字列を受け取る)



445
# File 'lib/vivlio_starter/cli/index/index_match_scanner.rb', line 445

def substitute!(pattern, &) = @text = @text.gsub(pattern, &)

#substitute_match!(pattern) ⇒ Object

捕捉グループが要る置換。ブロックへ MatchData を渡す。

substitute! では足りない——Regexp.last_match($~)はフレームローカルで、 gsub がここで立てる値は「呼び出し側で定義されたブロック」からは見えず常に nil になる。索引マークアップ [用語|読み] は読みの分離に捕捉グループが要る ので、MatchData を明示的に手渡してその制約を越える。



453
# File 'lib/vivlio_starter/cli/index/index_match_scanner.rb', line 453

def substitute_match!(pattern) = @text = @text.gsub(pattern) { yield ::Regexp.last_match }

#token?(text) ⇒ Boolean

Returns:

  • (Boolean)


462
# File 'lib/vivlio_starter/cli/index/index_match_scanner.rb', line 462

def token?(text) = text.include?(SENTINEL)