metanorma 2.0.0 → 2.5.5
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/.envrc +1 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +23 -2
- data/Gemfile +17 -0
- data/Gemfile.devel +10 -0
- data/README.adoc +1 -1
- data/lib/metanorma/collection/artifact_store.rb +142 -0
- data/lib/metanorma/collection/collection.rb +160 -122
- data/lib/metanorma/collection/config/bibdata.rb +3 -3
- data/lib/metanorma/collection/config/compile_options.rb +4 -5
- data/lib/metanorma/collection/config/config.rb +81 -83
- data/lib/metanorma/collection/config/converters.rb +92 -8
- data/lib/metanorma/collection/config/directive.rb +9 -3
- data/lib/metanorma/collection/config/doc_container.rb +28 -0
- data/lib/metanorma/collection/config/manifest.rb +59 -29
- data/lib/metanorma/collection/config/namespaces.rb +12 -0
- data/lib/metanorma/collection/document/document.rb +56 -15
- data/lib/metanorma/collection/filelookup/filelookup.rb +185 -77
- data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +99 -27
- data/lib/metanorma/collection/filelookup/utils.rb +52 -0
- data/lib/metanorma/collection/helpers.rb +82 -0
- data/lib/metanorma/collection/log.rb +24 -0
- data/lib/metanorma/collection/manifest/manifest.rb +45 -14
- data/lib/metanorma/collection/multilingual/multilingual.rb +660 -0
- data/lib/metanorma/collection/renderer/filelocation.rb +166 -0
- data/lib/metanorma/collection/renderer/fileparse.rb +135 -115
- data/lib/metanorma/collection/renderer/fileprocess.rb +185 -37
- data/lib/metanorma/collection/renderer/navigation.rb +21 -7
- data/lib/metanorma/collection/renderer/render_word.rb +15 -10
- data/lib/metanorma/collection/renderer/renderer.rb +236 -32
- data/lib/metanorma/collection/renderer/svg.rb +107 -0
- data/lib/metanorma/collection/renderer/utils.rb +110 -39
- data/lib/metanorma/collection/sectionsplit/collection.rb +110 -0
- data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +198 -115
- data/lib/metanorma/collection/util/disambig_files.rb +4 -5
- data/lib/metanorma/collection/util/util.rb +155 -18
- data/lib/metanorma/collection/xrefprocess/xrefprocess.rb +47 -33
- data/lib/metanorma/compile/assets/icc-boilerplate.adoc +41 -0
- data/lib/metanorma/compile/compile.rb +181 -156
- data/lib/metanorma/compile/compile_options.rb +186 -79
- data/lib/metanorma/compile/extract.rb +75 -58
- data/lib/metanorma/compile/flavor.rb +28 -0
- data/lib/metanorma/compile/output_filename.rb +75 -0
- data/lib/metanorma/compile/output_filename_config.rb +27 -0
- data/lib/metanorma/compile/relaton_drop.rb +56 -0
- data/lib/metanorma/compile/render.rb +226 -0
- data/lib/metanorma/compile/validator.rb +26 -0
- data/lib/metanorma/compile/writeable.rb +12 -0
- data/lib/metanorma/util/fontist_helper.rb +35 -19
- data/lib/metanorma/version.rb +1 -1
- data/lib/metanorma.rb +1 -7
- data/metanorma.gemspec +15 -21
- data/plans/collection-rendering-architecture-review.md +516 -0
- metadata +65 -113
- data/.hound.yml +0 -5
- data/lib/metanorma/asciidoctor_extensions/glob_include_processor.rb +0 -22
- data/lib/metanorma/asciidoctor_extensions.rb +0 -1
- data/lib/metanorma/compile/compile_validate.rb +0 -68
- data/lib/metanorma/config/config.rb +0 -23
- data/lib/metanorma/input/asciidoc.rb +0 -107
- data/lib/metanorma/input/base.rb +0 -9
- data/lib/metanorma/input.rb +0 -7
- data/lib/metanorma/processor/processor.rb +0 -54
- data/lib/metanorma/processor.rb +0 -6
- data/lib/metanorma/registry/registry.rb +0 -63
- data/lib/metanorma/shale_monkeypatch.rb +0 -15
- data/lib/metanorma/util/util.rb +0 -45
|
@@ -1,21 +1,51 @@
|
|
|
1
1
|
require "isodoc"
|
|
2
2
|
require "htmlentities"
|
|
3
|
+
require "mime/types"
|
|
3
4
|
require_relative "fileprocess"
|
|
4
5
|
require_relative "../../util/fontist_helper"
|
|
5
|
-
|
|
6
|
+
require "metanorma-core"
|
|
6
7
|
require_relative "../filelookup/filelookup"
|
|
8
|
+
require_relative "../multilingual/multilingual"
|
|
7
9
|
require_relative "utils"
|
|
8
10
|
require_relative "render_word"
|
|
9
11
|
require_relative "navigation"
|
|
12
|
+
require_relative "svg"
|
|
13
|
+
require_relative "../log"
|
|
10
14
|
|
|
11
15
|
module Metanorma
|
|
12
16
|
class Collection
|
|
13
17
|
class Renderer
|
|
14
|
-
FORMATS = %i[html xml doc pdf].freeze
|
|
18
|
+
FORMATS = %i[html xml doc pdf pdf-portfolio].freeze
|
|
15
19
|
|
|
16
|
-
attr_accessor :isodoc, :
|
|
20
|
+
attr_accessor :isodoc, :isodoc_presxml
|
|
17
21
|
attr_reader :xml, :compile, :compile_options, :documents, :outdir,
|
|
18
|
-
:manifest
|
|
22
|
+
:manifest, :fatal_errors
|
|
23
|
+
|
|
24
|
+
# Run the block with the renderer in nested mode, restoring the previous
|
|
25
|
+
# mode afterwards. Nested mode (used by sectionsplit, which re-enters this
|
|
26
|
+
# renderer's update_xrefs on the pre-split document) preserves unresolved
|
|
27
|
+
# erefs and skips the finalising reference passes.
|
|
28
|
+
def with_nested
|
|
29
|
+
saved = @nested
|
|
30
|
+
@nested = true
|
|
31
|
+
yield
|
|
32
|
+
ensure
|
|
33
|
+
@nested = saved
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Run the block with the renderer preserving unresolved cross-document
|
|
37
|
+
# references as stubs instead of stripping them -- for an isolated build of
|
|
38
|
+
# a collection member (compiled without the rest of its collection
|
|
39
|
+
# present). Unlike +with_nested+, the ordinary intra-document passes still
|
|
40
|
+
# run (xref_process, svgmap); only the cross-document resolve/strip is
|
|
41
|
+
# turned into preserve, so a later reinflation pass can relink the stubs.
|
|
42
|
+
def with_preserve_unresolved
|
|
43
|
+
saved = @preserve_unresolved
|
|
44
|
+
@preserve_unresolved = true
|
|
45
|
+
yield
|
|
46
|
+
ensure
|
|
47
|
+
@preserve_unresolved = saved
|
|
48
|
+
end
|
|
19
49
|
|
|
20
50
|
# This is only going to render the HTML collection
|
|
21
51
|
# @param xml [Metanorma::Collection] input XML collection
|
|
@@ -32,21 +62,26 @@ module Metanorma
|
|
|
32
62
|
def initialize(collection, folder, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
33
63
|
check_options options
|
|
34
64
|
@xml = Nokogiri::XML collection.to_xml # @xml is the collection manifest
|
|
35
|
-
@
|
|
36
|
-
@
|
|
37
|
-
@script = collection.bibdata.script.first || "Latn"
|
|
65
|
+
@lang = Array(collection.bibdata.language).first || "en"
|
|
66
|
+
@script = Array(collection.bibdata.script).first || "Latn"
|
|
38
67
|
@locale = @xml.at("//xmlns:bibdata/xmlns:locale")&.text
|
|
39
|
-
@
|
|
68
|
+
@registry = Metanorma::Registry.instance
|
|
69
|
+
@flavor = options[:flavor] || flavor
|
|
40
70
|
@compile = Compile.new
|
|
41
|
-
@compile.load_flavor(@
|
|
71
|
+
@compile.load_flavor(@flavor)
|
|
42
72
|
|
|
43
|
-
|
|
73
|
+
# output processor for flavour
|
|
74
|
+
@isodoc = Util::isodoc_create(Util::taste2flavor(@flavor), @lang,
|
|
75
|
+
@script, @xml)
|
|
76
|
+
@isodoc_presxml = Util::isodoc_create(Util::taste2flavor(@flavor),
|
|
77
|
+
@lang, @script, @xml,
|
|
78
|
+
presxml: true)
|
|
44
79
|
@outdir = dir_name_cleanse(options[:output_folder])
|
|
45
|
-
@coverpage = options[:coverpage] || collection.coverpage
|
|
46
80
|
@format = ::Metanorma::Util.sort_extensions_execution(options[:format])
|
|
47
81
|
@compile_options = options[:compile] || {}
|
|
48
82
|
@compile_options[:install_fonts] = true if options[:install_fonts]
|
|
49
83
|
@log = options[:log]
|
|
84
|
+
@log&.add_msg(METANORMA_LOG_MESSAGES)
|
|
50
85
|
@bibdata = collection.bibdata
|
|
51
86
|
@documents = collection.documents
|
|
52
87
|
@bibdatas = collection.documents
|
|
@@ -58,10 +93,45 @@ module Metanorma
|
|
|
58
93
|
@final = collection.final
|
|
59
94
|
@c = HTMLEntities.new
|
|
60
95
|
@files_to_delete = []
|
|
61
|
-
|
|
96
|
+
# Per-member rendering failures (missing outputs, pooled compile
|
|
97
|
+
# errors), accumulated by file_compile_verify; Collection#render
|
|
98
|
+
# aborts on them once every member has rendered (#586).
|
|
99
|
+
@fatal_errors = []
|
|
100
|
+
@nested = options[:nested]
|
|
101
|
+
# if false, this is the root instance of Renderer
|
|
62
102
|
# if true, then this is not the last time Renderer will be run
|
|
63
103
|
# (e.g. this is sectionsplit)
|
|
104
|
+
# Isolated/incremental build: preserve unresolved cross-document
|
|
105
|
+
# references as stubs instead of resolving or stripping them, so each
|
|
106
|
+
# member compiles independently and a later reinflation pass relinks.
|
|
107
|
+
@preserve_unresolved = options[:preserve_unresolved]
|
|
108
|
+
# Durable content-addressed store for staged artefacts. Opt-in: a plain
|
|
109
|
+
# build sets neither :preserve_unresolved nor :artifact_store_dir, so it
|
|
110
|
+
# gets the no-op Null store and is unaffected. Staging (:preserve_unresolved)
|
|
111
|
+
# defaults the store directory to ArtifactStore::DEFAULT_DIRNAME when no
|
|
112
|
+
# :artifact_store_dir is given, so callers need not name it. (Reinflation
|
|
113
|
+
# reads stored stubs via the manifest, not this object, so it does not
|
|
114
|
+
# trigger the default -- that would only create an empty directory.)
|
|
115
|
+
@artifact_store =
|
|
116
|
+
if (dir = options[:artifact_store_dir]) || options[:preserve_unresolved]
|
|
117
|
+
ArtifactStore.new(dir || ArtifactStore::DEFAULT_DIRNAME)
|
|
118
|
+
else
|
|
119
|
+
NullArtifactStore.new
|
|
120
|
+
end
|
|
121
|
+
# Reinflation pass: the input is a stored stub-bearing Semantic XML that
|
|
122
|
+
# already has intra-doc xrefs and the document suffix applied, so run
|
|
123
|
+
# only the cross-document resolution, not the passes already done.
|
|
124
|
+
@reinflate = options[:reinflate]
|
|
125
|
+
# Cache retention: by default keep only each document's latest artefacts,
|
|
126
|
+
# pruning superseded content-hash versions on write; keep_cache retains
|
|
127
|
+
# every version (history / multiple states side by side).
|
|
128
|
+
@keep_cache = options[:keep_cache]
|
|
64
129
|
|
|
130
|
+
@coverpage = options[:coverpage] || collection.coverpage
|
|
131
|
+
@coverpage_pdf_portflio = options[:coverpage_pdf_portfolio] ||
|
|
132
|
+
collection.coverpage_pdf_portfolio || Util::taste2coverpage_pdf_portfolio(@flavor)
|
|
133
|
+
collection.directives = directives_normalise(collection.directives)
|
|
134
|
+
@directives = collection.directives
|
|
65
135
|
# list of files in the collection
|
|
66
136
|
@files = Metanorma::Collection::FileLookup.new(folder, self)
|
|
67
137
|
@files.add_section_split
|
|
@@ -69,8 +139,57 @@ module Metanorma
|
|
|
69
139
|
create_non_existing_directory(@outdir)
|
|
70
140
|
end
|
|
71
141
|
|
|
142
|
+
def directives_normalise(directives)
|
|
143
|
+
@coverpage_pdf_portflio and
|
|
144
|
+
directives = directives_normalise_coverpage_pdf_portfolio(directives)
|
|
145
|
+
directives_normalise_keystore_pdf_portfolio(directives)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def directives_normalise_coverpage_pdf_portfolio(directives)
|
|
149
|
+
directives_resolve_filepath(directives, "coverpage-pdf-portfolio",
|
|
150
|
+
@coverpage_pdf_portflio)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def directives_normalise_keystore_pdf_portfolio(directives)
|
|
154
|
+
f = directives.find { |d| d.key == "keystore-pdf-portfolio" }
|
|
155
|
+
f.nil? || f.value.nil? and return directives
|
|
156
|
+
directives_resolve_filepath(directives, "keystore-pdf-portfolio",
|
|
157
|
+
f.value)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def directives_resolve_filepath(directives, name, val)
|
|
161
|
+
abs = Pathname.new(val).absolute?
|
|
162
|
+
directives.reject! { |d| d.key == name }
|
|
163
|
+
val = Util::rel_path_resolve(@dirname, val)
|
|
164
|
+
val =
|
|
165
|
+
if abs then directives_host_materialise(val)
|
|
166
|
+
else Pathname.new(val).relative_path_from(Pathname.new(@outdir)).to_s
|
|
167
|
+
end
|
|
168
|
+
directives << ::Metanorma::Collection::Config::Directive
|
|
169
|
+
.new(key: name, value: val)
|
|
170
|
+
directives
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# An absolute directive value can point inside a gem payload (e.g. a
|
|
174
|
+
# taste's data/ file from Util.taste2coverpage_pdf_portfolio). A
|
|
175
|
+
# spawned child process that is not this Ruby (mn2pdf/java) may be
|
|
176
|
+
# unable to read such a path (a packaged virtual filesystem mount,
|
|
177
|
+
# e.g. tebako on Windows). Copy the file into the output folder —
|
|
178
|
+
# the child already reads the collection XML from there — and point
|
|
179
|
+
# the directive at the copy. A missing file keeps its value: the
|
|
180
|
+
# child reports that as it always has.
|
|
181
|
+
def directives_host_materialise(val)
|
|
182
|
+
File.exist?(val) or return val
|
|
183
|
+
dst = File.expand_path(File.join(@outdir, File.basename(val)))
|
|
184
|
+
dst == File.expand_path(val) and return val
|
|
185
|
+
FileUtils.mkdir_p(@outdir)
|
|
186
|
+
FileUtils.cp(val, dst)
|
|
187
|
+
dst
|
|
188
|
+
end
|
|
189
|
+
|
|
72
190
|
def flush_files
|
|
73
191
|
warn "\n\n\n\n\nDone: #{DateTime.now.strftime('%H:%M:%S')}"
|
|
192
|
+
warn "\nFiles to delete:\n"
|
|
74
193
|
warn @files.files_to_delete
|
|
75
194
|
@files.files_to_delete.each { |f| FileUtils.rm_f(f) }
|
|
76
195
|
@files_to_delete.each { |f| FileUtils.rm_f(f) }
|
|
@@ -85,61 +204,145 @@ module Metanorma
|
|
|
85
204
|
warn "\n\n\n\n\nRender Init: #{DateTime.now.strftime('%H:%M:%S')}"
|
|
86
205
|
cr = new(col, File.dirname(col.file), options)
|
|
87
206
|
cr.files
|
|
207
|
+
cr.rxl(options)
|
|
88
208
|
cr.concatenate(col, options)
|
|
89
209
|
options[:format]&.include?(:html) and cr.coverpage
|
|
90
210
|
cr.flush_files
|
|
91
211
|
cr
|
|
92
212
|
end
|
|
93
213
|
|
|
214
|
+
def rxl(options)
|
|
215
|
+
@bibdata or return
|
|
216
|
+
options[:site_generate] and options[:format] << :xml
|
|
217
|
+
options[:format].include?(:rxl) or return
|
|
218
|
+
File.open(File.join(@outdir, "collection.rxl"), "w:UTF-8") do |f|
|
|
219
|
+
f.write(@bibdata.to_xml(bibdata: true))
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
94
223
|
def concatenate(col, options)
|
|
95
224
|
warn "\n\n\n\n\nConcatenate: #{DateTime.now.strftime('%H:%M:%S')}"
|
|
96
|
-
(options
|
|
225
|
+
concatenate_presentation?(options) and
|
|
97
226
|
options[:format] << :presentation
|
|
98
227
|
concatenate_prep(col, options)
|
|
99
228
|
concatenate_outputs(options)
|
|
100
229
|
end
|
|
101
230
|
|
|
231
|
+
def concatenate_presentation?(options)
|
|
232
|
+
!(options[:format] & %i(pdf doc)).empty? ||
|
|
233
|
+
(@directives.detect { |d| d.key == "bilingual" } &&
|
|
234
|
+
options[:format].include?(:html))
|
|
235
|
+
end
|
|
236
|
+
|
|
102
237
|
def concatenate_prep(col, options)
|
|
238
|
+
warn options[:format]
|
|
103
239
|
%i(xml presentation).each do |e|
|
|
104
240
|
options[:format].include?(e) or next
|
|
105
241
|
ext = e == :presentation ? "presentation.xml" : e.to_s
|
|
106
242
|
File.open(File.join(@outdir, "collection.#{ext}"), "w:UTF-8") do |f|
|
|
107
243
|
b = concatenate1(col.clone, e).to_xml
|
|
108
|
-
e == :presentation and
|
|
109
|
-
b.sub!("<metanorma-collection>", "<metanorma-collection xmlns='http://metanorma.org'>")
|
|
110
|
-
# TODO BEING FORCED TO DO THAT BECAUSE SHALE IS NOT DEALING WITH DEFAULT NAMESPACES
|
|
244
|
+
e == :presentation and b = concatenate_presentation(b)
|
|
111
245
|
f.write(b)
|
|
112
246
|
end
|
|
113
247
|
end
|
|
114
248
|
end
|
|
115
249
|
|
|
250
|
+
def concatenate_presentation(xml)
|
|
251
|
+
@directives.detect { |d| d.key == "bilingual" } and
|
|
252
|
+
xml = Metanorma::Collection::Multilingual
|
|
253
|
+
.new({ align_cross_elements: %w(p note) }).to_bilingual(xml)
|
|
254
|
+
xml
|
|
255
|
+
end
|
|
256
|
+
|
|
116
257
|
def concatenate_outputs(options)
|
|
117
|
-
pres =
|
|
118
|
-
|
|
258
|
+
pres, compile_opts = concatenate_outputs_prep(options)
|
|
259
|
+
warn pp compile_opts
|
|
260
|
+
options[:format].include?(:pdf) and pdfconv(compile_opts).convert(pres)
|
|
261
|
+
options[:format].include?(:"pdf-portfolio") and
|
|
262
|
+
pdfconv(pdf_portfolio_mn2pdf_options)
|
|
263
|
+
.convert(pres, nil, nil,
|
|
264
|
+
File.join(@outdir, "collection.portfolio.pdf"))
|
|
119
265
|
options[:format].include?(:doc) and docconv_convert(pres)
|
|
266
|
+
bilingual_output(options, pres)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def concatenate_outputs_prep(_options)
|
|
270
|
+
pres = File.join(@outdir, "collection.presentation.xml")
|
|
271
|
+
fonts = extract_added_fonts(pres)
|
|
272
|
+
if fonts
|
|
273
|
+
# Install fonts before trying to locate them
|
|
274
|
+
font_options = @compile_options.merge({ fonts: fonts })
|
|
275
|
+
::Metanorma::Util::FontistHelper.install_fonts(@compile.processor,
|
|
276
|
+
font_options)
|
|
277
|
+
|
|
278
|
+
mn2pdf = {
|
|
279
|
+
font_manifest: ::Metanorma::Util::FontistHelper
|
|
280
|
+
.location_manifest(@compile.processor, { fonts: fonts }),
|
|
281
|
+
}
|
|
282
|
+
end
|
|
283
|
+
[pres, { fonts: fonts, mn2pdf: mn2pdf }.compact]
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def extract_added_fonts(pres)
|
|
287
|
+
File.exist?(pres) or return
|
|
288
|
+
xml = Nokogiri::XML(File.read(pres, encoding: "UTF-8"), &:huge)
|
|
289
|
+
x = xml.xpath("//*[local-name() = 'presentation-metadata']/" \
|
|
290
|
+
"*[local-name() = 'fonts']")
|
|
291
|
+
x.empty? and return
|
|
292
|
+
x.map(&:text).join(";")
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def pdf_portfolio_mn2pdf_options
|
|
296
|
+
f1 = @directives.find { |d| d.key == "keystore-pdf-portfolio" }&.value
|
|
297
|
+
f2 = @directives.find do |d|
|
|
298
|
+
d.key == "keystore-password-pdf-portfolio"
|
|
299
|
+
end&.value
|
|
300
|
+
{ "pdf-portfolio": "true", pdfkeystore: f1,
|
|
301
|
+
pdfkeystorepassword: f2 }.compact
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def bilingual_output(options, pres)
|
|
305
|
+
@directives.detect { |d| d.key == "bilingual" } &&
|
|
306
|
+
options[:format].include?(:html) and
|
|
307
|
+
Metanorma::Collection::Multilingual.new(
|
|
308
|
+
{ flavor: Util::taste2flavor(flavor).to_sym,
|
|
309
|
+
converter_options: PdfOptionsNode.new(Util::taste2flavor(flavor),
|
|
310
|
+
@compile_options),
|
|
311
|
+
outdir: @outdir },
|
|
312
|
+
).to_html(pres)
|
|
120
313
|
end
|
|
121
314
|
|
|
122
315
|
def concatenate1(out, ext)
|
|
123
|
-
out.directives << ::Metanorma::Collection::Config::Directive
|
|
316
|
+
out.directives << ::Metanorma::Collection::Config::Directive
|
|
317
|
+
.new(key: "documents-inline")
|
|
124
318
|
out.bibdatas.each_key do |ident|
|
|
125
319
|
id = @isodoc.docid_prefix(nil, ident.dup)
|
|
126
|
-
@files.get(id, :attachment)
|
|
320
|
+
@files.get(id, :attachment) and next
|
|
321
|
+
# A non-attachment document with no compiled output for THIS format
|
|
322
|
+
# cannot be inlined: leaving it in place serialises as a bibdata-only,
|
|
323
|
+
# namespace-less <metanorma> that mn2pdf silently drops. Skip loudly
|
|
324
|
+
# rather than crash on a nil path or corrupt the collection PDF.
|
|
325
|
+
# (Sectionsplit parents get a :presentation output upstream, so they
|
|
326
|
+
# inline whole for the PDF/presentation path.)
|
|
327
|
+
outputs = @files.get(id, :outputs)
|
|
328
|
+
# The registered path may exist without the file: a member whose
|
|
329
|
+
# rendering failed registers its intended outputs but writes
|
|
330
|
+
# nothing (#586), so check the disk, not just the registration.
|
|
331
|
+
if outputs.nil? || outputs[ext].nil? || !File.exist?(outputs[ext])
|
|
332
|
+
warn "[metanorma] collection document '#{id}' has no compiled " \
|
|
333
|
+
"#{ext} output to inline; skipping (its doc-container would " \
|
|
334
|
+
"otherwise be bibdata-only)."
|
|
335
|
+
next
|
|
336
|
+
end
|
|
127
337
|
out.documents[Util::key id] =
|
|
128
|
-
Metanorma::Collection::Document.raw_file(
|
|
129
|
-
:outputs)[ext])
|
|
338
|
+
Metanorma::Collection::Document.raw_file(outputs[ext])
|
|
130
339
|
end
|
|
131
340
|
out
|
|
132
341
|
end
|
|
133
342
|
|
|
134
|
-
# infer
|
|
135
|
-
def
|
|
136
|
-
|
|
137
|
-
dt = docid.downcase
|
|
138
|
-
elsif (docid = @xml.at("//bibdata/docidentifier")&.text)
|
|
139
|
-
dt = docid.sub(/\s.*$/, "").lowercase
|
|
140
|
-
else return "standoc"
|
|
141
|
-
end
|
|
142
|
-
@registry = Metanorma::Registry.instance
|
|
343
|
+
# TODO: infer flavor from publisher when available
|
|
344
|
+
def flavor
|
|
345
|
+
dt = @xml.at("//bibdata/ext/flavor")&.text or return "standoc"
|
|
143
346
|
@registry.alias(dt.to_sym)&.to_s || dt
|
|
144
347
|
end
|
|
145
348
|
|
|
@@ -147,9 +350,10 @@ module Metanorma
|
|
|
147
350
|
# collection manifest
|
|
148
351
|
def coverpage
|
|
149
352
|
@coverpage or return
|
|
353
|
+
@coverpage_path = Util::rel_path_resolve(@dirname, @coverpage)
|
|
150
354
|
warn "\n\n\n\n\nCoverpage: #{DateTime.now.strftime('%H:%M:%S')}"
|
|
151
355
|
File.open(File.join(@outdir, "index.html"), "w:UTF-8") do |f|
|
|
152
|
-
f.write @isodoc.populate_template(File.read(@
|
|
356
|
+
f.write @isodoc.populate_template(File.read(@coverpage_path))
|
|
153
357
|
end
|
|
154
358
|
end
|
|
155
359
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module Metanorma
|
|
2
|
+
class Collection
|
|
3
|
+
class Renderer
|
|
4
|
+
# Converts SVG images to data URIs for inline embedding
|
|
5
|
+
def svg_datauri(docxml, docid)
|
|
6
|
+
rel = @files.get(docid, :rel_path)
|
|
7
|
+
parent = @files.get(docid, :parentid) and
|
|
8
|
+
rel = @files.get(parent, :rel_path)
|
|
9
|
+
# if sectionsplit, use orig file dir
|
|
10
|
+
dir = File.join(@dirname, File.dirname(rel))
|
|
11
|
+
datauri_encode(docxml, dir)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Resolves SVG map references and processes SVG ID disambiguation.
|
|
15
|
+
#
|
|
16
|
+
# Delegates SVG manipulation to Vectory gem, which handles:
|
|
17
|
+
# 1. Document-level suffixing for cross-document ID disambiguation
|
|
18
|
+
# 2. Index-based suffixing for multiple svgmaps within one document
|
|
19
|
+
# 3. Link remapping and SVG extraction
|
|
20
|
+
#
|
|
21
|
+
# Metanorma's role: State requirements and coordinate the workflow.
|
|
22
|
+
# Vectory's role: Handle all SVG ID manipulation internally.
|
|
23
|
+
#
|
|
24
|
+
# @param docxml [Nokogiri::XML::Document] The document XML to process
|
|
25
|
+
# @param docid [String] Document identifier for retrieving suffix
|
|
26
|
+
# @param presxml [Boolean, nil] Whether this is presentation XML
|
|
27
|
+
def svgmap_resolve(docxml, docid, presxml)
|
|
28
|
+
ids, docxml, isodoc, tag = svgmap_resolve_prep(docxml, docid, presxml)
|
|
29
|
+
|
|
30
|
+
# Stage 1: Resolve EREF references to their targets
|
|
31
|
+
resolve_svgmap_erefs(docxml, tag, isodoc, ids, presxml)
|
|
32
|
+
|
|
33
|
+
# Stage 2: Normalize prefixes (Vectory expects eref, not fmt-eref)
|
|
34
|
+
normalize_svgmap_prefixes(docxml)
|
|
35
|
+
|
|
36
|
+
# Stage 3: Process with Vectory
|
|
37
|
+
# Pass document suffix to Vectory as id_suffix for proper SVG ID disambiguation.
|
|
38
|
+
# Vectory handles both id_suffix (document-level) and index suffix internally.
|
|
39
|
+
doc_suffix = @files.get(docid, :document_suffix)
|
|
40
|
+
Vectory::SvgMapping.new(docxml, "", id_suffix: doc_suffix).call
|
|
41
|
+
|
|
42
|
+
# Stage 4: Extract processed SVG content
|
|
43
|
+
extract_svgmap_content(docxml, isodoc)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Prepares document and context for svgmap resolution
|
|
47
|
+
def svgmap_resolve_prep(docxml, docid, presxml)
|
|
48
|
+
ids = @files.get(docid, :ids)
|
|
49
|
+
docxml = svg_unnest(svg_datauri(docxml, docid))
|
|
50
|
+
isodoc = IsoDoc::PresentationXMLConvert.new({})
|
|
51
|
+
isodoc.bibitem_lookup(docxml)
|
|
52
|
+
tag = presxml ? "fmt-eref" : "eref"
|
|
53
|
+
[ids, docxml, isodoc, tag]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Resolves EREF elements within svgmap to their actual link targets
|
|
59
|
+
def resolve_svgmap_erefs(docxml, tag, isodoc, ids, presxml)
|
|
60
|
+
docxml.xpath(ns("//svgmap//#{tag}")).each do |e|
|
|
61
|
+
svgmap_resolve_eref(e, isodoc, docxml, ids, presxml)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Converts fmt-eref/fmt-link/fmt-xref back to eref/link/xref
|
|
66
|
+
# Vectory expects non-prefixed element names for proper processing
|
|
67
|
+
def normalize_svgmap_prefixes(docxml)
|
|
68
|
+
svgmap_fmt_prefix_remove(docxml)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Extracts processed SVG content from svgmap elements
|
|
72
|
+
def extract_svgmap_content(docxml, isodoc)
|
|
73
|
+
docxml.xpath(ns("//svgmap")).each { |s| isodoc.svgmap_extract(s) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Converts Presentation XML fmt- prefix back to standard element names
|
|
77
|
+
# Vectory expects eref/link/xref, not fmt-eref/fmt-link/fmt-xref
|
|
78
|
+
def svgmap_fmt_prefix_remove(docxml)
|
|
79
|
+
docxml.xpath(ns("//svgmap/target")).each do |t|
|
|
80
|
+
n = t.at(ns(".//fmt-link | .//fmt-xref | .//fmt-eref")) or next
|
|
81
|
+
n.name = n.name.sub(/^fmt-/, "")
|
|
82
|
+
t.children = n
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Removes nested image elements within svgmap, flattening the structure
|
|
87
|
+
def svg_unnest(docxml)
|
|
88
|
+
docxml.xpath(ns("//svgmap//image[.//*[name() = 'image']]")).each do |i|
|
|
89
|
+
s = i.elements.detect { |e| e.name == "svg" } and
|
|
90
|
+
i.replace(s)
|
|
91
|
+
end
|
|
92
|
+
docxml
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Resolves a single EREF element to its target link
|
|
96
|
+
def svgmap_resolve_eref(eref, isodoc, _docxml, ids, presxml)
|
|
97
|
+
href = isodoc.eref_target(eref) or return
|
|
98
|
+
href = href[:link]
|
|
99
|
+
href == "##{eref['bibitemid']}" ||
|
|
100
|
+
(href =~ /^#/ && !ids[href.sub(/^#/, "")]) and return
|
|
101
|
+
eref["target"] = href.strip
|
|
102
|
+
eref.name = presxml ? "fmt-link" : "link"
|
|
103
|
+
eref.elements&.remove
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|