Class: Docbook::Mirror::DocbookToMirror

Inherits:
Object
  • Object
show all
Defined in:
lib/docbook/mirror/docbook_to_mirror.rb

Overview

Transforms DocBook element tree into DocbookMirror node tree.

This is the forward direction of the transformation: DocBook XML elements are converted into ProseMirror-style JSON node tree suitable for the Vue frontend's MirrorRenderer.

Uses a HandlerRegistry for dispatch, making it extensible without modifying this class. The default registry maps all built-in DocBook elements to their handlers.

Usage: doc = Docbook::Document.from_xml(xml_string) mirror_doc = DocbookToMirror.new.call(doc)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sort_glossary: false, registry: Docbook::Mirror.default_registry) ⇒ DocbookToMirror

Returns a new instance of DocbookToMirror.



24
25
26
27
28
29
30
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 24

def initialize(sort_glossary: false, registry: Docbook::Mirror.default_registry)
  @sort_glossary = sort_glossary
  @registry = registry
  @xml_id_map = {}
  @footnote_counter = 0
  @footnotes = []
end

Instance Attribute Details

#registryObject (readonly)

Returns the value of attribute registry.



22
23
24
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 22

def registry
  @registry
end

#sort_glossaryObject (readonly)

Returns the value of attribute sort_glossary.



22
23
24
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 22

def sort_glossary
  @sort_glossary
end

#xml_id_mapObject (readonly)

Returns the value of attribute xml_id_map.



22
23
24
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 22

def xml_id_map
  @xml_id_map
end

Instance Method Details

#build_xml_id_map(doc) ⇒ Object

=========================================

XML ID Map



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 214

def build_xml_id_map(doc)
  map = {}

  doc.each_mixed_content do |node|
    next if node.is_a?(String)

    id = node.element_id
    title = node.resolve_title
    title = Array(title).join if title
    map[id] = title if id && !id.empty? && title

    build_xml_id_map(node).each { |k, v| map[k] = v }
  end
  map
end

#call(docbook_doc) ⇒ Object

Entry point: Convert DocBook document to DocbookMirror



33
34
35
36
37
38
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 33

def call(docbook_doc)
  @xml_id_map = build_xml_id_map(docbook_doc)
  @footnote_counter = 0
  @footnotes = []
  document_node(docbook_doc)
end

#citetitle_node(element) ⇒ Object



140
141
142
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 140

def citetitle_node(element)
  Handlers::Inline.citetitle(element, context: self)
end

#document_node(docbook_doc) ⇒ Object

=========================================

Context API — methods available to handlers via context:



44
45
46
47
48
49
50
51
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 44

def document_node(docbook_doc)
  title = docbook_doc.resolve_title

  attrs = { title: title }.compact
  content = extract_content(docbook_doc)

  Docbook::Mirror::Node::Document.new(attrs: attrs, content: content)
end

#extract_co_markers(element) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 103

def extract_co_markers(element)
  markers = []
  counter = 0

  element.each_mixed_content do |node|
    next if node.is_a?(String)
    next unless node.callout_marker?

    counter += 1
    id = node.xml_id
    label = node.label || counter.to_s
    markers << { number: counter, id: id, label: label }.compact
  end
  markers
end

#extract_content(element) ⇒ Object

Extract content nodes from an element using the handler registry. Iterates mixed content, dispatching each DocBook element to its registered handler. Unknown elements are silently skipped.



56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 56

def extract_content(element)
  content = []

  element.each_mixed_content do |node|
    case node
    when String
      next if node.strip.empty?
    else
      handle_node(node, content)
    end
  end
  content.compact
end

#extract_text(element) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 91

def extract_text(element)
  texts = []
  element.each_mixed_content do |node|
    if node.is_a?(String)
      texts << node
    elsif node.content
      texts << node.content.join
    end
  end
  texts.join
end

#extract_text_with_callouts(element, co_markers) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 119

def extract_text_with_callouts(element, co_markers)
  marker_idx = 0
  texts = []
  element.each_mixed_content do |node|
    if node.is_a?(String)
      texts << node
    elsif node.callout_marker?
      marker = co_markers[marker_idx]
      texts << "(#{marker[:label]})"
      marker_idx += 1
    elsif node.content
      texts << node.content.join
    end
  end
  texts.join
end

#flush_footnotesObject



195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 195

def flush_footnotes
  return nil if @footnotes.empty?

  entries = @footnotes.map do |fn|
    Node.new(
      type: "footnote_entry",
      attrs: { id: fn[:id], ref_id: fn[:ref_id], number: fn[:number] },
      content: fn[:content],
    )
  end

  @footnotes = []
  Node.new(type: "footnotes", content: entries)
end


136
137
138
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 136

def link_node(element)
  Handlers::Inline.link(element, context: self)
end

#paragraph_handler(para) ⇒ Object

Paragraph handler used by annotation and others that need paragraph_node directly



79
80
81
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 79

def paragraph_handler(para)
  Handlers::Paragraph.call(para, context: self)
end

#process_inline_content(element) ⇒ Object

=========================================

Inline Processing (delegated to Handlers::Inline)



74
75
76
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 74

def process_inline_content(element)
  Handlers::Inline.process(element, context: self)
end

#register_footnote(element) ⇒ Object

=========================================

Footnote Management



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 152

def register_footnote(element)
  @footnote_counter += 1
  num = @footnote_counter
  fn_id = "fn-#{num}"
  ref_id = "fn-ref-#{num}"

  fn_content = if element.para&.any?
                 element.para.filter_map { |p| paragraph_handler(p) }
               elsif element.content
                 process_inline_content(element)
               else
                 [text_node(extract_text(element))]
               end

  @footnotes << {
    id: fn_id,
    ref_id: ref_id,
    number: num,
    xml_id: element.xml_id,
    content: fn_content,
  }

  Node.new(
    type: "footnote_marker",
    attrs: { id: fn_id, ref_id: ref_id, number: num },
  )
end

#resolve_footnoteref(element) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 180

def resolve_footnoteref(element)
  linkend = element.linkend
  ref_fn = @footnotes.find { |fn| fn[:xml_id] == linkend } if linkend

  if ref_fn
    Node.new(
      type: "footnote_marker",
      attrs: { id: ref_fn[:id],
               ref_id: "fn-ref-#{ref_fn[:number]}-dup-#{@footnote_counter}", number: ref_fn[:number] },
    )
  else
    text_node("[footnote]")
  end
end

#resolve_title(element) ⇒ Object



144
145
146
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 144

def resolve_title(element)
  element.resolve_title
end

#text_node(text, marks: []) ⇒ Object

=========================================

Shared Helpers (used by handlers via context)



87
88
89
# File 'lib/docbook/mirror/docbook_to_mirror.rb', line 87

def text_node(text, marks: [])
  Node::Text.new(text: text, marks: marks)
end