metanorma-document 0.2.12 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,18 +3,22 @@
3
3
  module Metanorma
4
4
  module Html
5
5
  class Generator
6
- @renderers = []
7
6
  @tastes = []
8
7
  @setup = false
9
8
 
10
9
  class << self
11
- def register(model_class, renderer_class)
12
- @renderers << [model_class, renderer_class]
10
+ # Returns the full FlavorRegistry built from setup!. Read-only
11
+ # access for BaseRenderer, PubidRenderer, and any consumer that
12
+ # needs flavor identity.
13
+ def flavors
14
+ setup! unless @setup
15
+ @flavors
13
16
  end
14
17
 
15
- # Register a taste: same document model, different renderer based on publisher.
16
- # When the document's first author publisher abbreviation matches,
17
- # the taste renderer takes precedence over the model-based renderer.
18
+ # Register a taste: same document model, different renderer based
19
+ # on publisher. When the document's first author publisher
20
+ # abbreviation matches, the taste renderer takes precedence over
21
+ # the model-based renderer.
18
22
  def register_taste(model_class, publisher_abbrev, renderer_class)
19
23
  @tastes << [model_class, publisher_abbrev, renderer_class]
20
24
  end
@@ -27,15 +31,11 @@ module Metanorma
27
31
  def renderer_for(document)
28
32
  setup! unless @setup
29
33
 
30
- # Check tastes first (publisher-based dispatch)
31
34
  taste_renderer = find_taste(document)
32
35
  return taste_renderer if taste_renderer
33
36
 
34
- # Fall back to model-based dispatch (most specific last)
35
- @renderers.reverse_each do |model_class, renderer_class|
36
- return renderer_class if document.is_a?(model_class)
37
- end
38
- BaseRenderer
37
+ flavor = @flavors.find_for(document.class)
38
+ flavor&.renderer_class || BaseRenderer
39
39
  end
40
40
 
41
41
  private
@@ -79,6 +79,7 @@ module Metanorma
79
79
  return if @setup
80
80
 
81
81
  @setup = true
82
+ @flavors = build_flavor_registry
82
83
 
83
84
  # Trigger autoloads by referencing constants
84
85
  BaseRenderer
@@ -97,27 +98,103 @@ module Metanorma
97
98
  PdfaRenderer
98
99
  RiboseRenderer
99
100
 
100
- # Register renderers (most general first, most specific last)
101
- register Metanorma::Document::Root, BaseRenderer
102
- register Metanorma::StandardDocument::Root, StandardRenderer
103
- register Metanorma::IsoDocument::Root, IsoRenderer
104
- register Metanorma::BipmDocument::Root, BipmRenderer
105
- register Metanorma::CcDocument::Root, CcRenderer
106
- register Metanorma::IecDocument::Root, IecRenderer
107
- register Metanorma::IeeeDocument::Root, IeeeRenderer
108
- register Metanorma::IetfDocument::Root, IetfRenderer
109
- register Metanorma::IhoDocument::Root, IhoRenderer
110
- register Metanorma::ItuDocument::Root, ItuRenderer
111
- register Metanorma::OgcDocument::Root, OgcRenderer
112
- register Metanorma::OimlDocument::Root, OimlRenderer
113
- register Metanorma::RiboseDocument::Root, RiboseRenderer
114
-
115
101
  # Register tastes (publisher-based dispatch within same model)
116
- register_taste Metanorma::IsoDocument::Root, "ICC",
117
- IccRenderer
102
+ register_taste Metanorma::IsoDocument::Root, "ICC", IccRenderer
118
103
  register_taste Metanorma::RiboseDocument::Root, "PDF Association",
119
104
  PdfaRenderer
120
105
  end
106
+
107
+ # Single source of truth for flavor identity. Each Flavor ties
108
+ # together the model class, the renderer, the symbolic name, and
109
+ # (if applicable) the Pubid module. Adding a new flavor = one
110
+ # entry here.
111
+ def build_flavor_registry
112
+ FlavorRegistry.new.tap do |registry|
113
+ registry.register(Flavor.new(
114
+ name: nil,
115
+ model_class: Metanorma::Document::Root,
116
+ renderer_class: BaseRenderer,
117
+ ))
118
+ registry.register(Flavor.new(
119
+ name: nil,
120
+ model_class: Metanorma::StandardDocument::Root,
121
+ renderer_class: StandardRenderer,
122
+ ))
123
+ registry.register(Flavor.new(
124
+ name: :iso,
125
+ model_class: Metanorma::IsoDocument::Root,
126
+ renderer_class: IsoRenderer,
127
+ pubid_module: :"Pubid::Iso",
128
+ ))
129
+ registry.register(Flavor.new(
130
+ name: :bipm,
131
+ model_class: Metanorma::BipmDocument::Root,
132
+ renderer_class: BipmRenderer,
133
+ ))
134
+ registry.register(Flavor.new(
135
+ name: :cc,
136
+ model_class: Metanorma::CcDocument::Root,
137
+ renderer_class: CcRenderer,
138
+ ))
139
+ registry.register(Flavor.new(
140
+ name: :iec,
141
+ model_class: Metanorma::IecDocument::Root,
142
+ renderer_class: IecRenderer,
143
+ pubid_module: :"Pubid::Iec",
144
+ ))
145
+ registry.register(Flavor.new(
146
+ name: :ieee,
147
+ model_class: Metanorma::IeeeDocument::Root,
148
+ renderer_class: IeeeRenderer,
149
+ pubid_module: :"Pubid::Ieee",
150
+ ))
151
+ registry.register(Flavor.new(
152
+ name: :ietf,
153
+ model_class: Metanorma::IetfDocument::Root,
154
+ renderer_class: IetfRenderer,
155
+ ))
156
+ registry.register(Flavor.new(
157
+ name: :iho,
158
+ model_class: Metanorma::IhoDocument::Root,
159
+ renderer_class: IhoRenderer,
160
+ pubid_module: :"Pubid::Iho",
161
+ ))
162
+ registry.register(Flavor.new(
163
+ name: :itu,
164
+ model_class: Metanorma::ItuDocument::Root,
165
+ renderer_class: ItuRenderer,
166
+ pubid_module: :"Pubid::Ithu",
167
+ ))
168
+ registry.register(Flavor.new(
169
+ name: :ogc,
170
+ model_class: Metanorma::OgcDocument::Root,
171
+ renderer_class: OgcRenderer,
172
+ ))
173
+ registry.register(Flavor.new(
174
+ name: :oiml,
175
+ model_class: Metanorma::OimlDocument::Root,
176
+ renderer_class: OimlRenderer,
177
+ pubid_module: :"Pubid::Oiml",
178
+ ))
179
+ registry.register(Flavor.new(
180
+ name: :pdfa,
181
+ model_class: Metanorma::RiboseDocument::Root,
182
+ renderer_class: PdfaRenderer,
183
+ ))
184
+ registry.register(Flavor.new(
185
+ name: :ribose,
186
+ model_class: Metanorma::RiboseDocument::Root,
187
+ renderer_class: RiboseRenderer,
188
+ ))
189
+ end
190
+ end
191
+
192
+ def safe_attr(node, attr_name)
193
+ return nil unless node.is_a?(Lutaml::Model::Serializable)
194
+ return nil unless node.class.attributes.key?(attr_name)
195
+
196
+ node.public_send(attr_name)
197
+ end
121
198
  end
122
199
  end
123
200
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Html
5
+ # Shared forwarding methods for narrowing wrappers around a
6
+ # renderer. Both RendererContext (used by Drop factories) and
7
+ # Component::Base (used by Component renderers) include this
8
+ # module so the shared forwarding surface is declared in one
9
+ # place. Adding a new shared method is one edit here, not two.
10
+ #
11
+ # Per-class-specific forwarders stay declared in each wrapper.
12
+ module RendererDelegation
13
+ def safe_attr(...) = @renderer.safe_attr(...)
14
+ def escape_html(...) = @renderer.escape_html(...)
15
+ def extract_block_label(...)= @renderer.extract_block_label(...)
16
+ def extract_plain_text(...) = @renderer.extract_plain_text(...)
17
+ def render_mixed_inline(...)= @renderer.render_mixed_inline(...)
18
+ def render_liquid(...) = @renderer.render_liquid(...)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Html
5
+ module Renderers
6
+ # Builds the reverse lookup `{ xml_element_name => attribute_name }`
7
+ # from a node's xml mapping. Shared between walkers that iterate
8
+ # element_order: InlineRenderer#walk_ordered (renders in document
9
+ # order) and BaseRenderer#extract_plain_text (extracts text).
10
+ #
11
+ # Both walkers previously reimplemented this hash construction
12
+ # inline; the construction is the genuinely-shared traversal
13
+ # concern. Per-element handling (recursion, span emission, tab/br
14
+ # spacing) differs and stays in each walker.
15
+ module ElementOrderTraversal
16
+ module_function
17
+
18
+ # Returns `{ xml_element_name => attr_name }` including both
19
+ # symbol and string keys for each mapped element, so callers
20
+ # can look up by either el.name form.
21
+ def element_to_attr_map(xml_mapping)
22
+ {}.tap do |map|
23
+ xml_mapping.mapping_elements_hash.each_value do |rule_or_array|
24
+ Array(rule_or_array).each do |rule|
25
+ map[rule.name] = rule.to
26
+ map[rule.name.to_s] = rule.to if rule.name.is_a?(Symbol)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -15,16 +15,8 @@ module Metanorma
15
15
  xml_mapping = node.class.mappings_for(:xml, node.lutaml_register)
16
16
  return false unless xml_mapping
17
17
 
18
- element_to_attr = {}
19
- xml_mapping.mapping_elements_hash.each_value do |rule_or_array|
20
- Array(rule_or_array).each do |rule|
21
- element_to_attr[rule.name] = rule.to
22
- if rule.name.is_a?(Symbol)
23
- element_to_attr[rule.name.to_s] =
24
- rule.to
25
- end
26
- end
27
- end
18
+ element_to_attr =
19
+ Renderers::ElementOrderTraversal.element_to_attr_map(xml_mapping)
28
20
 
29
21
  skip_indices = build_semx_skip_set(node)
30
22
 
@@ -299,13 +291,14 @@ module Metanorma
299
291
  parts << escape_html(texts)
300
292
  end
301
293
 
302
- inline_attrs = %i[em strong smallcap sub sup tt underline strike
303
- xref eref link span stem concept fn br tab keyword
304
- fmt_annotation_start fmt_annotation_end
305
- fmt_stem fmt_fn_label fmt_concept
306
- bookmark image semx fmt_xref_label]
307
- inline_attrs.each do |attr|
308
- values = safe_attr(node, attr)
294
+ # Derive the element-mapped attribute list from the node's own
295
+ # xml_mapping rather than maintaining a parallel hardcoded list.
296
+ # The model is the single source of truth; adding a new inline
297
+ # element type via map_element on the model class is enough.
298
+ node.class.mappings[:xml].elements.each do |rule|
299
+ next if rule.to == :text
300
+
301
+ values = safe_attr(node, rule.to)
309
302
  next if values.nil?
310
303
 
311
304
  Array(values).each { |v| parts << (render_inline_element(v) || "") }
@@ -541,50 +534,44 @@ module Metanorma
541
534
  nil
542
535
  end
543
536
 
537
+ # Stem content formats in priority order. Each entry binds an
538
+ # attribute reader on the stem element to the value-render path
539
+ # (raw XML when serializable, escaped text otherwise). Adding a
540
+ # new format = one entry here.
541
+ STEM_FORMATS = [
542
+ { attr: :math, raw_xml: true },
543
+ { attr: :asciimath },
544
+ { attr: :latexmath },
545
+ ].freeze
546
+
544
547
  def render_stem_content(stem)
545
548
  return nil if stem.nil?
546
549
 
547
- if stem.is_a?(Metanorma::Document::Components::Inline::StemInlineElement)
548
- return nil
549
- end
550
-
551
- if stem.is_a?(Metanorma::Document::Components::Inline::FmtStemElement)
552
- return nil
553
- end
554
-
555
- if stem.is_a?(Metanorma::Document::Components::TextElements::StemElement)
556
- if stem.math
557
- math_val = stem.math
558
- if math_val.is_a?(Lutaml::Model::Serializable)
559
- return math_val.to_xml
560
- end
550
+ return nil if stem.is_a?(Metanorma::Document::Components::Inline::StemInlineElement) ||
551
+ stem.is_a?(Metanorma::Document::Components::Inline::FmtStemElement)
561
552
 
562
- text = escape_html(coordinator.extract_text_value(math_val))
553
+ unless stem.is_a?(Metanorma::Document::Components::TextElements::StemElement)
554
+ text = coordinator.extract_text_value(stem)
555
+ unless text.empty?
563
556
  return render_liquid("_stem_span.html.liquid", {
564
557
  "data_attrs" => "",
565
- "text" => text,
566
- })
567
- elsif stem.asciimath
568
- text = escape_html(coordinator.extract_text_value(stem.asciimath))
569
- return render_liquid("_stem_span.html.liquid", {
570
- "data_attrs" => "",
571
- "text" => text,
572
- })
573
- end
574
- if stem.latexmath
575
- text = escape_html(coordinator.extract_text_value(stem.latexmath))
576
- return render_liquid("_stem_span.html.liquid", {
577
- "data_attrs" => "",
578
- "text" => text,
558
+ "text" => escape_html(text),
579
559
  })
580
560
  end
561
+ return nil
581
562
  end
582
563
 
583
- text = coordinator.extract_text_value(stem)
584
- unless text.empty?
564
+ STEM_FORMATS.each do |format|
565
+ value = stem.public_send(format[:attr])
566
+ next unless value
567
+
568
+ if format[:raw_xml] && value.is_a?(Lutaml::Model::Serializable)
569
+ return value.to_xml
570
+ end
571
+
585
572
  return render_liquid("_stem_span.html.liquid", {
586
573
  "data_attrs" => "",
587
- "text" => escape_html(text),
574
+ "text" => escape_html(coordinator.extract_text_value(value)),
588
575
  })
589
576
  end
590
577
  nil
@@ -4,15 +4,6 @@ module Metanorma
4
4
  module Html
5
5
  module Renderers
6
6
  class PubidRenderer
7
- FLAVOR_PUBID_MAP = {
8
- "IsoDocument" => :"Pubid::Iso",
9
- "IecDocument" => :"Pubid::Iec",
10
- "IeeeDocument" => :"Pubid::Ieee",
11
- "IhoDocument" => :"Pubid::Iho",
12
- "ItuDocument" => :"Pubid::Ithu",
13
- "OimlDocument" => :"Pubid::Oiml",
14
- }.freeze
15
-
16
7
  def initialize(coordinator)
17
8
  @coordinator = coordinator
18
9
  end
@@ -44,24 +35,11 @@ module Metanorma
44
35
  @coordinator.escape_html(text)
45
36
  end
46
37
 
38
+ # Flavor identity comes from the shared FlavorRegistry via the
39
+ # coordinator — no local duplicate of the flavor-to-pubid-module
40
+ # map.
47
41
  def resolve_pubid_module
48
- flavor_name = @coordinator.flavor_name
49
- return nil unless flavor_name
50
-
51
- @coordinator.class.ancestors.each do |ancestor|
52
- next unless ancestor.is_a?(Class)
53
-
54
- ns = ancestor.name&.split("::")&.detect do |n|
55
- FLAVOR_PUBID_MAP.key?(n)
56
- end
57
- next unless ns
58
-
59
- mod = FLAVOR_PUBID_MAP[ns]
60
- return Object.const_get(mod.to_s)
61
- end
62
- nil
63
- rescue NameError
64
- nil
42
+ @coordinator.pubid_module
65
43
  end
66
44
  end
67
45
  end
@@ -3,6 +3,8 @@
3
3
  module Metanorma
4
4
  module Html
5
5
  module Renderers
6
+ autoload :ElementOrderTraversal,
7
+ "metanorma/html/renderers/element_order_traversal"
6
8
  end
7
9
  end
8
10
  end
@@ -454,11 +454,13 @@ module Metanorma
454
454
  [prefix_text, rest.empty? ? nil : rest]
455
455
  end
456
456
 
457
+ PREFERRED_LINK_TYPES = %w[src citation].freeze
458
+
457
459
  def bibitem_url(item)
458
460
  links = Array(item.link)
459
461
  return nil if links.empty?
460
462
 
461
- preferred = links.find { |l| ["src", "citation"].include?(l.type) }
463
+ preferred = links.find { |l| PREFERRED_LINK_TYPES.include?(l.type) }
462
464
  return preferred.content.to_s if preferred && !preferred.content.to_s.empty?
463
465
 
464
466
  non_rss = links.find do |l|
@@ -65,7 +65,10 @@ module Metanorma
65
65
  end
66
66
 
67
67
  autoload :BaseRenderer, "metanorma/html/base_renderer"
68
+ autoload :Flavor, "metanorma/html/flavor"
69
+ autoload :FlavorRegistry, "metanorma/html/flavor_registry"
68
70
  autoload :Generator, "metanorma/html/generator"
71
+ autoload :RendererDelegation, "metanorma/html/renderer_delegation"
69
72
  autoload :Theme, "metanorma/html/theme"
70
73
  autoload :AssetPipeline, "metanorma/html/asset_pipeline"
71
74
  autoload :Component, "metanorma/html/component"
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mirror
5
+ module Handlers
6
+ module Inline
7
+ # Shared declarative table of inline marks whose HTML rendering is
8
+ # a fixed tag with optional static attrs. Two adapters consume it:
9
+ #
10
+ # - RichHtmlRenderer: renders Metanorma XML elements to HTML
11
+ # directly (the fallback / attribute-value path).
12
+ # - Output::HtmlRenderers::MarkRenderers: renders mirror Model marks
13
+ # to HTML (the primary SSR path).
14
+ #
15
+ # Both adapters must produce visually equivalent HTML for the same
16
+ # mark type — the table is the single source of truth for the
17
+ # mark-name to tag-and-attrs mapping. Marks whose attrs vary per
18
+ # instance (link, xref, eref, span) carry data in mark.attrs and
19
+ # are not in this table; each adapter keeps custom handlers for
20
+ # those.
21
+ module Catalog
22
+ SIMPLE_WRAPS = {
23
+ "emphasis" => { tag: :em },
24
+ "strong" => { tag: :strong },
25
+ "subscript" => { tag: :sub },
26
+ "superscript" => { tag: :sup },
27
+ "code" => { tag: :code },
28
+ "underline" => { tag: :u },
29
+ "strike" => { tag: :s },
30
+ "smallcap" => { tag: :span, style: "font-variant: small-caps" },
31
+ "concept" => { tag: :span, class: "concept" },
32
+ "bcp14" => { tag: :span, class: "bcp14" },
33
+ "footnote" => { tag: :sup, class: "footnote-inline" },
34
+ "stem" => { tag: :span, class: "stem" },
35
+ }.freeze
36
+
37
+ # XML element classes that render via SIMPLE_WRAPS without
38
+ # per-instance attrs. Maps element class → mark type (catalog
39
+ # key). RichHtmlRenderer uses this to dispatch simple elements
40
+ # through the shared table.
41
+ SIMPLE_ELEMENTS = {
42
+ Metanorma::Document::Components::Inline::EmRawElement => "emphasis",
43
+ Metanorma::Document::Components::Inline::StrongRawElement => "strong",
44
+ Metanorma::Document::Components::Inline::SubElement => "subscript",
45
+ Metanorma::Document::Components::Inline::SupElement => "superscript",
46
+ Metanorma::Document::Components::Inline::TtElement => "code",
47
+ Metanorma::Document::Components::TextElements::UnderlineElement => "underline",
48
+ Metanorma::Document::Components::TextElements::StrikeElement => "strike",
49
+ Metanorma::Document::Components::Inline::SmallCapElement => "smallcap",
50
+ Metanorma::Document::Components::Inline::Bcp14Element => "bcp14",
51
+ Metanorma::Document::Components::Inline::ConceptElement => "concept",
52
+ }.freeze
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -10,35 +10,15 @@ module Metanorma
10
10
  # representation of inline content is required (e.g., attribute values,
11
11
  # fallback rendering). All HTML construction goes through
12
12
  # Nokogiri::HTML5::Builder to guarantee well-formed, escaped output.
13
+ #
14
+ # Simple-wrap elements (em, strong, sub, sup, code, u, s, smallcap,
15
+ # bcp14, concept) share their tag-and-attrs mapping with the Model-side
16
+ # renderer via Inline::Catalog, so the XML→HTML and Model→HTML paths
17
+ # produce visually equivalent output for the same mark type.
13
18
  module RichHtmlRenderer
14
- RENDERERS = {
15
- Metanorma::Document::Components::Inline::EmRawElement => ->(el) {
16
- wrap { |d| d.em { d << raw(extract(el)) } }
17
- },
18
- Metanorma::Document::Components::Inline::StrongRawElement => ->(el) {
19
- wrap { |d| d.strong { d << raw(extract(el)) } }
20
- },
21
- Metanorma::Document::Components::Inline::SubElement => ->(el) {
22
- wrap { |d| d.sub { d << raw(extract(el)) } }
23
- },
24
- Metanorma::Document::Components::Inline::SupElement => ->(el) {
25
- wrap { |d| d.sup { d << raw(extract(el)) } }
26
- },
27
- Metanorma::Document::Components::Inline::TtElement => ->(el) {
28
- wrap { |d| d.code { d << raw(extract(el)) } }
29
- },
30
- Metanorma::Document::Components::TextElements::UnderlineElement => ->(el) {
31
- wrap { |d| d.u { d << raw(extract(el)) } }
32
- },
33
- Metanorma::Document::Components::TextElements::StrikeElement => ->(el) {
34
- wrap { |d| d.s { d << raw(extract(el)) } }
35
- },
36
- Metanorma::Document::Components::Inline::SmallCapElement => ->(el) {
37
- wrap { |d| d.span { d << raw(extract(el)) } }
38
- },
39
- Metanorma::Document::Components::Inline::Bcp14Element => ->(el) {
40
- wrap { |d| d.span { d << raw(extract(el)) } }
41
- },
19
+ # Complex element handlers — these have per-instance attrs or
20
+ # multi-step rendering and bypass the shared Catalog.
21
+ COMPLEX_RENDERERS = {
42
22
  Metanorma::Document::Components::Inline::StemInlineElement => ->(el) {
43
23
  for_stem(el)
44
24
  },
@@ -57,9 +37,6 @@ module Metanorma
57
37
  Metanorma::Document::Components::Inline::FnElement => ->(el) {
58
38
  for_fn(el)
59
39
  },
60
- Metanorma::Document::Components::Inline::ConceptElement => ->(el) {
61
- wrap { |d| d.span(class: "concept") { d << raw(extract(el)) } }
62
- },
63
40
  Metanorma::Document::Components::Inline::SpanElement => ->(el) {
64
41
  for_span(el)
65
42
  },
@@ -93,8 +70,25 @@ module Metanorma
93
70
  end
94
71
 
95
72
  def self.render_element(element)
96
- renderer = RENDERERS[element.class]
97
- renderer ? renderer.call(element) : extract(element)
73
+ complex = COMPLEX_RENDERERS[element.class]
74
+ return complex.call(element) if complex
75
+
76
+ mark_type = Catalog::SIMPLE_ELEMENTS[element.class]
77
+ return render_simple(mark_type, element) if mark_type
78
+
79
+ extract(element)
80
+ end
81
+
82
+ def self.render_simple(mark_type, element)
83
+ spec = Catalog::SIMPLE_WRAPS[mark_type]
84
+ attrs = spec.except(:tag)
85
+ wrap do |d|
86
+ if attrs.empty?
87
+ d.public_send(spec[:tag]) { d << raw(extract(element)) }
88
+ else
89
+ d.public_send(spec[:tag], attrs) { d << raw(extract(element)) }
90
+ end
91
+ end
98
92
  end
99
93
 
100
94
  def self.for_stem(element)
@@ -105,7 +99,10 @@ module Metanorma
105
99
  .sub(/\s*xmlns(:\w+)?="[^"]*"\s*/, " ")
106
100
  .gsub(/\s+/, " ")
107
101
  .strip
108
- wrap { |d| d.span(class: "inline-math") { d << raw(mathml) } }
102
+ spec = Catalog::SIMPLE_WRAPS["stem"]
103
+ wrap do |d|
104
+ d.public_send(spec[:tag], spec.except(:tag)) { d << raw(mathml) }
105
+ end
109
106
  end
110
107
 
111
108
  def self.for_xref(element)
@@ -130,7 +127,10 @@ module Metanorma
130
127
 
131
128
  def self.for_fn(element)
132
129
  reference = SafeAttr.read(element, :reference) || ""
133
- wrap { |d| d.sup(class: "footnote-inline") { d.text reference } }
130
+ spec = Catalog::SIMPLE_WRAPS["footnote"]
131
+ wrap do |d|
132
+ d.public_send(spec[:tag], spec.except(:tag)) { d.text reference }
133
+ end
134
134
  end
135
135
 
136
136
  def self.for_span(element)
@@ -6,6 +6,7 @@ module Metanorma
6
6
  module Inline
7
7
  autoload :TextExtractor, "#{__dir__}/inline/text_extractor"
8
8
  autoload :RichHtmlRenderer, "#{__dir__}/inline/rich_html_renderer"
9
+ autoload :Catalog, "#{__dir__}/inline/catalog"
9
10
 
10
11
  MARK_BUILDERS = {
11
12
  Metanorma::Document::Components::Inline::EmRawElement => ->(_el) {