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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +9 -116
- data/README.adoc +1 -1
- data/lib/metanorma/document/components/inline/semx_element.rb +7 -8
- data/lib/metanorma/document/version.rb +1 -1
- data/lib/metanorma/html/base_renderer.rb +49 -75
- data/lib/metanorma/html/component/base.rb +2 -9
- data/lib/metanorma/html/flavor.rb +45 -0
- data/lib/metanorma/html/flavor_registry.rb +49 -0
- data/lib/metanorma/html/generator.rb +106 -29
- data/lib/metanorma/html/renderer_delegation.rb +21 -0
- data/lib/metanorma/html/renderers/element_order_traversal.rb +34 -0
- data/lib/metanorma/html/renderers/inline_renderer.rb +36 -49
- data/lib/metanorma/html/renderers/pubid_renderer.rb +4 -26
- data/lib/metanorma/html/renderers.rb +2 -0
- data/lib/metanorma/html/standard_renderer.rb +3 -1
- data/lib/metanorma/html.rb +3 -0
- data/lib/metanorma/mirror/handlers/inline/catalog.rb +57 -0
- data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +35 -35
- data/lib/metanorma/mirror/handlers/inline.rb +1 -0
- data/lib/metanorma/mirror/output/html_renderers/mark_renderers.rb +14 -37
- data/lib/metanorma/mirror/transformer.rb +91 -3
- data/lib/metanorma/mirror.rb +0 -1
- metadata +17 -7
- data/lib/metanorma/mirror/metanorma_to_mirror.rb +0 -101
|
@@ -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
|
-
|
|
12
|
-
|
|
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
|
|
16
|
-
# When the document's first author publisher
|
|
17
|
-
# the taste renderer takes precedence over
|
|
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
|
-
|
|
35
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
584
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
@@ -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|
|
|
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|
|
data/lib/metanorma/html.rb
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|