metanorma-oiml 0.1.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 +21 -0
- data/README.adoc +291 -0
- data/data/themes/oiml/assets/oiml-logo-full-dark.svg +1 -0
- data/data/themes/oiml/assets/oiml-logo-full-light.svg +1 -0
- data/data/themes/oiml/assets/oiml-logo-icon-dark.svg +1 -0
- data/data/themes/oiml/assets/oiml-logo-icon-light.svg +1 -0
- data/data/themes/oiml/assets/oiml-logo.svg +1 -0
- data/data/themes/oiml/theme.yaml +56 -0
- data/exe/oiml-sts +10 -0
- data/lib/metanorma/oiml/document/root.rb +29 -0
- data/lib/metanorma/oiml/document.rb +35 -0
- data/lib/metanorma/oiml/html/renderer.rb +14 -0
- data/lib/metanorma/oiml/html.rb +14 -0
- data/lib/metanorma/oiml/sts/cli.rb +89 -0
- data/lib/metanorma/oiml/sts/html_renderer/assets/metanorma-icon-dark.svg +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/assets/metanorma-icon-light.svg +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/assets/oiml-logo-dark.svg +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/assets/oiml-logo.svg +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/assets/oiml-smart-logo-dark.svg +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/assets/oiml-smart-logo.svg +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/assets/page.js +145 -0
- data/lib/metanorma/oiml/sts/html_renderer/assets/theme.css +509 -0
- data/lib/metanorma/oiml/sts/html_renderer/ruby.rb +1071 -0
- data/lib/metanorma/oiml/sts/html_renderer/templates/_element.html.liquid +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/templates/_footer.html.liquid +21 -0
- data/lib/metanorma/oiml/sts/html_renderer/templates/_hero.html.liquid +9 -0
- data/lib/metanorma/oiml/sts/html_renderer/templates/_img.html.liquid +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/templates/_link.html.liquid +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/templates/_meta_header.html.liquid +1 -0
- data/lib/metanorma/oiml/sts/html_renderer/templates/document.html.liquid +43 -0
- data/lib/metanorma/oiml/sts/html_renderer.rb +19 -0
- data/lib/metanorma/oiml/sts/mathml_builder.rb +93 -0
- data/lib/metanorma/oiml/sts/parity_validator/normaliser.rb +95 -0
- data/lib/metanorma/oiml/sts/parity_validator/report.rb +137 -0
- data/lib/metanorma/oiml/sts/parity_validator.rb +23 -0
- data/lib/metanorma/oiml/sts/transformer/back_transformer.rb +55 -0
- data/lib/metanorma/oiml/sts/transformer/base.rb +71 -0
- data/lib/metanorma/oiml/sts/transformer/block_dispatcher.rb +145 -0
- data/lib/metanorma/oiml/sts/transformer/body_transformer.rb +23 -0
- data/lib/metanorma/oiml/sts/transformer/context.rb +27 -0
- data/lib/metanorma/oiml/sts/transformer/dl_transformer.rb +49 -0
- data/lib/metanorma/oiml/sts/transformer/document_transformer.rb +65 -0
- data/lib/metanorma/oiml/sts/transformer/figure_transformer.rb +49 -0
- data/lib/metanorma/oiml/sts/transformer/footnote_collector.rb +33 -0
- data/lib/metanorma/oiml/sts/transformer/formula_transformer.rb +45 -0
- data/lib/metanorma/oiml/sts/transformer/front_transformer.rb +84 -0
- data/lib/metanorma/oiml/sts/transformer/id_generator.rb +39 -0
- data/lib/metanorma/oiml/sts/transformer/inline_transformer.rb +14 -0
- data/lib/metanorma/oiml/sts/transformer/list_transformer.rb +82 -0
- data/lib/metanorma/oiml/sts/transformer/model_builder.rb +322 -0
- data/lib/metanorma/oiml/sts/transformer/note_transformer.rb +103 -0
- data/lib/metanorma/oiml/sts/transformer/paragraph_transformer.rb +574 -0
- data/lib/metanorma/oiml/sts/transformer/reference_transformer.rb +107 -0
- data/lib/metanorma/oiml/sts/transformer/rendered_text_extractor.rb +168 -0
- data/lib/metanorma/oiml/sts/transformer/section_transformer.rb +233 -0
- data/lib/metanorma/oiml/sts/transformer/source_document.rb +246 -0
- data/lib/metanorma/oiml/sts/transformer/standard.rb +37 -0
- data/lib/metanorma/oiml/sts/transformer/table_transformer.rb +290 -0
- data/lib/metanorma/oiml/sts/transformer/term_transformer.rb +236 -0
- data/lib/metanorma/oiml/sts/transformer.rb +47 -0
- data/lib/metanorma/oiml/sts/validation_report.rb +43 -0
- data/lib/metanorma/oiml/sts/validator.rb +126 -0
- data/lib/metanorma/oiml/sts/version.rb +9 -0
- data/lib/metanorma/oiml/sts.rb +84 -0
- data/lib/metanorma-oiml.rb +10 -0
- data/schematron/oiml-sts.sch +70 -0
- metadata +221 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Oiml
|
|
5
|
+
module Sts
|
|
6
|
+
module Transformer
|
|
7
|
+
class BlockDispatcher < Base
|
|
8
|
+
HANDLERS = {
|
|
9
|
+
"IsoClauseSection" => :dispatch_via_section,
|
|
10
|
+
"ClauseSection" => :dispatch_via_section,
|
|
11
|
+
"IsoForewordSection" => :dispatch_via_section,
|
|
12
|
+
"IsoIntroductionSection" => :dispatch_via_section,
|
|
13
|
+
"IsoTermsSection" => :dispatch_via_section,
|
|
14
|
+
"DefinitionSection" => :dispatch_via_section,
|
|
15
|
+
"IsoAnnexSection" => :dispatch_via_section,
|
|
16
|
+
"ParagraphBlock" => :dispatch_via_paragraph,
|
|
17
|
+
"ParagraphWithFootnote" => :dispatch_via_paragraph,
|
|
18
|
+
"UnorderedList" => :dispatch_via_list,
|
|
19
|
+
"OrderedList" => :dispatch_via_list,
|
|
20
|
+
"DefinitionList" => :dispatch_via_dl,
|
|
21
|
+
"TableBlock" => :dispatch_via_table,
|
|
22
|
+
"FigureBlock" => :dispatch_via_figure,
|
|
23
|
+
"FormulaBlock" => :dispatch_via_formula,
|
|
24
|
+
"NoteBlock" => :dispatch_via_note,
|
|
25
|
+
"ExampleBlock" => :dispatch_via_note,
|
|
26
|
+
"AdmonitionBlock" => :dispatch_via_note,
|
|
27
|
+
"QuoteBlock" => :dispatch_via_note,
|
|
28
|
+
"SourcecodeBlock" => :dispatch_via_sourcecode,
|
|
29
|
+
"StandardReferencesSection" => :dispatch_via_references,
|
|
30
|
+
"BibliographySection" => :dispatch_via_references
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
PRESENTATION_CLASSES = %w[
|
|
34
|
+
FmtTitleElement FmtXrefLabelElement FmtNameElement FmtStemElement
|
|
35
|
+
FmtFnLabelElement FmtConceptElement SemxElement LocalizedString
|
|
36
|
+
LocalizedStrings FmtVal AsciiMath BrElement TabElement Bookmark
|
|
37
|
+
SpanElement
|
|
38
|
+
].freeze
|
|
39
|
+
|
|
40
|
+
def dispatch(source_obj)
|
|
41
|
+
return nil if source_obj.nil? || source_obj.is_a?(String)
|
|
42
|
+
class_name = source_obj.class.name&.split("::")&.last
|
|
43
|
+
return nil if PRESENTATION_CLASSES.include?(class_name)
|
|
44
|
+
|
|
45
|
+
handler = HANDLERS[class_name]
|
|
46
|
+
return nil unless handler
|
|
47
|
+
|
|
48
|
+
send(handler, source_obj)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
SECTION_CLASS_NAMES = %w[
|
|
52
|
+
IsoClauseSection ClauseSection IsoForewordSection
|
|
53
|
+
IsoIntroductionSection IsoTermsSection DefinitionSection
|
|
54
|
+
IsoAnnexSection AnnexSection
|
|
55
|
+
].freeze
|
|
56
|
+
|
|
57
|
+
# Typed attribute → handler method mapping for ContentSection.
|
|
58
|
+
# These are NOT yielded by each_mixed_content; we iterate them
|
|
59
|
+
# explicitly to dispatch tables, lists, notes, etc.
|
|
60
|
+
TYPED_ATTR_DISPATCH = {
|
|
61
|
+
paragraphs: :dispatch_via_paragraph,
|
|
62
|
+
unordered_lists: :dispatch_via_list,
|
|
63
|
+
ordered_lists: :dispatch_via_list,
|
|
64
|
+
tables: :dispatch_via_table,
|
|
65
|
+
figures: :dispatch_via_figure,
|
|
66
|
+
formulas: :dispatch_via_formula,
|
|
67
|
+
notes: :dispatch_via_note,
|
|
68
|
+
examples: :dispatch_via_note,
|
|
69
|
+
admonitions: :dispatch_via_note,
|
|
70
|
+
sourcecode_blocks: :dispatch_via_sourcecode,
|
|
71
|
+
quote_blocks: :dispatch_via_note,
|
|
72
|
+
definition_lists: :dispatch_via_dl,
|
|
73
|
+
definitions: :dispatch_via_section,
|
|
74
|
+
}.freeze
|
|
75
|
+
|
|
76
|
+
def dispatch_section_blocks(section)
|
|
77
|
+
results = []
|
|
78
|
+
|
|
79
|
+
# Walk typed collection attributes for all content types.
|
|
80
|
+
# each_mixed_content is NOT used because it overlaps with
|
|
81
|
+
# typed attributes (causing duplication) and misses some
|
|
82
|
+
# content types (tables, lists, notes).
|
|
83
|
+
TYPED_ATTR_DISPATCH.each do |attr, handler|
|
|
84
|
+
next unless section.class.method_defined?(attr)
|
|
85
|
+
collection = section.public_send(attr)
|
|
86
|
+
Array(collection).each do |item|
|
|
87
|
+
result = send(handler, item)
|
|
88
|
+
results << result if result
|
|
89
|
+
dispatch_paragraph_siblings(item, results)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
results
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# MN's typed model attaches "block siblings" as attributes on
|
|
97
|
+
# the preceding block (ParagraphBlock.note holds notes that
|
|
98
|
+
# semantically follow the paragraph; TableBlock.note holds
|
|
99
|
+
# table notes, which render right after the table). Walk those
|
|
100
|
+
# sibling collections and dispatch each, preserving document
|
|
101
|
+
# order. Restricted to ParagraphBlock and TableBlock to avoid
|
|
102
|
+
# firing for lists/etc. that have their own note sub-collections
|
|
103
|
+
# with different semantics.
|
|
104
|
+
def dispatch_paragraph_siblings(item, results)
|
|
105
|
+
return unless paragraph_block?(item) || table_block?(item)
|
|
106
|
+
|
|
107
|
+
SIBLING_ATTRS.each do |attr, handler|
|
|
108
|
+
next unless item.class.method_defined?(attr)
|
|
109
|
+
Array(item.public_send(attr)).each do |sibling|
|
|
110
|
+
result = send(handler, sibling)
|
|
111
|
+
results << result if result
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def paragraph_block?(item)
|
|
117
|
+
item.is_a?(Metanorma::Document::Components::Paragraphs::ParagraphBlock)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def table_block?(item)
|
|
121
|
+
item.is_a?(Metanorma::Document::Components::Tables::TableBlock)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
SIBLING_ATTRS = {
|
|
125
|
+
note: :dispatch_via_note,
|
|
126
|
+
example: :dispatch_via_note,
|
|
127
|
+
}.freeze
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def dispatch_via_section(obj); section_transformer.transform(obj); end
|
|
132
|
+
def dispatch_via_paragraph(obj); paragraph_transformer.transform(obj); end
|
|
133
|
+
def dispatch_via_list(obj); list_transformer.transform(obj); end
|
|
134
|
+
def dispatch_via_dl(obj); dl_transformer.transform(obj); end
|
|
135
|
+
def dispatch_via_table(obj); table_transformer.transform(obj); end
|
|
136
|
+
def dispatch_via_figure(obj); figure_transformer.transform(obj); end
|
|
137
|
+
def dispatch_via_formula(obj); formula_transformer.transform(obj); end
|
|
138
|
+
def dispatch_via_note(obj); note_transformer.transform(obj); end
|
|
139
|
+
def dispatch_via_sourcecode(obj); note_transformer.transform_preformat(obj); end
|
|
140
|
+
def dispatch_via_references(obj); reference_transformer.transform_section(obj); end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Metanorma
|
|
3
|
+
module Oiml
|
|
4
|
+
module Sts
|
|
5
|
+
module Transformer
|
|
6
|
+
class BodyTransformer < Base
|
|
7
|
+
def transform(source)
|
|
8
|
+
sections = []
|
|
9
|
+
|
|
10
|
+
sections += front_transformer.preface_sections(source) if source.front?
|
|
11
|
+
|
|
12
|
+
source.sections.each do |clause|
|
|
13
|
+
sec = dispatcher.dispatch(clause)
|
|
14
|
+
sections << sec if sec
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
ModelBuilder.body(sec: sections)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Oiml
|
|
5
|
+
module Sts
|
|
6
|
+
module Transformer
|
|
7
|
+
class Context
|
|
8
|
+
attr_reader :source, :id_generator, :footnote_collector
|
|
9
|
+
|
|
10
|
+
def initialize(source)
|
|
11
|
+
@source = source
|
|
12
|
+
@id_generator = IdGenerator.new
|
|
13
|
+
@footnote_collector = FootnoteCollector.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def language
|
|
17
|
+
source.language || "en"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def docidentifier
|
|
21
|
+
source.docidentifier
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Oiml
|
|
5
|
+
module Sts
|
|
6
|
+
module Transformer
|
|
7
|
+
# Converts MN `<dl>`/`<dt>`/`<dd>` to STS `<def-list>`/`<def-item>`.
|
|
8
|
+
# Follows mnconvert's pattern (mn2xml.xsl lines 5327-5528): each
|
|
9
|
+
# dt/dd pair becomes a def-item with term + def children. Notes
|
|
10
|
+
# inside dl are extracted as siblings after the def-list.
|
|
11
|
+
class DlTransformer < Base
|
|
12
|
+
def transform(source_dl)
|
|
13
|
+
def_items = pair_dt_dd(source_dl)
|
|
14
|
+
ModelBuilder.def_list(id: source_dl.id, def_items: def_items)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def pair_dt_dd(source_dl)
|
|
20
|
+
dts = Array(source_dl.dt)
|
|
21
|
+
dds = Array(source_dl.dd)
|
|
22
|
+
dts.each_with_index.map do |dt, i|
|
|
23
|
+
build_def_item(dt, dds[i])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def build_def_item(dt, dd)
|
|
28
|
+
attrs = {}
|
|
29
|
+
attrs[:id] = dt.id if dt.class.method_defined?(:id) && dt.id
|
|
30
|
+
|
|
31
|
+
term_text = RenderedTextExtractor.text_of(dt)
|
|
32
|
+
term = term_text.nil? || term_text.empty? ? nil : ModelBuilder.term(content: [term_text])
|
|
33
|
+
|
|
34
|
+
defn = dd ? build_def(dd) : nil
|
|
35
|
+
|
|
36
|
+
attrs[:term] = term if term
|
|
37
|
+
attrs[:defn] = defn if defn
|
|
38
|
+
ModelBuilder.def_item(**attrs)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def build_def(dd)
|
|
42
|
+
paragraphs = Array(dd.p).map { |p| paragraph_transformer.transform(p) }
|
|
43
|
+
ModelBuilder.def(paragraph: paragraphs)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Oiml
|
|
5
|
+
module Sts
|
|
6
|
+
module Transformer
|
|
7
|
+
class DocumentTransformer < Base
|
|
8
|
+
def transform(source)
|
|
9
|
+
front_model = build_front(source)
|
|
10
|
+
body_model = build_body(source)
|
|
11
|
+
back_model = build_back(source)
|
|
12
|
+
|
|
13
|
+
std = ModelBuilder.standard(
|
|
14
|
+
lang: context.language,
|
|
15
|
+
dtd_version: "1.2",
|
|
16
|
+
front: front_model,
|
|
17
|
+
body: body_model,
|
|
18
|
+
back: back_model
|
|
19
|
+
)
|
|
20
|
+
std
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def transform_to_xml(source)
|
|
24
|
+
std = transform(source)
|
|
25
|
+
xml = std.to_xml
|
|
26
|
+
inject_namespaces(xml)
|
|
27
|
+
.then { |x| inject_processing_meta(x) }
|
|
28
|
+
.then { |x| fix_lang_attribute(x) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def build_front(source)
|
|
34
|
+
return nil unless source.has_metadata?
|
|
35
|
+
front_transformer.transform(source)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def build_body(source)
|
|
39
|
+
BodyTransformer.new(context).transform(source)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def build_back(source)
|
|
43
|
+
return nil unless source.has_back?
|
|
44
|
+
BackTransformer.new(context).transform(source)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def inject_namespaces(xml)
|
|
48
|
+
return xml if xml.include?("xmlns:xlink")
|
|
49
|
+
xml.sub("<standard", '<standard xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:mml="http://www.w3.org/1998/Math/MathML"')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def inject_processing_meta(xml)
|
|
53
|
+
return xml if xml.include?("<processing-meta")
|
|
54
|
+
meta = '<processing-meta tagset-family="sts" base-tagset="interchange" table-model="xhtml" mathml="MathML 3.0" terminology-model="tbx"/>'
|
|
55
|
+
xml.sub(/(<standard[^>]*>)/, "\\1\n #{meta}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def fix_lang_attribute(xml)
|
|
59
|
+
xml.gsub(/\slang="([a-z-]+)"/, ' xml:lang="\\1"')
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Metanorma
|
|
3
|
+
module Oiml
|
|
4
|
+
module Sts
|
|
5
|
+
module Transformer
|
|
6
|
+
class FigureTransformer < Base
|
|
7
|
+
def transform(source_fig)
|
|
8
|
+
attrs = {}
|
|
9
|
+
attrs[:id] = source_fig.id if source_fig.class.method_defined?(:id) && source_fig.id
|
|
10
|
+
|
|
11
|
+
label_text = extract_label(source_fig)
|
|
12
|
+
caption_text = extract_title(source_fig)
|
|
13
|
+
graphic = extract_graphic(source_fig)
|
|
14
|
+
|
|
15
|
+
ModelBuilder.fig(id: attrs[:id], label: label_text, caption: ModelBuilder.caption(title: caption_text), graphic: graphic)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# "Figure 1" from the presentation XML autonum attribute —
|
|
21
|
+
# same pattern as TableTransformer's label.
|
|
22
|
+
def extract_label(source_fig)
|
|
23
|
+
return nil unless source_fig.class.method_defined?(:autonum) && source_fig.autonum
|
|
24
|
+
|
|
25
|
+
"Figure #{source_fig.autonum}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def extract_title(source_fig)
|
|
29
|
+
return nil unless source_fig.class.method_defined?(:name) && source_fig.name
|
|
30
|
+
|
|
31
|
+
text = RenderedTextExtractor.text_of(source_fig.name).strip
|
|
32
|
+
text.empty? ? nil : text
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def extract_graphic(source_fig)
|
|
36
|
+
return nil unless source_fig.class.method_defined?(:image)
|
|
37
|
+
images = Array(source_fig.image)
|
|
38
|
+
return nil if images.empty?
|
|
39
|
+
img = images.first
|
|
40
|
+
href = img.src if img.class.method_defined?(:src)
|
|
41
|
+
href ||= img.href if img.class.method_defined?(:href)
|
|
42
|
+
return nil unless href
|
|
43
|
+
ModelBuilder.graphic(xlink_href: href)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Oiml
|
|
5
|
+
module Sts
|
|
6
|
+
module Transformer
|
|
7
|
+
# Collects footnotes during conversion so they can be flushed to the
|
|
8
|
+
# right location in the STS output.
|
|
9
|
+
class FootnoteCollector
|
|
10
|
+
def initialize
|
|
11
|
+
@seen = {}
|
|
12
|
+
@counter = 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register(source_id)
|
|
16
|
+
return @seen[source_id] if @seen.key?(source_id)
|
|
17
|
+
|
|
18
|
+
@counter += 1
|
|
19
|
+
@seen[source_id] = "fn_#{@counter}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def known?(source_id)
|
|
23
|
+
@seen.key?(source_id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def size
|
|
27
|
+
@seen.size
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Metanorma
|
|
3
|
+
module Oiml
|
|
4
|
+
module Sts
|
|
5
|
+
module Transformer
|
|
6
|
+
class FormulaTransformer < Base
|
|
7
|
+
def transform(source_formula)
|
|
8
|
+
id = source_formula.id if source_formula.class.method_defined?(:id) && source_formula.id
|
|
9
|
+
label = extract_label(source_formula)
|
|
10
|
+
math = extract_math(source_formula)
|
|
11
|
+
ModelBuilder.disp_formula(id: id, label: label, math: math)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def extract_label(source_formula)
|
|
17
|
+
return nil unless source_formula.class.method_defined?(:fmt_name)
|
|
18
|
+
fmt_name = Array(source_formula.fmt_name).first
|
|
19
|
+
return nil unless fmt_name
|
|
20
|
+
RenderedTextExtractor.text_of(fmt_name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# MathML passthrough: extract <math> from the formula's stem and
|
|
24
|
+
# parse into Mml::V3::Math. Delegates to MathmlBuilder so the
|
|
25
|
+
# math extraction logic lives in one place. Zero Nokogiri.
|
|
26
|
+
def extract_math(source_formula)
|
|
27
|
+
stem = first_stem(source_formula)
|
|
28
|
+
return nil unless stem
|
|
29
|
+
|
|
30
|
+
::Metanorma::Oiml::Sts::MathmlBuilder.math_from_stem(stem)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def first_stem(source_formula)
|
|
34
|
+
if source_formula.class.method_defined?(:stem)
|
|
35
|
+
Array(source_formula.stem).first
|
|
36
|
+
elsif source_formula.class.method_defined?(:formula)
|
|
37
|
+
formula_el = Array(source_formula.formula).first
|
|
38
|
+
Array(formula_el.stem).first if formula_el
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Metanorma
|
|
3
|
+
module Oiml
|
|
4
|
+
module Sts
|
|
5
|
+
module Transformer
|
|
6
|
+
class FrontTransformer < Base
|
|
7
|
+
def transform(source)
|
|
8
|
+
std_meta = build_std_meta(source)
|
|
9
|
+
ModelBuilder.front(std_meta: std_meta)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def preface_sections(source)
|
|
13
|
+
sections = []
|
|
14
|
+
sections += build_boilerplate(source)
|
|
15
|
+
sections += build_foreword(source) if source.foreword
|
|
16
|
+
sections += build_introduction(source) if source.introduction
|
|
17
|
+
sections
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def build_std_meta(source)
|
|
23
|
+
title = source.formatted_title || source.title(type: "main") || source.title
|
|
24
|
+
ModelBuilder.std_meta(
|
|
25
|
+
doc_identifier: source.docidentifier,
|
|
26
|
+
title: title,
|
|
27
|
+
pub_date: source.pub_date,
|
|
28
|
+
permissions: ModelBuilder.permissions(
|
|
29
|
+
holder: source.copyright_holder || source.publisher || "OIML",
|
|
30
|
+
year: source.copyright_year || source.pub_date
|
|
31
|
+
),
|
|
32
|
+
custom_meta_group: build_custom_meta(source)
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_custom_meta(source)
|
|
37
|
+
series = source.docidentifier.to_s.match(/OIML\s+([A-Z])\s/) { |m| m[1] }
|
|
38
|
+
return nil unless series
|
|
39
|
+
ModelBuilder.custom_meta_group(name: "oiml-doc-series", value: series)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def build_boilerplate(source)
|
|
43
|
+
typed = source.typed_root
|
|
44
|
+
bp = typed.boilerplate
|
|
45
|
+
return [] unless bp
|
|
46
|
+
|
|
47
|
+
sections = []
|
|
48
|
+
Array(bp.copyright_statement).each do |cs|
|
|
49
|
+
paragraphs = []
|
|
50
|
+
cs.each_mixed_content do |inner|
|
|
51
|
+
next if inner.is_a?(String)
|
|
52
|
+
next unless inner.class.method_defined?(:paragraphs)
|
|
53
|
+
Array(inner.paragraphs).each { |p| paragraphs << paragraph_transformer.transform(p) }
|
|
54
|
+
end
|
|
55
|
+
next if paragraphs.empty?
|
|
56
|
+
sections << ModelBuilder.sec(title: "Copyright", paragraph: paragraphs)
|
|
57
|
+
end
|
|
58
|
+
Array(bp.feedback_statement).each do |fs|
|
|
59
|
+
paragraphs = []
|
|
60
|
+
fs.each_mixed_content do |inner|
|
|
61
|
+
next if inner.is_a?(String)
|
|
62
|
+
next unless inner.class.method_defined?(:paragraphs)
|
|
63
|
+
Array(inner.paragraphs).each { |p| paragraphs << paragraph_transformer.transform(p) }
|
|
64
|
+
end
|
|
65
|
+
next if paragraphs.empty?
|
|
66
|
+
sections << ModelBuilder.sec(title: "Feedback", paragraph: paragraphs)
|
|
67
|
+
end
|
|
68
|
+
sections
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def build_foreword(source)
|
|
72
|
+
content = dispatcher.dispatch_section_blocks(source.foreword)
|
|
73
|
+
[ModelBuilder.sec(title: "Foreword", paragraph: content)]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def build_introduction(source)
|
|
77
|
+
content = dispatcher.dispatch_section_blocks(source.introduction)
|
|
78
|
+
[ModelBuilder.sec(title: "Introduction", paragraph: content)]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Oiml
|
|
5
|
+
module Sts
|
|
6
|
+
module Transformer
|
|
7
|
+
class IdGenerator
|
|
8
|
+
def initialize
|
|
9
|
+
@registry = {}
|
|
10
|
+
@counter = 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def register(source_id, sts_id)
|
|
14
|
+
@registry[source_id] = sts_id
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def remap(source_id)
|
|
18
|
+
@registry[source_id]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def id_for(obj, prefix: "sec")
|
|
22
|
+
return nil unless obj
|
|
23
|
+
source_id = obj.id if obj.is_a?(Lutaml::Model::Serializable) && obj.class.method_defined?(:id)
|
|
24
|
+
source_id ||= nil
|
|
25
|
+
|
|
26
|
+
if source_id && !source_id.empty?
|
|
27
|
+
return @registry[source_id] if @registry.key?(source_id)
|
|
28
|
+
register(source_id, source_id)
|
|
29
|
+
return source_id
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@counter += 1
|
|
33
|
+
"#{prefix}_#{@counter}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Metanorma
|
|
3
|
+
module Oiml
|
|
4
|
+
module Sts
|
|
5
|
+
module Transformer
|
|
6
|
+
class ListTransformer < Base
|
|
7
|
+
def transform(source_list)
|
|
8
|
+
list_type = source_list.is_a?(Metanorma::Document::Components::Lists::OrderedList) ? "order" : "bullet"
|
|
9
|
+
items = Array(source_list.listitem).map { |li| build_list_item(li) }
|
|
10
|
+
ModelBuilder.list(list_type: list_type, list_item: items)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def transform_def_list(source_dl)
|
|
14
|
+
items = Array(source_dl.dt).zip(Array(source_dl.dd)).map do |dt, dd|
|
|
15
|
+
build_def_item(dt, dd)
|
|
16
|
+
end.compact
|
|
17
|
+
::Sts::NisoSts::DefList.new(def_item: items)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def build_list_item(li)
|
|
23
|
+
nested_lists = build_nested_lists(li)
|
|
24
|
+
|
|
25
|
+
::Sts::NisoSts::ListItem.new do |item|
|
|
26
|
+
label = item_label(li)
|
|
27
|
+
item.label ::Sts::NisoSts::Label.new(content: [label]) if label
|
|
28
|
+
Array(li.paragraphs).each { |p| item.p paragraph_transformer.transform(p) }
|
|
29
|
+
nested_lists.each { |l| item.list l }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The item's marker from the presentation XML (its fmt-name /
|
|
34
|
+
# autonum, e.g. "—" or "1."), nil when absent.
|
|
35
|
+
def item_label(li)
|
|
36
|
+
return nil unless li.class.method_defined?(:fmt_name) && li.fmt_name
|
|
37
|
+
|
|
38
|
+
text = RenderedTextExtractor.text_of(li.fmt_name).strip
|
|
39
|
+
text.empty? ? nil : text
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Recurses into nested <ul>/<ol> children of a list item.
|
|
43
|
+
# MN stores them as typed collection attrs on ListItem.
|
|
44
|
+
def build_nested_lists(li)
|
|
45
|
+
results = []
|
|
46
|
+
[:unordered_lists, :ordered_lists].each do |attr|
|
|
47
|
+
next unless li.class.method_defined?(attr)
|
|
48
|
+
Array(li.public_send(attr)).each do |nested|
|
|
49
|
+
results << transform(nested)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
results
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def build_def_item(dt, dd)
|
|
56
|
+
term_text = RenderedTextExtractor.text_of(dt)
|
|
57
|
+
desc_text = extract_dd_text(dd)
|
|
58
|
+
return nil unless term_text || desc_text
|
|
59
|
+
|
|
60
|
+
attrs = {}
|
|
61
|
+
attrs[:term] = ::Sts::NisoSts::Term.new(text: [term_text]) if term_text && !term_text.empty?
|
|
62
|
+
if desc_text && !desc_text.empty?
|
|
63
|
+
attrs[:def] = ::Sts::NisoSts::Def.new(
|
|
64
|
+
paragraph: [::Sts::NisoSts::Paragraph.new(content: [desc_text])]
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
::Sts::NisoSts::DefItem.new(attrs)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def extract_dd_text(dd)
|
|
71
|
+
return nil unless dd
|
|
72
|
+
ps = dd.p if dd.is_a?(Lutaml::Model::Serializable) && dd.class.method_defined?(:p)
|
|
73
|
+
ps = Array(ps)
|
|
74
|
+
return RenderedTextExtractor.text_of(dd) if ps.empty?
|
|
75
|
+
texts = ps.map { |p| RenderedTextExtractor.text_of(p) }.reject(&:empty?)
|
|
76
|
+
texts.empty? ? nil : texts.join(" ")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|