metanorma-mirror 1.0.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 +7 -0
- data/LICENSE +25 -0
- data/README.adoc +41 -0
- data/lib/metanorma/mirror/class_table.rb +50 -0
- data/lib/metanorma/mirror/default_registry.rb +23 -0
- data/lib/metanorma/mirror/handler_registry.rb +60 -0
- data/lib/metanorma/mirror/handler_result.rb +37 -0
- data/lib/metanorma/mirror/handlers/admonition.rb +18 -0
- data/lib/metanorma/mirror/handlers/example.rb +21 -0
- data/lib/metanorma/mirror/handlers/figure.rb +53 -0
- data/lib/metanorma/mirror/handlers/formula.rb +35 -0
- data/lib/metanorma/mirror/handlers/imagemap.rb +43 -0
- data/lib/metanorma/mirror/handlers/inline/catalog.rb +36 -0
- data/lib/metanorma/mirror/handlers/inline/rich_html_renderer.rb +132 -0
- data/lib/metanorma/mirror/handlers/inline/text_extractor.rb +114 -0
- data/lib/metanorma/mirror/handlers/inline.rb +155 -0
- data/lib/metanorma/mirror/handlers/list.rb +110 -0
- data/lib/metanorma/mirror/handlers/note.rb +26 -0
- data/lib/metanorma/mirror/handlers/paragraph.rb +19 -0
- data/lib/metanorma/mirror/handlers/quote.rb +16 -0
- data/lib/metanorma/mirror/handlers/review.rb +19 -0
- data/lib/metanorma/mirror/handlers/section.rb +149 -0
- data/lib/metanorma/mirror/handlers/sourcecode.rb +27 -0
- data/lib/metanorma/mirror/handlers/structural.rb +68 -0
- data/lib/metanorma/mirror/handlers/svgmap.rb +52 -0
- data/lib/metanorma/mirror/handlers/table.rb +81 -0
- data/lib/metanorma/mirror/handlers/term.rb +100 -0
- data/lib/metanorma/mirror/handlers.rb +62 -0
- data/lib/metanorma/mirror/id_strategy/positional.rb +151 -0
- data/lib/metanorma/mirror/id_strategy/preserve.rb +12 -0
- data/lib/metanorma/mirror/id_strategy.rb +34 -0
- data/lib/metanorma/mirror/math_util.rb +53 -0
- data/lib/metanorma/mirror/metadata.rb +37 -0
- data/lib/metanorma/mirror/model/container.rb +50 -0
- data/lib/metanorma/mirror/model/factory.rb +34 -0
- data/lib/metanorma/mirror/model/guide.rb +29 -0
- data/lib/metanorma/mirror/model/leaf.rb +17 -0
- data/lib/metanorma/mirror/model/mark.rb +39 -0
- data/lib/metanorma/mirror/model/node.rb +42 -0
- data/lib/metanorma/mirror/model/soft_break.rb +32 -0
- data/lib/metanorma/mirror/model/text.rb +27 -0
- data/lib/metanorma/mirror/model.rb +16 -0
- data/lib/metanorma/mirror/output/builder.rb +39 -0
- data/lib/metanorma/mirror/output/formats/base_format.rb +90 -0
- data/lib/metanorma/mirror/output/formats/inline_format.rb +142 -0
- data/lib/metanorma/mirror/output/formats.rb +53 -0
- data/lib/metanorma/mirror/output/pipeline.rb +109 -0
- data/lib/metanorma/mirror/output/pipeline_context.rb +22 -0
- data/lib/metanorma/mirror/output.rb +12 -0
- data/lib/metanorma/mirror/rewriter.rb +123 -0
- data/lib/metanorma/mirror/safe_attr.rb +16 -0
- data/lib/metanorma/mirror/serialization/json_serializer.rb +27 -0
- data/lib/metanorma/mirror/serialization/yaml_serializer.rb +20 -0
- data/lib/metanorma/mirror/serialization.rb +10 -0
- data/lib/metanorma/mirror/transformer.rb +110 -0
- data/lib/metanorma/mirror/version.rb +7 -0
- data/lib/metanorma/mirror.rb +98 -0
- metadata +197 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Inline
|
|
7
|
+
module TextExtractor
|
|
8
|
+
def self.extract_element_text(element)
|
|
9
|
+
return "" unless element.is_a?(Lutaml::Model::Serializable)
|
|
10
|
+
|
|
11
|
+
text = SafeAttr.read(element, :text)
|
|
12
|
+
if text.is_a?(Array)
|
|
13
|
+
joined = text.join.strip
|
|
14
|
+
return joined unless joined.empty?
|
|
15
|
+
elsif text.is_a?(String) && !text.strip.empty?
|
|
16
|
+
return text
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
content = SafeAttr.read(element, :content)
|
|
20
|
+
if content.is_a?(Array)
|
|
21
|
+
parts = content.filter_map do |c|
|
|
22
|
+
next c.to_s if c.is_a?(String)
|
|
23
|
+
next extract_element_text(c) if c.is_a?(Lutaml::Model::Serializable)
|
|
24
|
+
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
joined = parts.join.strip
|
|
28
|
+
return joined unless joined.empty?
|
|
29
|
+
elsif content.is_a?(String) && !content.strip.empty?
|
|
30
|
+
return content
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
""
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.extract_formatted_text(element)
|
|
37
|
+
return "" unless element
|
|
38
|
+
return element.to_s unless element.is_a?(Lutaml::Model::Serializable)
|
|
39
|
+
|
|
40
|
+
parts = []
|
|
41
|
+
element.each_mixed_content do |node|
|
|
42
|
+
case node
|
|
43
|
+
when String
|
|
44
|
+
parts << node
|
|
45
|
+
when Lutaml::Model::Serializable
|
|
46
|
+
inner = extract_formatted_text(node)
|
|
47
|
+
parts << inner if inner && !inner.empty?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
result = parts.join.strip
|
|
51
|
+
result.empty? ? extract_element_text(element) : result
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.extract_text_from_model(node)
|
|
55
|
+
substitution = Mirror.inline_text_substitutions.lookup(node)
|
|
56
|
+
if substitution
|
|
57
|
+
substitution
|
|
58
|
+
else
|
|
59
|
+
text = extract_formatted_text(node)
|
|
60
|
+
text.empty? ? extract_element_text(node) : text
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.extract_fn_label(element)
|
|
65
|
+
label = SafeAttr.read(element, :fmt_fn_label)
|
|
66
|
+
return extract_element_text(element) unless label
|
|
67
|
+
|
|
68
|
+
extract_formatted_text(label)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.extract_stem_text(element)
|
|
72
|
+
asciimath = MathUtil.asciimath_from_stem(element)
|
|
73
|
+
return asciimath if asciimath
|
|
74
|
+
|
|
75
|
+
extract_element_text(element)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.extract_span_text(element)
|
|
79
|
+
text = extract_element_text(element)
|
|
80
|
+
return text unless text.strip.empty?
|
|
81
|
+
|
|
82
|
+
extract_formatted_text(element)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.extract_name_text(name)
|
|
86
|
+
return name if name.is_a?(String)
|
|
87
|
+
|
|
88
|
+
text = SafeAttr.read(name, :text)
|
|
89
|
+
return text.to_s if text.is_a?(String) && !text.strip.empty?
|
|
90
|
+
|
|
91
|
+
stems = SafeAttr.read(name, :stem)
|
|
92
|
+
if stems.is_a?(Array) && !stems.empty?
|
|
93
|
+
parts = Array(text).dup
|
|
94
|
+
stems.each_with_index do |s, i|
|
|
95
|
+
stem_text = MathUtil.text_from_stem(s)
|
|
96
|
+
parts.insert(i + 1, stem_text) if stem_text && !stem_text.empty?
|
|
97
|
+
end
|
|
98
|
+
joined = parts.join.strip
|
|
99
|
+
return joined unless joined.empty?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
return Array(text).join if text.is_a?(Array) && !text.empty?
|
|
103
|
+
|
|
104
|
+
""
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.extract_bibdata_title(bibdata)
|
|
108
|
+
Metadata.title_from_bibdata(bibdata)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Inline
|
|
7
|
+
autoload :TextExtractor, "#{__dir__}/inline/text_extractor"
|
|
8
|
+
autoload :RichHtmlRenderer, "#{__dir__}/inline/rich_html_renderer"
|
|
9
|
+
autoload :Catalog, "#{__dir__}/inline/catalog"
|
|
10
|
+
|
|
11
|
+
SEMX_MARK_CONFIG = {
|
|
12
|
+
"xref" => { mark_type: "xref", target_attr: :target,
|
|
13
|
+
fmt_attr: :fmt_xref },
|
|
14
|
+
"eref" => { mark_type: "eref", target_attr: :target,
|
|
15
|
+
fmt_attr: :fmt_xref },
|
|
16
|
+
"link" => { mark_type: "link", target_attr: :target,
|
|
17
|
+
fmt_attr: :fmt_link },
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
CROSSREF_MARKS = %w[xref eref link].to_set.freeze
|
|
21
|
+
|
|
22
|
+
# Delegates to TextExtractor
|
|
23
|
+
def self.extract_element_text(element)
|
|
24
|
+
TextExtractor.extract_element_text(element)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.extract_formatted_text(element)
|
|
28
|
+
TextExtractor.extract_formatted_text(element)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.extract_text_from_model(node)
|
|
32
|
+
TextExtractor.extract_text_from_model(node)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Delegates to RichHtmlRenderer
|
|
36
|
+
def self.extract_rich_html(element)
|
|
37
|
+
RichHtmlRenderer.extract(element)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Primary entry point: extract inline content from a mixed-content element.
|
|
41
|
+
# Iterates semantic children only — `fmt-*` rendered siblings are skipped
|
|
42
|
+
# to avoid emitting duplicate marks for the same logical content.
|
|
43
|
+
def self.extract_inline(element, context:)
|
|
44
|
+
nodes = []
|
|
45
|
+
iterator = Mirror.inline_content_iterator or
|
|
46
|
+
raise Error, "no inline content iterator registered; " \
|
|
47
|
+
"the document seed must provide one"
|
|
48
|
+
iterator.each(element) do |node|
|
|
49
|
+
case node
|
|
50
|
+
when String
|
|
51
|
+
nodes << context.text_node(node) unless node.empty?
|
|
52
|
+
else
|
|
53
|
+
handle_inline_element(node, nodes, context:)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
filter_empty_crossrefs(nodes)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.handle_inline_element(element, nodes, context:)
|
|
60
|
+
if semx_element?(element)
|
|
61
|
+
handle_semx(element, nodes, context:)
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if block_in_inline?(element, context)
|
|
66
|
+
handle_block_in_inline(element, nodes, context:)
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
mark_builder = Mirror.mark_builders.lookup(element)
|
|
71
|
+
if mark_builder
|
|
72
|
+
mark = mark_builder.call(element)
|
|
73
|
+
text = extract_marked_text(mark.type, element)
|
|
74
|
+
nodes << context.text_node(text, marks: [mark])
|
|
75
|
+
else
|
|
76
|
+
handle_structured_inline(element, nodes, context:)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.handle_semx(element, nodes, context:)
|
|
81
|
+
config = SEMX_MARK_CONFIG[element.element_attr]
|
|
82
|
+
if config
|
|
83
|
+
handle_semx_crossref(element, config, nodes, context:)
|
|
84
|
+
else
|
|
85
|
+
handle_structured_inline(element, nodes, context:)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.handle_semx_crossref(element, config, nodes, context:)
|
|
90
|
+
fmt_children = SafeAttr.read(element, config[:fmt_attr])
|
|
91
|
+
fmt_child = fmt_children&.first
|
|
92
|
+
|
|
93
|
+
mark_attrs = {}
|
|
94
|
+
if fmt_child && config[:target_attr]
|
|
95
|
+
mark_attrs[config[:target_attr]] = SafeAttr.read(fmt_child, :target)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if element.element_attr == "eref" && fmt_child
|
|
99
|
+
mark_attrs[:citeas] = extract_formatted_text(fmt_child)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
text = extract_formatted_text(fmt_child || element)
|
|
103
|
+
mark = Handlers.build_mark(config[:mark_type],
|
|
104
|
+
attrs: mark_attrs.compact)
|
|
105
|
+
nodes << context.text_node(text, marks: [mark])
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.handle_block_in_inline(element, nodes, context:)
|
|
109
|
+
result = context.registry.handle(element, context: context)
|
|
110
|
+
result.append_to(nodes)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.handle_structured_inline(element, nodes, context:)
|
|
114
|
+
if element.is_a?(Lutaml::Model::Serializable)
|
|
115
|
+
inner_nodes = extract_inline(element, context:)
|
|
116
|
+
nodes.concat(inner_nodes)
|
|
117
|
+
else
|
|
118
|
+
text = element.text
|
|
119
|
+
nodes << context.text_node(text) if text.is_a?(String) && !text.strip.empty?
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.mark_text_extractors
|
|
124
|
+
@mark_text_extractors ||= {
|
|
125
|
+
"footnote" => TextExtractor.method(:extract_fn_label),
|
|
126
|
+
"stem" => TextExtractor.method(:extract_stem_text),
|
|
127
|
+
"span" => TextExtractor.method(:extract_span_text),
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.extract_marked_text(mark_type, element)
|
|
132
|
+
extractor = mark_text_extractors[mark_type]
|
|
133
|
+
return extractor.call(element) if extractor
|
|
134
|
+
|
|
135
|
+
TextExtractor.extract_element_text(element)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def self.semx_element?(element)
|
|
139
|
+
!Mirror.semx_elements.lookup(element).nil?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.block_in_inline?(element, context)
|
|
143
|
+
context.registry.registered?(element.class)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.filter_empty_crossrefs(nodes)
|
|
147
|
+
nodes.reject do |n|
|
|
148
|
+
n.is_a?(Model::Text) && n.text.strip.empty? &&
|
|
149
|
+
n.marks.any? { |m| CROSSREF_MARKS.include?(m.type) }
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module List
|
|
7
|
+
def self.bullet(element, context:)
|
|
8
|
+
attrs = list_attrs(element)
|
|
9
|
+
items = extract_items(element, context:)
|
|
10
|
+
|
|
11
|
+
Handlers.build_node("bullet_list", attrs: attrs, content: items)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.ordered(element, context:)
|
|
15
|
+
attrs = list_attrs(element)
|
|
16
|
+
attrs[:type] = SafeAttr.read(element, :type)
|
|
17
|
+
attrs[:start] = SafeAttr.read(element, :start)
|
|
18
|
+
attrs[:group] = SafeAttr.read(element, :group)
|
|
19
|
+
items = extract_items(element, context:)
|
|
20
|
+
|
|
21
|
+
Handlers.build_node("ordered_list", attrs: attrs.compact,
|
|
22
|
+
content: items)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.definition(element, context:)
|
|
26
|
+
attrs = list_attrs(element)
|
|
27
|
+
attrs[:key] = SafeAttr.read(element, :key)
|
|
28
|
+
items = extract_definition_items(element, context:)
|
|
29
|
+
|
|
30
|
+
Handlers.build_node("dl", attrs: attrs.compact, content: items)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.list_item(element, context:)
|
|
34
|
+
attrs = {}
|
|
35
|
+
attrs[:id] = SafeAttr.read(element, :id)
|
|
36
|
+
attrs[:checkbox] = SafeAttr.read(element, :checkbox)
|
|
37
|
+
attrs[:checkedcheckbox] = SafeAttr.read(element, :checkedcheckbox)
|
|
38
|
+
|
|
39
|
+
content = []
|
|
40
|
+
text = SafeAttr.read(element, :text)
|
|
41
|
+
if text.is_a?(Array)
|
|
42
|
+
text.each do |t|
|
|
43
|
+
next if t.is_a?(String) && t.strip.empty?
|
|
44
|
+
|
|
45
|
+
content << context.text_node(t.to_s)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
%i[paragraphs unordered_lists ordered_lists sourcecode figure example
|
|
50
|
+
note quote table].each do |attr|
|
|
51
|
+
collection = SafeAttr.read(element, attr)
|
|
52
|
+
next unless collection
|
|
53
|
+
|
|
54
|
+
collection.each do |child|
|
|
55
|
+
result = context.registry.handle(child, context: context)
|
|
56
|
+
result.append_to(content)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
Handlers.build_node("list_item", attrs: attrs.compact,
|
|
61
|
+
content: content)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.extract_items(element, context:)
|
|
65
|
+
Array(element.listitem).filter_map do |li|
|
|
66
|
+
result = context.registry.handle(li, context: context)
|
|
67
|
+
result.nodes
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.extract_definition_items(element, context:)
|
|
72
|
+
items = []
|
|
73
|
+
dts = Array(element.dt)
|
|
74
|
+
dds = Array(element.dd)
|
|
75
|
+
|
|
76
|
+
dts.each_with_index do |dt, idx|
|
|
77
|
+
dt_attrs = {}
|
|
78
|
+
dt_attrs[:id] = SafeAttr.read(dt, :id)
|
|
79
|
+
dt_content = Inline.extract_inline(dt, context:)
|
|
80
|
+
items << Handlers.build_node("dt", attrs: dt_attrs.compact,
|
|
81
|
+
content: dt_content)
|
|
82
|
+
|
|
83
|
+
dd = dds[idx]
|
|
84
|
+
next unless dd
|
|
85
|
+
|
|
86
|
+
dd_attrs = {}
|
|
87
|
+
dd_attrs[:id] = SafeAttr.read(dd, :id)
|
|
88
|
+
dd_content = context.extract_named_collections(dd,
|
|
89
|
+
%i[p ul ol
|
|
90
|
+
sourcecode figure example note table formula quote dl])
|
|
91
|
+
items << Handlers.build_node("dd", attrs: dd_attrs.compact,
|
|
92
|
+
content: dd_content)
|
|
93
|
+
end
|
|
94
|
+
items
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.list_attrs(element)
|
|
98
|
+
attrs = {}
|
|
99
|
+
attrs[:id] = SafeAttr.read(element, :id)
|
|
100
|
+
attrs[:nobullet] = SafeAttr.read(element, :nobullet)
|
|
101
|
+
attrs[:bare] = SafeAttr.read(element, :bare)
|
|
102
|
+
attrs[:spacing] = SafeAttr.read(element, :spacing)
|
|
103
|
+
attrs[:indent] = SafeAttr.read(element, :indent)
|
|
104
|
+
attrs[:semx_id] = SafeAttr.read(element, :semx_id)
|
|
105
|
+
attrs.compact
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Note
|
|
7
|
+
EXTRA = { type_attr: :type_attr, remove_in_rfc: nil }.freeze
|
|
8
|
+
|
|
9
|
+
def self.call(element, context:)
|
|
10
|
+
attrs = Handlers.extract_attrs(element, extra_attrs: EXTRA)
|
|
11
|
+
|
|
12
|
+
content = []
|
|
13
|
+
element_content = SafeAttr.read(element, :content)
|
|
14
|
+
if element_content.is_a?(Array)
|
|
15
|
+
element_content.each do |p|
|
|
16
|
+
result = context.registry.handle(p, context: context)
|
|
17
|
+
result.append_to(content)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Handlers.build_node("note", attrs: attrs, content: content)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Paragraph
|
|
7
|
+
EXTRA = { alignment: nil, keep_with_next: nil,
|
|
8
|
+
keep_with_previous: nil }.freeze
|
|
9
|
+
|
|
10
|
+
def self.call(element, context:)
|
|
11
|
+
attrs = Handlers.extract_attrs(element, extra_attrs: EXTRA)
|
|
12
|
+
content = Inline.extract_inline(element, context:)
|
|
13
|
+
|
|
14
|
+
Handlers.build_node("paragraph", attrs: attrs, content: content)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Quote
|
|
7
|
+
def self.call(element, context:)
|
|
8
|
+
attrs = Handlers.extract_attrs(element)
|
|
9
|
+
content = context.extract_blocks(element)
|
|
10
|
+
|
|
11
|
+
Handlers.build_node("quote", attrs: attrs, content: content)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Review
|
|
7
|
+
EXTRA = { date: nil, from: nil, to: nil, reviewer: nil,
|
|
8
|
+
display: nil }.freeze
|
|
9
|
+
|
|
10
|
+
def self.call(element, context:)
|
|
11
|
+
attrs = Handlers.extract_attrs(element, extra_attrs: EXTRA)
|
|
12
|
+
content = context.extract_blocks(element)
|
|
13
|
+
|
|
14
|
+
Handlers.build_node("review", attrs: attrs, content: content)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Section
|
|
7
|
+
def self.clause(element, context:)
|
|
8
|
+
attrs = section_attrs(element, context:)
|
|
9
|
+
content = context.extract_blocks(element)
|
|
10
|
+
|
|
11
|
+
Handlers.build_node("clause", attrs: attrs, content: content)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.annex(element, context:)
|
|
15
|
+
attrs = section_attrs(element, context:)
|
|
16
|
+
attrs[:commentary] = SafeAttr.read(element, :commentary)
|
|
17
|
+
attrs[:language] = SafeAttr.read(element, :language)
|
|
18
|
+
attrs[:script] = SafeAttr.read(element, :script)
|
|
19
|
+
|
|
20
|
+
content = context.extract_blocks(element)
|
|
21
|
+
|
|
22
|
+
Handlers.build_node("annex", attrs: attrs.compact, content: content)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.content_section(element, context:)
|
|
26
|
+
attrs = section_attrs(element, context:)
|
|
27
|
+
content = context.extract_blocks(element)
|
|
28
|
+
|
|
29
|
+
subsection = SafeAttr.read(element, :subsection)
|
|
30
|
+
subsection&.each do |sub|
|
|
31
|
+
result = context.registry.handle(sub, context: context)
|
|
32
|
+
result.append_to(content)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Handlers.build_node("content_section", attrs: attrs, content: content)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.terms(element, context:)
|
|
39
|
+
attrs = section_attrs(element, context:)
|
|
40
|
+
content = context.extract_named_collections(element,
|
|
41
|
+
%i[p term dl example
|
|
42
|
+
admonition])
|
|
43
|
+
children = context.extract_section_children(element)
|
|
44
|
+
|
|
45
|
+
Handlers.build_node("terms", attrs: attrs,
|
|
46
|
+
content: content + children)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.definitions(element, context:)
|
|
50
|
+
attrs = section_attrs(element, context:)
|
|
51
|
+
content = context.extract_blocks(element)
|
|
52
|
+
|
|
53
|
+
Handlers.build_node("definitions", attrs: attrs, content: content)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.references(element, context:)
|
|
57
|
+
attrs = section_attrs(element, context:)
|
|
58
|
+
attrs[:normative] = SafeAttr.read(element, :normative)
|
|
59
|
+
attrs[:hidden] = SafeAttr.read(element, :hidden)
|
|
60
|
+
|
|
61
|
+
# Prefatory paragraphs (e.g. "The following documents are referred to...")
|
|
62
|
+
content = context.extract_named_collections(element,
|
|
63
|
+
%i[p])
|
|
64
|
+
|
|
65
|
+
# Bibliographic items
|
|
66
|
+
refs = SafeAttr.read(element, :references)
|
|
67
|
+
refs&.each do |ref|
|
|
68
|
+
text = extract_biblio_text(ref)
|
|
69
|
+
next if text.strip.empty?
|
|
70
|
+
|
|
71
|
+
content << Handlers.build_node("paragraph",
|
|
72
|
+
attrs: { id: SafeAttr.read(ref,
|
|
73
|
+
:id) }.compact,
|
|
74
|
+
content: [context.text_node(text)])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
Handlers.build_node("references", attrs: attrs, content: content)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.extract_biblio_text(ref)
|
|
81
|
+
parts = []
|
|
82
|
+
|
|
83
|
+
tag = SafeAttr.read(ref, :biblio_tag)
|
|
84
|
+
if tag
|
|
85
|
+
tag_text = Inline.extract_formatted_text(tag).strip
|
|
86
|
+
parts << tag_text unless tag_text.empty?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
formatted = SafeAttr.read(ref, :formatted_ref)
|
|
90
|
+
if formatted
|
|
91
|
+
fmt_text = Inline.extract_formatted_text(formatted).strip
|
|
92
|
+
parts << fmt_text unless fmt_text.empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
return parts.join(" ") unless parts.empty?
|
|
96
|
+
|
|
97
|
+
docid = SafeAttr.read(ref, :docidentifier)
|
|
98
|
+
if docid
|
|
99
|
+
text = Inline.extract_formatted_text(docid)
|
|
100
|
+
return text unless text.strip.empty?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
""
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.floating_title(element, context:)
|
|
107
|
+
attrs = {}
|
|
108
|
+
attrs[:id] = context.id_strategy.assign_id(element)
|
|
109
|
+
attrs[:depth] = SafeAttr.read(element, :depth)
|
|
110
|
+
attrs[:semx_id] = SafeAttr.read(element, :semx_id)
|
|
111
|
+
|
|
112
|
+
title = extract_title(element)
|
|
113
|
+
attrs[:title] = title if title
|
|
114
|
+
|
|
115
|
+
Handlers.build_node("floating_title", attrs: attrs.compact)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.section_attrs(element, context:)
|
|
119
|
+
attrs = {}
|
|
120
|
+
attrs[:id] = context.id_strategy.assign_id(element)
|
|
121
|
+
attrs[:number] = SafeAttr.read(element, :number)
|
|
122
|
+
attrs[:obligation] = SafeAttr.read(element, :obligation)
|
|
123
|
+
attrs[:unnumbered] = SafeAttr.read(element, :unnumbered)
|
|
124
|
+
attrs[:toc] = SafeAttr.read(element, :toc)
|
|
125
|
+
attrs[:type] = SafeAttr.read(element, :type)
|
|
126
|
+
attrs[:semx_id] = SafeAttr.read(element, :semx_id)
|
|
127
|
+
attrs[:autonum] = SafeAttr.read(element, :autonum)
|
|
128
|
+
attrs[:displayorder] = SafeAttr.read(element, :displayorder)
|
|
129
|
+
|
|
130
|
+
title = extract_title(element)
|
|
131
|
+
attrs[:title] = title if title
|
|
132
|
+
attrs.compact
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.extract_title(element)
|
|
136
|
+
title = SafeAttr.read(element, :title)
|
|
137
|
+
return nil unless title
|
|
138
|
+
|
|
139
|
+
case title
|
|
140
|
+
when String then title
|
|
141
|
+
else
|
|
142
|
+
rich = Inline.extract_rich_html(title)
|
|
143
|
+
rich.empty? ? Inline.extract_element_text(title) : rich
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Mirror
|
|
5
|
+
module Handlers
|
|
6
|
+
module Sourcecode
|
|
7
|
+
EXTRA = { language: :lang, filename: nil, linenums: nil }.freeze
|
|
8
|
+
|
|
9
|
+
def self.call(element, context:)
|
|
10
|
+
attrs = Handlers.extract_attrs(element, extra_attrs: EXTRA)
|
|
11
|
+
|
|
12
|
+
body = SafeAttr.read(element, :body)
|
|
13
|
+
text = if body
|
|
14
|
+
body.decoded_content
|
|
15
|
+
elsif SafeAttr.read(element, :content).is_a?(String)
|
|
16
|
+
SafeAttr.read(element, :content)
|
|
17
|
+
else
|
|
18
|
+
""
|
|
19
|
+
end
|
|
20
|
+
attrs[:text] = text
|
|
21
|
+
|
|
22
|
+
Handlers.build_node("sourcecode", attrs: attrs.compact)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|