Module: VivlioStarter::CLI::HeadingSegmenter

Defined in:
lib/vivlio_starter/cli/heading_segmenter.rb

Overview

見出しを折返し可能な語へ分割するモジュール

Constant Summary collapse

MAX_LENGTH =

見出しとして現実的な長さの上限(これを超えるものは解析しない)

200
GLUE_PATTERN =

行頭に単独で置きたくない語か。約物・閉じ括弧・1〜2 文字の仮名(助詞等)。

/\A[ぁ-んー、。,.・:;?!)]}」』〉》】〕”’]+\z/
OPEN_BRACKET_PATTERN =

行末に単独で置きたくない語(開き括弧)は次の語へ接着する

/\A[([{「『〈《【〔“‘]+\z/

Class Method Summary collapse

Class Method Details

.collect_surfaces(text) ⇒ Object

MeCab の表層形を並べる(読みは使わないので feature は見ない)。 MeCab は空白を落とすので、原文を走査して落ちた分を直前の語の末尾へ戻す (語頭へ付けると行頭に空白が来る)。連結して原文に戻ることを呼び出し側が検証する。



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/vivlio_starter/cli/heading_segmenter.rb', line 56

def collect_surfaces(text)
  surfaces = []
  pos = 0
  inferrer.send(:mecab).parse(text) do |node|
    next if node.is_eos? || node.surface.empty?

    index = text.index(node.surface, pos) or next
    skipped = text[pos...index]
    surfaces[-1] = surfaces[-1] + skipped if !skipped.empty? && !surfaces.empty?
    surfaces << (surfaces.empty? ? skipped + node.surface : node.surface)
    pos = index + node.surface.length
  end
  surfaces[-1] = surfaces[-1] + text[pos..] if pos < text.length && !surfaces.empty?
  surfaces
end

.glue_open_brackets(words) ⇒ Object



95
96
97
98
99
100
101
102
103
# File 'lib/vivlio_starter/cli/heading_segmenter.rb', line 95

def glue_open_brackets(words)
  words.reverse.each_with_object([]) do |word, acc|
    if acc.empty? || !word.match?(OPEN_BRACKET_PATTERN)
      acc << word.dup
    else
      acc[-1] = word + acc[-1]
    end
  end.reverse
end

.glue_to_previous?(word) ⇒ Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/vivlio_starter/cli/heading_segmenter.rb', line 91

def glue_to_previous?(word)
  word.length <= 2 && word.match?(GLUE_PATTERN)
end

.inferrerObject

YomiInferrer の MeCab インスタンス・可用性判定を共有する (MeCab の初期化と不在時の案内を 2 か所に持たない)。



107
108
109
110
# File 'lib/vivlio_starter/cli/heading_segmenter.rb', line 107

def inferrer
  require_relative 'index/yomi_inferrer'
  @inferrer ||= IndexCommands::YomiInferrer.new
end

.merge_unbreakable(words) ⇒ Object

直前の語と離してはいけない語を接着する。 助詞・助動詞・記号だけの語や 1 文字の送り仮名を単独で行頭へ送ると、 かえって不自然に見える(「章の/管理」より「章の管理」)。



75
76
77
78
79
80
81
82
83
84
# File 'lib/vivlio_starter/cli/heading_segmenter.rb', line 75

def merge_unbreakable(words)
  merged = words.each_with_object([]) do |word, acc|
    if acc.empty? || !glue_to_previous?(word)
      acc << word.dup
    else
      acc[-1] << word
    end
  end
  glue_open_brackets(merged)
end

.segment(text) ⇒ Array<String>

見出しを語の配列へ分割する。 分割できない(MeCab 不在・空文字・長すぎる)場合は [text] を返す。

Parameters:

  • text (String)

Returns:

  • (Array<String>)

    連結すると元の文字列に戻る語の配列



41
42
43
44
45
46
47
48
49
50
51
# File 'lib/vivlio_starter/cli/heading_segmenter.rb', line 41

def segment(text)
  source = text.to_s
  return [source] if source.empty? || source.length > MAX_LENGTH
  return [source] unless inferrer.available?

  words = collect_surfaces(source)
  words.join == source ? merge_unbreakable(words) : [source]
rescue StandardError => e
  Common.log_debug("見出しの語分割に失敗(そのまま扱います): #{e.message}")
  [source]
end