Module: Metanorma::Mirror::Handlers::Inline

Defined in:
lib/metanorma/mirror/handlers/inline.rb,
lib/metanorma/mirror/handlers/inline/text_extractor.rb,
lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb

Defined Under Namespace

Modules: RichHtmlRenderer, TextExtractor

Constant Summary collapse

MARK_BUILDERS =
{
  Metanorma::Document::Components::Inline::EmRawElement => ->(_el) {
    Handlers.build_mark("emphasis")
  },
  Metanorma::Document::Components::Inline::StrongRawElement => ->(_el) {
    Handlers.build_mark("strong")
  },
  Metanorma::Document::Components::Inline::SubElement => ->(_el) {
    Handlers.build_mark("subscript")
  },
  Metanorma::Document::Components::Inline::SupElement => ->(_el) {
    Handlers.build_mark("superscript")
  },
  Metanorma::Document::Components::Inline::TtElement => ->(_el) {
    Handlers.build_mark("code")
  },
  Metanorma::Document::Components::TextElements::UnderlineElement => ->(_el) {
    Handlers.build_mark("underline")
  },
  Metanorma::Document::Components::TextElements::StrikeElement => ->(_el) {
    Handlers.build_mark("strike")
  },
  Metanorma::Document::Components::Inline::SmallCapElement => ->(_el) {
    Handlers.build_mark("smallcap")
  },
  Metanorma::Document::Components::Inline::Bcp14Element => ->(_el) {
    Handlers.build_mark("bcp14")
  },
  Metanorma::Document::Components::Inline::LinkElement => ->(el) {
    attrs = {
      href: SafeAttr.read(el, :target) || SafeAttr.read(el, :href),
    }.compact
    Handlers.build_mark("link", attrs: attrs)
  },
  Metanorma::Document::Components::Inline::XrefElement => ->(el) {
    attrs = { target: SafeAttr.read(el, :target) }.compact
    Handlers.build_mark("xref", attrs: attrs)
  },
  Metanorma::Document::Components::Inline::ErefElement => ->(el) {
    attrs = {
      bibitemid: SafeAttr.read(el, :bibitemid),
      citeas: SafeAttr.read(el, :citeas),
    }.compact
    Handlers.build_mark("eref", attrs: attrs)
  },
  Metanorma::Document::Components::Inline::FnElement => ->(el) {
    attrs = {
      id: SafeAttr.read(el, :id),
      reference: SafeAttr.read(el, :reference),
    }.compact
    Handlers.build_mark("footnote", attrs: attrs)
  },
  Metanorma::Document::Components::Inline::StemInlineElement => ->(el) {
    attrs = { stem_type: SafeAttr.read(el, :stem_type) || "MathML" }
    math = SafeAttr.read(el, :math)
    attrs[:mathml] = MathUtil.mathml_from_math(math) if math
    Handlers.build_mark("stem", attrs: attrs.compact)
  },
  Metanorma::Document::Components::Inline::ConceptElement => ->(el) {
    attrs = {
      refterm: SafeAttr.read(el, :refterm),
      renderterm: SafeAttr.read(el, :renderterm),
    }.compact
    Handlers.build_mark("concept", attrs: attrs)
  },
  Metanorma::Document::Components::Inline::SpanElement => ->(el) {
    attrs = {
      class_attr: SafeAttr.read(el, :class_attr),
      style: SafeAttr.read(el, :style),
    }.compact
    Handlers.build_mark("span", attrs: attrs)
  },
}.freeze
SEMX_MARK_CONFIG =
{
  "xref" => { mark_type: "xref", target_attr: :target,
              fmt_attr: :fmt_xref },
  "eref" => { mark_type: "eref", target_attr: :target,
              fmt_attr: :fmt_xref },
  "link" => { mark_type: "link", target_attr: :target,
              fmt_attr: :fmt_link },
}.freeze
CROSSREF_MARKS =
%w[xref eref link].to_set.freeze

Class Method Summary collapse

Class Method Details

.block_in_inline?(element, context) ⇒ Boolean

Returns:

  • (Boolean)


212
213
214
# File 'lib/metanorma/mirror/handlers/inline.rb', line 212

def self.block_in_inline?(element, context)
  context.registry.registered?(element.class)
end

.extract_element_text(element) ⇒ Object

Delegates to TextExtractor



96
97
98
# File 'lib/metanorma/mirror/handlers/inline.rb', line 96

def self.extract_element_text(element)
  TextExtractor.extract_element_text(element)
end

.extract_formatted_text(element) ⇒ Object



100
101
102
# File 'lib/metanorma/mirror/handlers/inline.rb', line 100

def self.extract_formatted_text(element)
  TextExtractor.extract_formatted_text(element)
end

.extract_inline(element, context:) ⇒ Object

Primary entry point: extract inline content from a mixed-content element. Iterates semantic children only — fmt-* rendered siblings are skipped to avoid emitting duplicate marks for the same logical content.



116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/metanorma/mirror/handlers/inline.rb', line 116

def self.extract_inline(element, context:)
  nodes = []
  Metanorma::Document::Components::Inline::SemanticContent.each(element) do |node|
    case node
    when String
      nodes << context.text_node(node) unless node.empty?
    else
      handle_inline_element(node, nodes, context:)
    end
  end
  filter_empty_crossrefs(nodes)
end

.extract_marked_text(mark_type, element) ⇒ Object



201
202
203
204
205
206
# File 'lib/metanorma/mirror/handlers/inline.rb', line 201

def self.extract_marked_text(mark_type, element)
  extractor = mark_text_extractors[mark_type]
  return extractor.call(element) if extractor

  TextExtractor.extract_element_text(element)
end

.extract_rich_html(element) ⇒ Object

Delegates to RichHtmlRenderer



109
110
111
# File 'lib/metanorma/mirror/handlers/inline.rb', line 109

def self.extract_rich_html(element)
  RichHtmlRenderer.extract(element)
end

.extract_text_from_model(node) ⇒ Object



104
105
106
# File 'lib/metanorma/mirror/handlers/inline.rb', line 104

def self.extract_text_from_model(node)
  TextExtractor.extract_text_from_model(node)
end

.filter_empty_crossrefs(nodes) ⇒ Object



216
217
218
219
220
221
# File 'lib/metanorma/mirror/handlers/inline.rb', line 216

def self.filter_empty_crossrefs(nodes)
  nodes.reject do |n|
    n.is_a?(Model::Text) && n.text.strip.empty? &&
      n.marks.any? { |m| CROSSREF_MARKS.include?(m.type) }
  end
end

.handle_block_in_inline(element, nodes, context:) ⇒ Object



178
179
180
181
# File 'lib/metanorma/mirror/handlers/inline.rb', line 178

def self.handle_block_in_inline(element, nodes, context:)
  result = context.registry.handle(element, context: context)
  result.append_to(nodes)
end

.handle_inline_element(element, nodes, context:) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/metanorma/mirror/handlers/inline.rb', line 129

def self.handle_inline_element(element, nodes, context:)
  if semx_element?(element)
    handle_semx(element, nodes, context:)
    return
  end

  if block_in_inline?(element, context)
    handle_block_in_inline(element, nodes, context:)
    return
  end

  mark_builder = MARK_BUILDERS[element.class]
  if mark_builder
    mark = mark_builder.call(element)
    text = extract_marked_text(mark.type, element)
    nodes << context.text_node(text, marks: [mark])
  else
    handle_structured_inline(element, nodes, context:)
  end
end

.handle_semx(element, nodes, context:) ⇒ Object



150
151
152
153
154
155
156
157
# File 'lib/metanorma/mirror/handlers/inline.rb', line 150

def self.handle_semx(element, nodes, context:)
  config = SEMX_MARK_CONFIG[element.element_attr]
  if config
    handle_semx_crossref(element, config, nodes, context:)
  else
    handle_structured_inline(element, nodes, context:)
  end
end

.handle_semx_crossref(element, config, nodes, context:) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/metanorma/mirror/handlers/inline.rb', line 159

def self.handle_semx_crossref(element, config, nodes, context:)
  fmt_children = SafeAttr.read(element, config[:fmt_attr])
  fmt_child = fmt_children&.first

  mark_attrs = {}
  if fmt_child && config[:target_attr]
    mark_attrs[config[:target_attr]] = SafeAttr.read(fmt_child, :target)
  end

  if element.element_attr == "eref" && fmt_child
    mark_attrs[:citeas] = extract_formatted_text(fmt_child)
  end

  text = extract_formatted_text(fmt_child || element)
  mark = Handlers.build_mark(config[:mark_type],
                             attrs: mark_attrs.compact)
  nodes << context.text_node(text, marks: [mark])
end

.handle_structured_inline(element, nodes, context:) ⇒ Object



183
184
185
186
187
188
189
190
191
# File 'lib/metanorma/mirror/handlers/inline.rb', line 183

def self.handle_structured_inline(element, nodes, context:)
  if element.is_a?(Lutaml::Model::Serializable)
    inner_nodes = extract_inline(element, context:)
    nodes.concat(inner_nodes)
  else
    text = element.text
    nodes << context.text_node(text) if text.is_a?(String) && !text.strip.empty?
  end
end

.mark_text_extractorsObject



193
194
195
196
197
198
199
# File 'lib/metanorma/mirror/handlers/inline.rb', line 193

def self.mark_text_extractors
  @mark_text_extractors ||= {
    "footnote" => TextExtractor.method(:extract_fn_label),
    "stem" => TextExtractor.method(:extract_stem_text),
    "span" => TextExtractor.method(:extract_span_text),
  }
end

.semx_element?(element) ⇒ Boolean

Returns:

  • (Boolean)


208
209
210
# File 'lib/metanorma/mirror/handlers/inline.rb', line 208

def self.semx_element?(element)
  element.is_a?(Metanorma::Document::Components::Inline::SemxElement)
end