metanorma-plugin-glossarist 0.3.9 → 0.3.10
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/lib/metanorma/plugin/glossarist/dataset_preprocessor.rb +72 -26
- data/lib/metanorma/plugin/glossarist/dataset_registry.rb +52 -1
- data/lib/metanorma/plugin/glossarist/non_verbal_formatters/base.rb +64 -0
- data/lib/metanorma/plugin/glossarist/non_verbal_formatters/figure.rb +59 -0
- data/lib/metanorma/plugin/glossarist/non_verbal_formatters/formula.rb +31 -0
- data/lib/metanorma/plugin/glossarist/non_verbal_formatters/table.rb +49 -0
- data/lib/metanorma/plugin/glossarist/non_verbal_formatters.rb +20 -0
- data/lib/metanorma/plugin/glossarist/non_verbal_renderer.rb +84 -0
- data/lib/metanorma/plugin/glossarist/section_renderer.rb +4 -2
- data/lib/metanorma/plugin/glossarist/template_renderer.rb +15 -6
- data/lib/metanorma/plugin/glossarist/version.rb +1 -1
- data/lib/metanorma-plugin-glossarist.rb +4 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0e98a4e95056510d3c6071f7c46e4bd378b2898e7fa75b44e14d5ca423efc4ca
|
|
4
|
+
data.tar.gz: ebbf812935c1fb98b523675892813de831c1ec87d152d0de78d1fd9103d6342d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99d6b54e76a006d51f7f657e5d408139e0dbcb1af14545f17d8f08f157d7c197c52a88934c84238560d5860dc0131fe78b4b4933c155934aea11410239a8cf6e
|
|
7
|
+
data.tar.gz: 34f5d1c25bb615c2a2a3efdac7710b9dd8e06d0c98c4f63bc4f8846475a952715fdf2269ca4dbc970a9bd5e0bdc17327f9f8204ef2c3a5ba42ed30a75d299747
|
|
@@ -15,6 +15,7 @@ module Metanorma
|
|
|
15
15
|
BLOCK_REGEX = /^\[glossarist,(.+?),(.+?)\]$/m
|
|
16
16
|
BIBLIOGRAPHY_REGEX = /^glossarist::render_bibliography\[(.*?)\]$/m
|
|
17
17
|
BIBLIOGRAPHY_ENTRY_REGEX = /^glossarist::render_bibliography_entry\[(.*?)\]$/m
|
|
18
|
+
NON_VERBAL_REGEX = /^glossarist::render_(figures|tables|formulas)\[(.*?)\]$/m
|
|
18
19
|
|
|
19
20
|
BIB_ANCHOR_REGEX = /^\*\s*\[\[\[([^,]+)/
|
|
20
21
|
|
|
@@ -69,32 +70,47 @@ module Metanorma
|
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def process_line(document, input_lines, current_line, liquid_doc)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
process_render_tag(liquid_doc, match)
|
|
76
|
-
elsif (match = current_line.match(IMPORT_SECTIONS_REGEX))
|
|
77
|
-
process_import_sections_tag(document, liquid_doc, match)
|
|
78
|
-
elsif (match = current_line.match(IMPORT_REGEX))
|
|
79
|
-
process_import_tag(liquid_doc, match)
|
|
80
|
-
elsif (match = current_line.match(BIBLIOGRAPHY_REGEX))
|
|
81
|
-
process_bibliography(document, liquid_doc, match)
|
|
82
|
-
elsif (match = current_line.match(BIBLIOGRAPHY_ENTRY_REGEX))
|
|
83
|
-
process_bibliography_entry(document, liquid_doc, match)
|
|
84
|
-
elsif (match = current_line.match(BLOCK_REGEX))
|
|
85
|
-
process_glossarist_block(document, liquid_doc, input_lines, match)
|
|
73
|
+
handler = directive_handler(current_line)
|
|
74
|
+
if handler
|
|
75
|
+
handler.call(document, input_lines, liquid_doc)
|
|
86
76
|
else
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
77
|
+
handle_plain_line(current_line, liquid_doc)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns a callable that handles the directive on +line+, or nil
|
|
82
|
+
# for plain content. Keeping the dispatch table small (regex →
|
|
83
|
+
# block) lets us add a new directive by appending one entry.
|
|
84
|
+
def directive_handler(line)
|
|
85
|
+
if (m = line.match(DATASET_ATTR_REGEX))
|
|
86
|
+
->(doc, lines, ldoc) { process_dataset_tag(doc, lines, ldoc, m) }
|
|
87
|
+
elsif (m = line.match(RENDER_REGEX))
|
|
88
|
+
->(_, _, ldoc) { process_render_tag(ldoc, m) }
|
|
89
|
+
elsif (m = line.match(IMPORT_SECTIONS_REGEX))
|
|
90
|
+
->(doc, _, ldoc) { process_import_sections_tag(doc, ldoc, m) }
|
|
91
|
+
elsif (m = line.match(IMPORT_REGEX))
|
|
92
|
+
->(_, _, ldoc) { process_import_tag(ldoc, m) }
|
|
93
|
+
elsif (m = line.match(BIBLIOGRAPHY_REGEX))
|
|
94
|
+
->(doc, _, ldoc) { process_bibliography(doc, ldoc, m) }
|
|
95
|
+
elsif (m = line.match(BIBLIOGRAPHY_ENTRY_REGEX))
|
|
96
|
+
->(doc, _, ldoc) { process_bibliography_entry(doc, ldoc, m) }
|
|
97
|
+
elsif (m = line.match(NON_VERBAL_REGEX))
|
|
98
|
+
->(_, _, ldoc) { process_non_verbal(ldoc, m) }
|
|
99
|
+
elsif (m = line.match(BLOCK_REGEX))
|
|
100
|
+
->(doc, lines, ldoc) { process_glossarist_block(doc, ldoc, lines, m) }
|
|
95
101
|
end
|
|
96
102
|
end
|
|
97
103
|
|
|
104
|
+
def handle_plain_line(current_line, liquid_doc)
|
|
105
|
+
if /^==+ \S/.match?(current_line)
|
|
106
|
+
@title_depth = current_line.sub(/ .*$/, "").size
|
|
107
|
+
end
|
|
108
|
+
if (match = current_line.match(BIB_ANCHOR_REGEX))
|
|
109
|
+
@existing_bib_anchors << match[1]
|
|
110
|
+
end
|
|
111
|
+
liquid_doc.add_content(current_line)
|
|
112
|
+
end
|
|
113
|
+
|
|
98
114
|
def process_dataset_tag(document, input_lines, liquid_doc, match)
|
|
99
115
|
@seen_glossarist = true
|
|
100
116
|
@registry.register(document, match[1])
|
|
@@ -157,7 +173,8 @@ module Metanorma
|
|
|
157
173
|
renderer = @renderer
|
|
158
174
|
rendered = renderer.render_concept(concept,
|
|
159
175
|
depth: @title_depth,
|
|
160
|
-
anchor_prefix: options["anchor-prefix"]
|
|
176
|
+
anchor_prefix: options["anchor-prefix"],
|
|
177
|
+
non_verbal: non_verbal_for(context_name))
|
|
161
178
|
liquid_doc.add_content("\n#{rendered}")
|
|
162
179
|
end
|
|
163
180
|
|
|
@@ -179,7 +196,8 @@ module Metanorma
|
|
|
179
196
|
renderer = @renderer
|
|
180
197
|
rendered = renderer.render_concepts(concepts,
|
|
181
198
|
depth: @title_depth,
|
|
182
|
-
anchor_prefix: options["anchor-prefix"]
|
|
199
|
+
anchor_prefix: options["anchor-prefix"],
|
|
200
|
+
non_verbal: non_verbal_for(context_name))
|
|
183
201
|
liquid_doc.add_content("\n#{rendered}")
|
|
184
202
|
end
|
|
185
203
|
|
|
@@ -196,11 +214,11 @@ module Metanorma
|
|
|
196
214
|
dataset = @registry.resolve_dataset(nil, context_name)
|
|
197
215
|
return unless dataset
|
|
198
216
|
|
|
199
|
-
parts = render_sections(dataset, register, sections, options)
|
|
217
|
+
parts = render_sections(dataset, register, sections, context_name, options)
|
|
200
218
|
liquid_doc.add_content("\n#{parts.join("\n\n")}")
|
|
201
219
|
end
|
|
202
220
|
|
|
203
|
-
def render_sections(dataset, register, sections, options)
|
|
221
|
+
def render_sections(dataset, register, sections, context_name, options)
|
|
204
222
|
section_filter = SectionFilter.new(
|
|
205
223
|
exclude: (options["section_exclude"] || "").split("|"),
|
|
206
224
|
include: (options["section_include"] || "").split("|"),
|
|
@@ -213,12 +231,25 @@ module Metanorma
|
|
|
213
231
|
depth: @title_depth,
|
|
214
232
|
sort_by: options["sort_by"] || SectionRenderer::DEFAULT_SORT_BY,
|
|
215
233
|
anchor_prefix: options["anchor-prefix"],
|
|
234
|
+
non_verbal: non_verbal_for(context_name),
|
|
216
235
|
)
|
|
217
236
|
renderer.render(filtered) do |concepts|
|
|
218
237
|
@rendered_concepts.concat(concepts)
|
|
219
238
|
end
|
|
220
239
|
end
|
|
221
240
|
|
|
241
|
+
# Builds a NonVerbalRenderer scoped to a dataset context, or
|
|
242
|
+
# returns nil if the dataset has no figures/tables/formulas. The
|
|
243
|
+
# resulting renderer is passed through to TemplateRenderer and
|
|
244
|
+
# SectionRenderer so concept-attached refs render as AsciiDoc
|
|
245
|
+
# blocks alongside the concept body.
|
|
246
|
+
def non_verbal_for(context_name)
|
|
247
|
+
collections = @registry.non_verbal_collections(context_name)
|
|
248
|
+
return nil if collections.empty?
|
|
249
|
+
|
|
250
|
+
NonVerbalRenderer.new(collections: collections)
|
|
251
|
+
end
|
|
252
|
+
|
|
222
253
|
def process_bibliography(document, liquid_doc, match)
|
|
223
254
|
@seen_glossarist = true
|
|
224
255
|
dataset_name = match[1].strip
|
|
@@ -252,6 +283,21 @@ module Metanorma
|
|
|
252
283
|
liquid_doc.add_content(entry) if entry
|
|
253
284
|
end
|
|
254
285
|
|
|
286
|
+
# Renders all dataset-level entities of a non-verbal kind
|
|
287
|
+
# (figures, tables, formulas) as AsciiDoc blocks. The directive
|
|
288
|
+
# shape is +glossarist::render_<kind>[dataset]+.
|
|
289
|
+
def process_non_verbal(liquid_doc, match)
|
|
290
|
+
@seen_glossarist = true
|
|
291
|
+
kind = :"#{match[1]}"
|
|
292
|
+
dataset_name = match[2].strip
|
|
293
|
+
collection = @registry.non_verbal_collection(dataset_name, kind)
|
|
294
|
+
return unless collection
|
|
295
|
+
|
|
296
|
+
renderer = NonVerbalRenderer.new(collections: { kind => collection })
|
|
297
|
+
rendered = renderer.render_kind(kind)
|
|
298
|
+
liquid_doc.add_content("\n#{rendered}") unless rendered.empty?
|
|
299
|
+
end
|
|
300
|
+
|
|
255
301
|
def relative_file_path(document, file_path)
|
|
256
302
|
return file_path if File.absolute_path?(file_path)
|
|
257
303
|
|
|
@@ -8,17 +8,28 @@ module Metanorma
|
|
|
8
8
|
# Resolves, caches, and exposes dataset models for a document.
|
|
9
9
|
#
|
|
10
10
|
# Single source of truth for everything the preprocessor needs from a
|
|
11
|
-
# glossarist dataset: concepts, section hierarchy, and
|
|
11
|
+
# glossarist dataset: concepts, section hierarchy, bibliography, and
|
|
12
|
+
# dataset-level non-verbal entities (figures, tables, formulas).
|
|
12
13
|
# Each is exposed as the typed Glossarist model object so callers
|
|
13
14
|
# never poke at raw YAML hashes.
|
|
14
15
|
class DatasetRegistry
|
|
15
16
|
BIBLIOGRAPHY_FILENAME = "bibliography.yaml"
|
|
16
17
|
REGISTER_FILENAME = "register.yaml"
|
|
17
18
|
|
|
19
|
+
# Map of plural kind symbol → (collection class, subdirectory name).
|
|
20
|
+
# Adding a new non-verbal kind = adding one entry here. The accessor
|
|
21
|
+
# `{kind}_for` and loader are derived from this table.
|
|
22
|
+
NON_VERBAL_KINDS = {
|
|
23
|
+
figures: [::Glossarist::Collections::FigureCollection, "figures"],
|
|
24
|
+
tables: [::Glossarist::Collections::TableCollection, "tables"],
|
|
25
|
+
formulas: [::Glossarist::Collections::FormulaCollection, "formulas"],
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
18
28
|
def initialize
|
|
19
29
|
@stores = {}
|
|
20
30
|
@registers = {}
|
|
21
31
|
@bibliographies = {}
|
|
32
|
+
@non_verbal = {}
|
|
22
33
|
@context_paths = {}
|
|
23
34
|
end
|
|
24
35
|
|
|
@@ -81,6 +92,38 @@ module Metanorma
|
|
|
81
92
|
store_for(path).concepts
|
|
82
93
|
end
|
|
83
94
|
|
|
95
|
+
# Returns the typed NonVerbalCollection for a registered context
|
|
96
|
+
# (e.g. FigureCollection), or nil if the dataset has no such
|
|
97
|
+
# subdirectory. +kind+ is one of the keys of NON_VERBAL_KINDS.
|
|
98
|
+
def non_verbal_collection(context_name, kind)
|
|
99
|
+
unless NON_VERBAL_KINDS.key?(kind)
|
|
100
|
+
raise ArgumentError, "unknown non-verbal kind: #{kind.inspect}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
path = @context_paths[context_name]
|
|
104
|
+
return nil unless path
|
|
105
|
+
|
|
106
|
+
collection_class, subdir = NON_VERBAL_KINDS.fetch(kind)
|
|
107
|
+
non_verbal_at(path, kind, subdir, collection_class)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
NON_VERBAL_KINDS.each_key do |kind|
|
|
111
|
+
define_method("#{kind}_for") do |context_name|
|
|
112
|
+
non_verbal_collection(context_name, kind)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns all available non-verbal collections for a context as a
|
|
117
|
+
# hash keyed by kind symbol (e.g. +{ figures: FigureCollection }+).
|
|
118
|
+
# Kinds whose subdirectory doesn't exist are omitted. Convenient
|
|
119
|
+
# for building a NonVerbalRenderer in one call.
|
|
120
|
+
def non_verbal_collections(context_name)
|
|
121
|
+
NON_VERBAL_KINDS.each_with_object({}) do |(kind, _), memo|
|
|
122
|
+
collection = non_verbal_collection(context_name, kind)
|
|
123
|
+
memo[kind] = collection if collection
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
84
127
|
private
|
|
85
128
|
|
|
86
129
|
def concepts_for(context_name)
|
|
@@ -108,6 +151,14 @@ module Metanorma
|
|
|
108
151
|
end
|
|
109
152
|
end
|
|
110
153
|
|
|
154
|
+
def non_verbal_at(path, kind, subdir, collection_class)
|
|
155
|
+
dir = File.join(path, subdir)
|
|
156
|
+
return nil unless File.directory?(dir)
|
|
157
|
+
|
|
158
|
+
@non_verbal[path] ||= {}
|
|
159
|
+
@non_verbal[path][kind] ||= collection_class.from_directory(dir)
|
|
160
|
+
end
|
|
161
|
+
|
|
111
162
|
def relative_file_path(document, file_path)
|
|
112
163
|
return file_path if File.absolute_path?(file_path)
|
|
113
164
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Glossarist
|
|
6
|
+
module NonVerbalFormatters
|
|
7
|
+
# Shared rendering helpers for non-verbal entity formatters.
|
|
8
|
+
#
|
|
9
|
+
# Each formatter owns the kind-specific body (image block, table
|
|
10
|
+
# block, stem block) and delegates anchor, caption, and a11y
|
|
11
|
+
# framing to this base. Localized text is resolved by ISO 639 code
|
|
12
|
+
# with graceful fallback to the first available value.
|
|
13
|
+
class Base
|
|
14
|
+
def initialize(entity, lang: "eng")
|
|
15
|
+
@entity = entity
|
|
16
|
+
@lang = lang
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_asciidoc
|
|
20
|
+
parts = [anchor_line, caption_line, body].compact.reject(&:empty?)
|
|
21
|
+
"#{parts.join("\n")}\n"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
attr_reader :entity, :lang
|
|
27
|
+
|
|
28
|
+
# Subclasses implement this with the kind-specific AsciiDoc body
|
|
29
|
+
# (e.g. image::, table, stem block).
|
|
30
|
+
def body
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def anchor_line
|
|
35
|
+
id = entity.id
|
|
36
|
+
id ? "[[#{id}]]" : nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def caption_line
|
|
40
|
+
text = localized(entity.caption)
|
|
41
|
+
text ? ".#{text}" : nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def alt_text
|
|
45
|
+
localized(entity.alt)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def description_text
|
|
49
|
+
localized(entity.description)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Picks the value for the requested language, falling back to
|
|
53
|
+
# the first available value if missing. Returns nil for empty
|
|
54
|
+
# or absent hashes.
|
|
55
|
+
def localized(hash)
|
|
56
|
+
return nil if hash.nil? || hash.empty?
|
|
57
|
+
|
|
58
|
+
hash[lang] || hash.values.first
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Glossarist
|
|
6
|
+
module NonVerbalFormatters
|
|
7
|
+
# Renders a Glossarist::Figure as an AsciiDoc image block.
|
|
8
|
+
#
|
|
9
|
+
# Picks a single best image variant: vector (SVG) preferred for
|
|
10
|
+
# resolution-independence, then any first image. Subfigures are
|
|
11
|
+
# rendered recursively as separate image blocks so each carries
|
|
12
|
+
# its own anchor and caption.
|
|
13
|
+
class Figure < Base
|
|
14
|
+
ROLE_PRIORITY = %w[vector raster print light dark].freeze
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
def body
|
|
19
|
+
image = best_image
|
|
20
|
+
return subfigure_blocks if image.nil?
|
|
21
|
+
|
|
22
|
+
line = "image::#{image.src}[#{image_attrs(image)}]"
|
|
23
|
+
subfigure_blocks ? "#{line}\n\n#{subfigure_blocks}" : line
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def best_image
|
|
29
|
+
images = Array(entity.images)
|
|
30
|
+
return nil if images.empty?
|
|
31
|
+
|
|
32
|
+
ROLE_PRIORITY.each do |role|
|
|
33
|
+
found = images.find { |img| img.role == role }
|
|
34
|
+
return found if found
|
|
35
|
+
end
|
|
36
|
+
images.first
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def image_attrs(image)
|
|
40
|
+
attrs = []
|
|
41
|
+
attrs << alt_text if alt_text
|
|
42
|
+
attrs << "width=#{image.width}" if image.width
|
|
43
|
+
attrs << "height=#{image.height}" if image.height
|
|
44
|
+
attrs.join(",")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def subfigure_blocks
|
|
48
|
+
subs = Array(entity.subfigures)
|
|
49
|
+
return nil if subs.empty?
|
|
50
|
+
|
|
51
|
+
subs.map do |sub|
|
|
52
|
+
self.class.new(sub, lang: lang).to_asciidoc
|
|
53
|
+
end.join("\n").strip
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Glossarist
|
|
6
|
+
module NonVerbalFormatters
|
|
7
|
+
# Renders a Glossarist::Formula as an AsciiDoc stem block.
|
|
8
|
+
#
|
|
9
|
+
# The expression hash is keyed by language (matching caption/alt);
|
|
10
|
+
# the +notation+ field carries the markup language (latex, mathml,
|
|
11
|
+
# asciimath) but the body is format-agnostic — Metanorma's stem
|
|
12
|
+
# block accepts any notation supported by the renderer.
|
|
13
|
+
class Formula < Base
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
def body
|
|
17
|
+
expr = localized(entity.expression)
|
|
18
|
+
return "" if expr.nil? || expr.empty?
|
|
19
|
+
|
|
20
|
+
<<~STEM
|
|
21
|
+
[stem]
|
|
22
|
+
++++
|
|
23
|
+
#{expr}
|
|
24
|
+
++++
|
|
25
|
+
STEM
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Glossarist
|
|
6
|
+
module NonVerbalFormatters
|
|
7
|
+
# Renders a Glossarist::Table as an AsciiDoc table block.
|
|
8
|
+
#
|
|
9
|
+
# Two payload shapes are supported:
|
|
10
|
+
# - +format: structured+ — +content+ has +headers+ and +rows+
|
|
11
|
+
# arrays, rendered as an AsciiDoc table.
|
|
12
|
+
# - +format: asciidoc+ (or any non-structured) — +content+ is a
|
|
13
|
+
# raw markup string emitted verbatim between caption and the
|
|
14
|
+
# next block.
|
|
15
|
+
class Table < Base
|
|
16
|
+
STRUCTURED = "structured"
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
def body
|
|
21
|
+
entity.format == STRUCTURED ? structured_table : raw_block
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def structured_table
|
|
27
|
+
lines = ["|==="]
|
|
28
|
+
append_headers(lines)
|
|
29
|
+
Array(entity.content&.dig("rows")).each do |row|
|
|
30
|
+
lines << "|#{Array(row).join(' |')}"
|
|
31
|
+
end
|
|
32
|
+
lines << "|==="
|
|
33
|
+
lines.join("\n")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def append_headers(lines)
|
|
37
|
+
headers = Array(entity.content&.dig("headers"))
|
|
38
|
+
lines << "|#{headers.join(' |')}" unless headers.empty?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def raw_block
|
|
42
|
+
content = entity.content
|
|
43
|
+
(content&.dig("asciidoc") || content&.dig("text")).to_s
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Glossarist
|
|
6
|
+
# Namespace for per-kind AsciiDoc formatters for dataset-level
|
|
7
|
+
# non-verbal entities. Adding a new kind = adding a new formatter
|
|
8
|
+
# class and registering it in NonVerbalRenderer::FORMATTERS.
|
|
9
|
+
module NonVerbalFormatters
|
|
10
|
+
autoload :Base, "metanorma/plugin/glossarist/non_verbal_formatters/base"
|
|
11
|
+
autoload :Figure,
|
|
12
|
+
"metanorma/plugin/glossarist/non_verbal_formatters/figure"
|
|
13
|
+
autoload :Table,
|
|
14
|
+
"metanorma/plugin/glossarist/non_verbal_formatters/table"
|
|
15
|
+
autoload :Formula,
|
|
16
|
+
"metanorma/plugin/glossarist/non_verbal_formatters/formula"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Glossarist
|
|
6
|
+
# Renders dataset-level non-verbal entities (Figure, Table, Formula)
|
|
7
|
+
# as AsciiDoc blocks. MECE sibling to BibliographyRenderer: where
|
|
8
|
+
# BibliographyRenderer owns citation provenance, NonVerbalRenderer
|
|
9
|
+
# owns the rendering of authored figures/tables/formulas.
|
|
10
|
+
#
|
|
11
|
+
# Per-kind formatting is delegated to a formatter class registered
|
|
12
|
+
# in +FORMATTERS+. Adding a new kind = adding one formatter class
|
|
13
|
+
# and one entry here; the dispatcher itself never changes shape.
|
|
14
|
+
class NonVerbalRenderer
|
|
15
|
+
FORMATTERS = {
|
|
16
|
+
figures: NonVerbalFormatters::Figure,
|
|
17
|
+
tables: NonVerbalFormatters::Table,
|
|
18
|
+
formulas: NonVerbalFormatters::Formula,
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# @param collections [Hash{Symbol => NonVerbalCollection, nil}]
|
|
22
|
+
# one entry per non-verbal kind, e.g.
|
|
23
|
+
# `{ figures: FigureCollection, tables: ..., formulas: ... }`.
|
|
24
|
+
# Missing or nil entries are silently skipped.
|
|
25
|
+
def initialize(collections:, lang: "eng")
|
|
26
|
+
@collections = collections
|
|
27
|
+
@lang = lang
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Render every entity in the named collection.
|
|
31
|
+
#
|
|
32
|
+
# @param kind [Symbol] key in FORMATTERS (e.g. +:figures+)
|
|
33
|
+
# @return [String] AsciiDoc blocks joined by blank lines, or ""
|
|
34
|
+
def render_kind(kind)
|
|
35
|
+
collection = @collections[kind]
|
|
36
|
+
return "" if collection.nil? || collection.entries.empty?
|
|
37
|
+
|
|
38
|
+
entries = collection.entries
|
|
39
|
+
"#{entries.map { |e| format_one(kind, e) }.join("\n\n")}\n"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Render the non-verbal entities referenced by a concept's
|
|
43
|
+
# figures/tables/formulas ref collections, in deterministic order
|
|
44
|
+
# (figures, tables, formulas). Unknown refs are skipped silently —
|
|
45
|
+
# they will surface as missing anchors during Metanorma rendering.
|
|
46
|
+
#
|
|
47
|
+
# @param concept [Glossarist::ManagedConcept]
|
|
48
|
+
# @return [String]
|
|
49
|
+
def render_concept_refs(concept)
|
|
50
|
+
FORMATTERS.keys.filter_map do |kind|
|
|
51
|
+
refs = concept_refs(concept, kind)
|
|
52
|
+
next if refs.empty?
|
|
53
|
+
|
|
54
|
+
blocks = refs.filter_map { |ref| render_ref(kind, ref) }
|
|
55
|
+
next if blocks.empty?
|
|
56
|
+
|
|
57
|
+
"#{blocks.join("\n\n")}\n"
|
|
58
|
+
end.join("\n")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def render_ref(kind, ref)
|
|
64
|
+
collection = @collections[kind]
|
|
65
|
+
return nil unless collection
|
|
66
|
+
|
|
67
|
+
entity = collection.by_id(ref.entity_id)
|
|
68
|
+
return nil unless entity
|
|
69
|
+
|
|
70
|
+
format_one(kind, entity)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def format_one(kind, entity)
|
|
74
|
+
FORMATTERS.fetch(kind).new(entity, lang: @lang).to_asciidoc
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def concept_refs(concept, kind)
|
|
78
|
+
refs = concept.data&.public_send(kind)
|
|
79
|
+
Array(refs)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -18,7 +18,7 @@ module Metanorma
|
|
|
18
18
|
# @param register [Glossarist::DatasetRegister, nil] for cascading
|
|
19
19
|
# @param renderer [TemplateRenderer] concept renderer
|
|
20
20
|
# @param depth [Integer] base heading depth for sections
|
|
21
|
-
# @param options [Hash] :sort_by, :anchor_prefix
|
|
21
|
+
# @param options [Hash] :sort_by, :anchor_prefix, :non_verbal
|
|
22
22
|
def initialize(dataset:, register:, renderer:, depth:, **options)
|
|
23
23
|
@dataset = dataset
|
|
24
24
|
@register = register
|
|
@@ -26,6 +26,7 @@ module Metanorma
|
|
|
26
26
|
@depth = depth
|
|
27
27
|
@sort_by = options[:sort_by] || DEFAULT_SORT_BY
|
|
28
28
|
@anchor_prefix = options[:anchor_prefix]
|
|
29
|
+
@non_verbal = options[:non_verbal]
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
# @param sections [Array<Glossarist::Section>]
|
|
@@ -55,7 +56,8 @@ module Metanorma
|
|
|
55
56
|
heading = "#{'=' * (@depth + 1)} #{section.name || section.id}"
|
|
56
57
|
body = @renderer.render_concepts(concepts,
|
|
57
58
|
depth: @depth + 1,
|
|
58
|
-
anchor_prefix: @anchor_prefix
|
|
59
|
+
anchor_prefix: @anchor_prefix,
|
|
60
|
+
non_verbal: @non_verbal)
|
|
59
61
|
"#{heading}\n\n#{body}"
|
|
60
62
|
end
|
|
61
63
|
end
|
|
@@ -12,13 +12,17 @@ module Metanorma
|
|
|
12
12
|
@template_cache = {}
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def render_concepts(concepts, depth:, anchor_prefix: nil
|
|
15
|
+
def render_concepts(concepts, depth:, anchor_prefix: nil,
|
|
16
|
+
non_verbal: nil)
|
|
16
17
|
tree = build_concept_tree(concepts)
|
|
17
|
-
parts = tree.map
|
|
18
|
+
parts = tree.map do |node|
|
|
19
|
+
render_tree_node(node, depth, anchor_prefix, non_verbal)
|
|
20
|
+
end
|
|
18
21
|
normalize_whitespace(parts.join("\n\n"))
|
|
19
22
|
end
|
|
20
23
|
|
|
21
|
-
def render_concept(concept, depth:, anchor_prefix: nil
|
|
24
|
+
def render_concept(concept, depth:, anchor_prefix: nil,
|
|
25
|
+
non_verbal: nil)
|
|
22
26
|
l10n = concept.localization(@lang)
|
|
23
27
|
context = {
|
|
24
28
|
"concept" => concept.to_liquid,
|
|
@@ -28,6 +32,9 @@ module Metanorma
|
|
|
28
32
|
}
|
|
29
33
|
template_content = cached_template(concept)
|
|
30
34
|
rendered = render_template(template_content, context)
|
|
35
|
+
if non_verbal
|
|
36
|
+
rendered += "\n\n#{non_verbal.render_concept_refs(concept)}"
|
|
37
|
+
end
|
|
31
38
|
normalize_whitespace(rendered)
|
|
32
39
|
end
|
|
33
40
|
|
|
@@ -41,12 +48,14 @@ module Metanorma
|
|
|
41
48
|
end
|
|
42
49
|
end
|
|
43
50
|
|
|
44
|
-
def render_tree_node((concept, children), depth, anchor_prefix
|
|
51
|
+
def render_tree_node((concept, children), depth, anchor_prefix,
|
|
52
|
+
non_verbal)
|
|
45
53
|
result = render_concept(concept, depth: depth,
|
|
46
|
-
anchor_prefix: anchor_prefix
|
|
54
|
+
anchor_prefix: anchor_prefix,
|
|
55
|
+
non_verbal: non_verbal)
|
|
47
56
|
children.each do |child_node|
|
|
48
57
|
result += "\n" + render_tree_node(child_node, depth + 1,
|
|
49
|
-
anchor_prefix)
|
|
58
|
+
anchor_prefix, non_verbal)
|
|
50
59
|
end
|
|
51
60
|
result
|
|
52
61
|
end
|
|
@@ -21,6 +21,10 @@ module Metanorma
|
|
|
21
21
|
autoload :Document, "metanorma/plugin/glossarist/document"
|
|
22
22
|
autoload :Liquid, "metanorma/plugin/glossarist/liquid"
|
|
23
23
|
autoload :LiquidRendering, "metanorma/plugin/glossarist/liquid_rendering"
|
|
24
|
+
autoload :NonVerbalFormatters,
|
|
25
|
+
"metanorma/plugin/glossarist/non_verbal_formatters"
|
|
26
|
+
autoload :NonVerbalRenderer,
|
|
27
|
+
"metanorma/plugin/glossarist/non_verbal_renderer"
|
|
24
28
|
autoload :Sanitize, "metanorma/plugin/glossarist/sanitize"
|
|
25
29
|
autoload :SectionCascade, "metanorma/plugin/glossarist/section_cascade"
|
|
26
30
|
autoload :SectionFilter, "metanorma/plugin/glossarist/section_filter"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: metanorma-plugin-glossarist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: asciidoctor
|
|
@@ -111,6 +111,12 @@ files:
|
|
|
111
111
|
- lib/metanorma/plugin/glossarist/liquid/multiply_local_file_system.rb
|
|
112
112
|
- lib/metanorma/plugin/glossarist/liquid_rendering.rb
|
|
113
113
|
- lib/metanorma/plugin/glossarist/liquid_templates/_concept.liquid
|
|
114
|
+
- lib/metanorma/plugin/glossarist/non_verbal_formatters.rb
|
|
115
|
+
- lib/metanorma/plugin/glossarist/non_verbal_formatters/base.rb
|
|
116
|
+
- lib/metanorma/plugin/glossarist/non_verbal_formatters/figure.rb
|
|
117
|
+
- lib/metanorma/plugin/glossarist/non_verbal_formatters/formula.rb
|
|
118
|
+
- lib/metanorma/plugin/glossarist/non_verbal_formatters/table.rb
|
|
119
|
+
- lib/metanorma/plugin/glossarist/non_verbal_renderer.rb
|
|
114
120
|
- lib/metanorma/plugin/glossarist/sanitize.rb
|
|
115
121
|
- lib/metanorma/plugin/glossarist/section_cascade.rb
|
|
116
122
|
- lib/metanorma/plugin/glossarist/section_filter.rb
|