Module: Arrolio::GenericAdapter::HeadingExtraction

Included in:
Arrolio::GenericAdapter
Defined in:
lib/arrolio/generic_adapter/heading_extraction.rb

Overview

Extracted conversion seam (TODO 56): one module per concern, included into the adapter. The class keeps dispatch and glue.

Instance Method Summary collapse

Instance Method Details

#clause_level(elem, number: nil) ⇒ Object

The level derives from the autonumber's dotted depth ("5.1.1" → 3, "A.1" → 2) when present — the semantic truth — falling back to the fmt-title depth attribute. Titles without either stay level 1. Relying on the attribute alone mis-leveled most sections (3.x, 5.1.x) because their fmt-title carries no depth.



13
14
15
16
17
18
19
20
21
22
# File 'lib/arrolio/generic_adapter/heading_extraction.rb', line 13

def clause_level(elem, number: nil)
  dotted = number.to_s.split('.').length
  return dotted if dotted > 1 && number.to_s.match?(/\A[\dA-Z]/)

  heading_name = selector('heading')
  depth_attr = selector('heading_depth_attribute')
  fmt_title = elem.elements.first { |e| e.name == heading_name }
  depth = fmt_title&.attribute(depth_attr)&.value.to_i
  [depth, 1].max
end

#extract_from_heading(heading_elem) ⇒ Object



36
37
38
39
40
41
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
72
73
74
# File 'lib/arrolio/generic_adapter/heading_extraction.rb', line 36

def extract_from_heading(heading_elem)
  parts = []
  number_parts = []
  title_parts = []
  non_number_parts = []
  autonum_class = selector('autonum_class')
  autonum_delim_class = selector('autonum_delim_class') || 'fmt-autonum-delim'
  caption_label_class = selector('caption_label_class') || 'fmt-caption-label'

  walk_heading_text(heading_elem) do |text, parent|
    parts << text
    element_attr = parent&.attribute('element')&.value
    parent_class = parent&.attribute('class')&.value
    if element_attr == 'autonum' ||
       parent_class == autonum_class ||
       parent_class == autonum_delim_class ||
       parent_class == caption_label_class
      number_parts << text
    elsif element_attr == 'title'
      title_parts << text
      non_number_parts << text
    else
      non_number_parts << text
    end
  end

  number = number_parts.map(&:strip).reject(&:empty?).join
  number = nil if number.empty?

  title = if title_parts.any?
            title_parts.join.strip
          elsif parts.include?("\t")
            parts.join.split("\t", 2).last&.strip
          else
            non_number_parts.join.strip
          end
  title = nil if title.nil? || title.empty?
  [title, number]
end

#extract_heading(clause) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
# File 'lib/arrolio/generic_adapter/heading_extraction.rb', line 24

def extract_heading(clause)
  heading_config = @rules['heading'] || {}
  source = heading_config['source'] || selector('heading')
  heading_elem = find_first(clause, source)
  if heading_elem
    extract_from_heading(heading_elem)
  else
    title = find_first(clause, selector('fallback_title'))
    [title ? text_of(title) : nil, nil]
  end
end

#walk_heading_text(element, &block) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/arrolio/generic_adapter/heading_extraction.rb', line 76

def walk_heading_text(element, &block)
  element.children.each do |child|
    next if child.is_a?(REXML::Element) &&
            (XREF_ELEMENTS.include?(child.name) ||
             child.attribute('element')&.value == 'xref')

    case child
    when REXML::Text
      yield(child.value, child.parent)
    when REXML::Element
      walk_heading_text(child, &block)
    end
  end
end