metanorma 2.1.4 → 2.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +23 -2
- data/Gemfile +14 -0
- data/README.adoc +1 -1
- data/lib/metanorma/collection/artifact_store.rb +142 -0
- data/lib/metanorma/collection/collection.rb +140 -131
- data/lib/metanorma/collection/config/bibdata.rb +1 -1
- data/lib/metanorma/collection/config/config.rb +50 -20
- data/lib/metanorma/collection/config/converters.rb +89 -31
- data/lib/metanorma/collection/config/doc_container.rb +28 -0
- data/lib/metanorma/collection/config/manifest.rb +30 -6
- data/lib/metanorma/collection/config/namespaces.rb +12 -0
- data/lib/metanorma/collection/document/document.rb +54 -15
- data/lib/metanorma/collection/filelookup/filelookup.rb +170 -74
- data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +62 -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 +27 -14
- data/lib/metanorma/collection/multilingual/multilingual.rb +1 -1
- data/lib/metanorma/collection/renderer/filelocation.rb +166 -0
- data/lib/metanorma/collection/renderer/fileparse.rb +60 -55
- data/lib/metanorma/collection/renderer/fileprocess.rb +184 -40
- data/lib/metanorma/collection/renderer/navigation.rb +20 -6
- data/lib/metanorma/collection/renderer/render_word.rb +8 -4
- data/lib/metanorma/collection/renderer/renderer.rb +202 -19
- data/lib/metanorma/collection/renderer/svg.rb +60 -5
- data/lib/metanorma/collection/renderer/utils.rb +76 -42
- data/lib/metanorma/collection/sectionsplit/collection.rb +14 -4
- data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +141 -61
- data/lib/metanorma/collection/util/disambig_files.rb +4 -5
- data/lib/metanorma/collection/util/util.rb +147 -23
- data/lib/metanorma/collection/xrefprocess/xrefprocess.rb +7 -7
- data/lib/metanorma/compile/assets/icc-boilerplate.adoc +41 -0
- data/lib/metanorma/compile/compile.rb +180 -165
- data/lib/metanorma/compile/compile_options.rb +155 -83
- data/lib/metanorma/compile/extract.rb +76 -56
- data/lib/metanorma/compile/flavor.rb +19 -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 +196 -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 +11 -20
- data/plans/collection-rendering-architecture-review.md +516 -0
- metadata +42 -122
- 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 -88
- 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/util/util.rb +0 -45
|
@@ -1,23 +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"
|
|
7
8
|
require_relative "../multilingual/multilingual"
|
|
8
9
|
require_relative "utils"
|
|
9
10
|
require_relative "render_word"
|
|
10
11
|
require_relative "navigation"
|
|
11
12
|
require_relative "svg"
|
|
13
|
+
require_relative "../log"
|
|
12
14
|
|
|
13
15
|
module Metanorma
|
|
14
16
|
class Collection
|
|
15
17
|
class Renderer
|
|
16
|
-
FORMATS = %i[html xml doc pdf].freeze
|
|
18
|
+
FORMATS = %i[html xml doc pdf pdf-portfolio].freeze
|
|
17
19
|
|
|
18
|
-
attr_accessor :isodoc, :
|
|
20
|
+
attr_accessor :isodoc, :isodoc_presxml
|
|
19
21
|
attr_reader :xml, :compile, :compile_options, :documents, :outdir,
|
|
20
|
-
: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
|
|
21
49
|
|
|
22
50
|
# This is only going to render the HTML collection
|
|
23
51
|
# @param xml [Metanorma::Collection] input XML collection
|
|
@@ -34,9 +62,8 @@ module Metanorma
|
|
|
34
62
|
def initialize(collection, folder, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
35
63
|
check_options options
|
|
36
64
|
@xml = Nokogiri::XML collection.to_xml # @xml is the collection manifest
|
|
37
|
-
@
|
|
38
|
-
@
|
|
39
|
-
@script = collection.bibdata.script.first || "Latn"
|
|
65
|
+
@lang = Array(collection.bibdata.language).first || "en"
|
|
66
|
+
@script = Array(collection.bibdata.script).first || "Latn"
|
|
40
67
|
@locale = @xml.at("//xmlns:bibdata/xmlns:locale")&.text
|
|
41
68
|
@registry = Metanorma::Registry.instance
|
|
42
69
|
@flavor = options[:flavor] || flavor
|
|
@@ -44,13 +71,17 @@ module Metanorma
|
|
|
44
71
|
@compile.load_flavor(@flavor)
|
|
45
72
|
|
|
46
73
|
# output processor for flavour
|
|
47
|
-
@isodoc = Util::isodoc_create(@flavor, @lang,
|
|
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)
|
|
48
79
|
@outdir = dir_name_cleanse(options[:output_folder])
|
|
49
|
-
@coverpage = options[:coverpage] || collection.coverpage
|
|
50
80
|
@format = ::Metanorma::Util.sort_extensions_execution(options[:format])
|
|
51
81
|
@compile_options = options[:compile] || {}
|
|
52
82
|
@compile_options[:install_fonts] = true if options[:install_fonts]
|
|
53
83
|
@log = options[:log]
|
|
84
|
+
@log&.add_msg(METANORMA_LOG_MESSAGES)
|
|
54
85
|
@bibdata = collection.bibdata
|
|
55
86
|
@documents = collection.documents
|
|
56
87
|
@bibdatas = collection.documents
|
|
@@ -62,11 +93,45 @@ module Metanorma
|
|
|
62
93
|
@final = collection.final
|
|
63
94
|
@c = HTMLEntities.new
|
|
64
95
|
@files_to_delete = []
|
|
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 = []
|
|
65
100
|
@nested = options[:nested]
|
|
66
101
|
# if false, this is the root instance of Renderer
|
|
67
102
|
# if true, then this is not the last time Renderer will be run
|
|
68
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]
|
|
69
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
|
|
70
135
|
# list of files in the collection
|
|
71
136
|
@files = Metanorma::Collection::FileLookup.new(folder, self)
|
|
72
137
|
@files.add_section_split
|
|
@@ -74,6 +139,54 @@ module Metanorma
|
|
|
74
139
|
create_non_existing_directory(@outdir)
|
|
75
140
|
end
|
|
76
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
|
+
|
|
77
190
|
def flush_files
|
|
78
191
|
warn "\n\n\n\n\nDone: #{DateTime.now.strftime('%H:%M:%S')}"
|
|
79
192
|
warn "\nFiles to delete:\n"
|
|
@@ -91,12 +204,22 @@ module Metanorma
|
|
|
91
204
|
warn "\n\n\n\n\nRender Init: #{DateTime.now.strftime('%H:%M:%S')}"
|
|
92
205
|
cr = new(col, File.dirname(col.file), options)
|
|
93
206
|
cr.files
|
|
207
|
+
cr.rxl(options)
|
|
94
208
|
cr.concatenate(col, options)
|
|
95
209
|
options[:format]&.include?(:html) and cr.coverpage
|
|
96
210
|
cr.flush_files
|
|
97
211
|
cr
|
|
98
212
|
end
|
|
99
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
|
+
|
|
100
223
|
def concatenate(col, options)
|
|
101
224
|
warn "\n\n\n\n\nConcatenate: #{DateTime.now.strftime('%H:%M:%S')}"
|
|
102
225
|
concatenate_presentation?(options) and
|
|
@@ -112,6 +235,7 @@ module Metanorma
|
|
|
112
235
|
end
|
|
113
236
|
|
|
114
237
|
def concatenate_prep(col, options)
|
|
238
|
+
warn options[:format]
|
|
115
239
|
%i(xml presentation).each do |e|
|
|
116
240
|
options[:format].include?(e) or next
|
|
117
241
|
ext = e == :presentation ? "presentation.xml" : e.to_s
|
|
@@ -124,8 +248,6 @@ module Metanorma
|
|
|
124
248
|
end
|
|
125
249
|
|
|
126
250
|
def concatenate_presentation(xml)
|
|
127
|
-
xml.sub!("<metanorma-collection>", "<metanorma-collection xmlns='http://metanorma.org'>")
|
|
128
|
-
# TODO BEING FORCED TO DO THAT BECAUSE SHALE IS NOT DEALING WITH DEFAULT NAMESPACES
|
|
129
251
|
@directives.detect { |d| d.key == "bilingual" } and
|
|
130
252
|
xml = Metanorma::Collection::Multilingual
|
|
131
253
|
.new({ align_cross_elements: %w(p note) }).to_bilingual(xml)
|
|
@@ -133,14 +255,59 @@ module Metanorma
|
|
|
133
255
|
end
|
|
134
256
|
|
|
135
257
|
def concatenate_outputs(options)
|
|
136
|
-
pres =
|
|
137
|
-
|
|
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"))
|
|
138
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)
|
|
139
305
|
@directives.detect { |d| d.key == "bilingual" } &&
|
|
140
306
|
options[:format].include?(:html) and
|
|
141
307
|
Metanorma::Collection::Multilingual.new(
|
|
142
|
-
{ flavor: flavor.to_sym,
|
|
143
|
-
converter_options: PdfOptionsNode.new(flavor
|
|
308
|
+
{ flavor: Util::taste2flavor(flavor).to_sym,
|
|
309
|
+
converter_options: PdfOptionsNode.new(Util::taste2flavor(flavor),
|
|
310
|
+
@compile_options),
|
|
144
311
|
outdir: @outdir },
|
|
145
312
|
).to_html(pres)
|
|
146
313
|
end
|
|
@@ -150,10 +317,25 @@ module Metanorma
|
|
|
150
317
|
.new(key: "documents-inline")
|
|
151
318
|
out.bibdatas.each_key do |ident|
|
|
152
319
|
id = @isodoc.docid_prefix(nil, ident.dup)
|
|
153
|
-
@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
|
|
154
337
|
out.documents[Util::key id] =
|
|
155
|
-
Metanorma::Collection::Document
|
|
156
|
-
.raw_file(@files.get(id, :outputs)[ext])
|
|
338
|
+
Metanorma::Collection::Document.raw_file(outputs[ext])
|
|
157
339
|
end
|
|
158
340
|
out
|
|
159
341
|
end
|
|
@@ -168,9 +350,10 @@ module Metanorma
|
|
|
168
350
|
# collection manifest
|
|
169
351
|
def coverpage
|
|
170
352
|
@coverpage or return
|
|
353
|
+
@coverpage_path = Util::rel_path_resolve(@dirname, @coverpage)
|
|
171
354
|
warn "\n\n\n\n\nCoverpage: #{DateTime.now.strftime('%H:%M:%S')}"
|
|
172
355
|
File.open(File.join(@outdir, "index.html"), "w:UTF-8") do |f|
|
|
173
|
-
f.write @isodoc.populate_template(File.read(@
|
|
356
|
+
f.write @isodoc.populate_template(File.read(@coverpage_path))
|
|
174
357
|
end
|
|
175
358
|
end
|
|
176
359
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Metanorma
|
|
2
2
|
class Collection
|
|
3
3
|
class Renderer
|
|
4
|
+
# Converts SVG images to data URIs for inline embedding
|
|
4
5
|
def svg_datauri(docxml, docid)
|
|
5
6
|
rel = @files.get(docid, :rel_path)
|
|
6
7
|
parent = @files.get(docid, :parentid) and
|
|
@@ -10,26 +11,79 @@ module Metanorma
|
|
|
10
11
|
datauri_encode(docxml, dir)
|
|
11
12
|
end
|
|
12
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
|
|
13
27
|
def svgmap_resolve(docxml, docid, presxml)
|
|
14
|
-
|
|
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)
|
|
15
48
|
ids = @files.get(docid, :ids)
|
|
16
49
|
docxml = svg_unnest(svg_datauri(docxml, docid))
|
|
17
50
|
isodoc = IsoDoc::PresentationXMLConvert.new({})
|
|
18
51
|
isodoc.bibitem_lookup(docxml)
|
|
19
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)
|
|
20
60
|
docxml.xpath(ns("//svgmap//#{tag}")).each do |e|
|
|
21
|
-
#require "debug"; binding.b
|
|
22
61
|
svgmap_resolve_eref(e, isodoc, docxml, ids, presxml)
|
|
23
62
|
end
|
|
24
|
-
|
|
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|
|
|
25
80
|
n = t.at(ns(".//fmt-link | .//fmt-xref | .//fmt-eref")) or next
|
|
26
81
|
n.name = n.name.sub(/^fmt-/, "")
|
|
27
82
|
t.children = n
|
|
28
83
|
end
|
|
29
|
-
Vectory::SvgMapping.new(docxml, "").call
|
|
30
|
-
docxml.xpath(ns("//svgmap")).each { |s| isodoc.svgmap_extract(s) }
|
|
31
84
|
end
|
|
32
85
|
|
|
86
|
+
# Removes nested image elements within svgmap, flattening the structure
|
|
33
87
|
def svg_unnest(docxml)
|
|
34
88
|
docxml.xpath(ns("//svgmap//image[.//*[name() = 'image']]")).each do |i|
|
|
35
89
|
s = i.elements.detect { |e| e.name == "svg" } and
|
|
@@ -38,6 +92,7 @@ module Metanorma
|
|
|
38
92
|
docxml
|
|
39
93
|
end
|
|
40
94
|
|
|
95
|
+
# Resolves a single EREF element to its target link
|
|
41
96
|
def svgmap_resolve_eref(eref, isodoc, _docxml, ids, presxml)
|
|
42
97
|
href = isodoc.eref_target(eref) or return
|
|
43
98
|
href = href[:link]
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "marcel"
|
|
2
|
+
|
|
1
3
|
module Metanorma
|
|
2
4
|
class Collection
|
|
3
5
|
class Renderer
|
|
@@ -5,21 +7,25 @@ module Metanorma
|
|
|
5
7
|
path = Pathname.new(name)
|
|
6
8
|
clean_regex = /[<>:"|?*\p{Zs}]/
|
|
7
9
|
fallback_sym = "_"
|
|
8
|
-
return name.gsub(clean_regex, fallback_sym)
|
|
9
|
-
|
|
10
|
+
path.absolute? or return name.gsub(clean_regex, fallback_sym)
|
|
10
11
|
File.join(path.dirname,
|
|
11
12
|
path.basename.to_s.gsub(clean_regex, fallback_sym))
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def dup_bibitem(docid, bib)
|
|
15
|
-
newbib = @files.get(docid, :bibdata)
|
|
16
|
+
newbib = deep_detached_clone(@files.get(docid, :bibdata))
|
|
16
17
|
newbib.name = "bibitem"
|
|
17
18
|
newbib["hidden"] = "true"
|
|
18
19
|
newbib&.at("./*[local-name() = 'ext']")&.remove
|
|
19
20
|
newbib["id"] = bib["id"]
|
|
21
|
+
bib["anchor"] and newbib["anchor"] = bib["anchor"]
|
|
20
22
|
newbib
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
def deep_detached_clone(node)
|
|
26
|
+
Nokogiri::XML(node.to_xml).root
|
|
27
|
+
end
|
|
28
|
+
|
|
23
29
|
def get_bibitem_docid(bib, identifier)
|
|
24
30
|
docid =
|
|
25
31
|
bib.at(ns("./docidentifier[@type = 'metanorma-collection']")) ||
|
|
@@ -33,17 +39,10 @@ module Metanorma
|
|
|
33
39
|
end
|
|
34
40
|
end
|
|
35
41
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def new_hidden_ref(xmldoc)
|
|
44
|
-
ins = xmldoc.at(ns("bibliography")) or
|
|
45
|
-
xmldoc.root << "<bibliography/>" and ins = xmldoc.at(ns("bibliography"))
|
|
46
|
-
ins.at(ns("./referenced[@hidden = 'true']")) or
|
|
42
|
+
def new_hidden_ref(xml)
|
|
43
|
+
ins = xml.at(ns("bibliography")) or
|
|
44
|
+
xml.root << "<bibliography/>" and ins = xml.at(ns("bibliography"))
|
|
45
|
+
ins.at(ns("./references[@hidden = 'true']")) or
|
|
47
46
|
ins.add_child("<references hidden='true' normative='false'/>").first
|
|
48
47
|
end
|
|
49
48
|
|
|
@@ -72,16 +71,35 @@ module Metanorma
|
|
|
72
71
|
"#{val}</docidentifier>"
|
|
73
72
|
end
|
|
74
73
|
|
|
75
|
-
def add_hidden_bibliography(
|
|
76
|
-
ins = new_hidden_ref(
|
|
74
|
+
def add_hidden_bibliography(xml, refs, current_id = nil)
|
|
75
|
+
ins = new_hidden_ref(xml)
|
|
76
|
+
current_html = if current_id
|
|
77
|
+
@files.get(current_id,
|
|
78
|
+
:outputs)&.dig(:html)
|
|
79
|
+
else
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
77
82
|
refs.each do |k, v|
|
|
78
83
|
url = @files.url(v, {})
|
|
84
|
+
url = make_relative_path(current_html, url) if current_html
|
|
79
85
|
ins << <<~XML
|
|
80
|
-
<bibitem id="#{k}">#{docid_xml(v)}<uri type='citation'>#{url}</uri></bibitem>
|
|
86
|
+
<bibitem id="#{k}" anchor="#{k}">#{docid_xml(v)}<uri type='citation'>#{url}</uri></bibitem>
|
|
81
87
|
XML
|
|
82
88
|
end
|
|
83
89
|
end
|
|
84
90
|
|
|
91
|
+
def make_relative_path(from_file, to_file)
|
|
92
|
+
return to_file if to_file.nil? || to_file.to_s.empty?
|
|
93
|
+
return to_file if to_file.start_with?("http://", "https://", "#")
|
|
94
|
+
|
|
95
|
+
from_dir = File.dirname(from_file)
|
|
96
|
+
return to_file if from_dir == "."
|
|
97
|
+
|
|
98
|
+
from_path = Pathname.new(from_dir)
|
|
99
|
+
to_path = Pathname.new(to_file)
|
|
100
|
+
to_path.relative_path_from(from_path).to_s
|
|
101
|
+
end
|
|
102
|
+
|
|
85
103
|
private
|
|
86
104
|
|
|
87
105
|
def docid_prefix(docid)
|
|
@@ -108,63 +126,79 @@ module Metanorma
|
|
|
108
126
|
# @raise [ArgumentError]
|
|
109
127
|
def check_options(options)
|
|
110
128
|
(options[:format].is_a?(Array) && (FORMATS & options[:format]).any?) or
|
|
111
|
-
raise ArgumentError,
|
|
129
|
+
raise ArgumentError,
|
|
130
|
+
"Need to specify formats (xml,html,pdf,pdf-portfolio,doc)"
|
|
112
131
|
end
|
|
113
132
|
|
|
114
|
-
def pdfconv
|
|
115
|
-
flavor =
|
|
133
|
+
def pdfconv(added_options)
|
|
134
|
+
flavor, taste_opts, x = pdfconv_prep
|
|
135
|
+
fonts = [taste_opts[:fonts], added_options[:fonts]].compact.flatten
|
|
136
|
+
.join(";")
|
|
137
|
+
opts = @compile_options.merge(added_options.merge(taste_opts))
|
|
138
|
+
opts[:fonts] = fonts
|
|
139
|
+
x.converter.pdf_converter(PdfOptionsNode.new(flavor, opts))
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def pdfconv_prep
|
|
143
|
+
flavor = Util::taste2flavor(@flavor).to_sym
|
|
144
|
+
taste_opts = Util::taste2isodoc_attrs(@flavor, :pdf)
|
|
116
145
|
x = Asciidoctor.load nil, backend: flavor
|
|
117
|
-
|
|
118
|
-
@compile_options))
|
|
146
|
+
[flavor, taste_opts, x]
|
|
119
147
|
end
|
|
120
148
|
|
|
121
149
|
def fail_update_bibitem(docid, identifier)
|
|
122
150
|
error = "[metanorma] Cannot find crossreference to document #{docid} " \
|
|
123
151
|
"in document #{identifier}."
|
|
124
|
-
@log&.add("
|
|
152
|
+
@log&.add("METANORMA_2", nil, params: [docid, identifier])
|
|
125
153
|
::Metanorma::Util.log(error, :warning)
|
|
126
154
|
end
|
|
127
155
|
|
|
128
156
|
def datauri_encode(docxml, directory)
|
|
129
157
|
docxml.xpath(ns("//image")).each do |i|
|
|
130
|
-
read_in_if_svg(i, directory.sub(%r{(?<!=/)$}, "/"))
|
|
158
|
+
read_in_if_svg(i, directory.sub(%r{(?<!=/)$}, "/"))
|
|
131
159
|
end
|
|
132
160
|
docxml.xpath(ns("//image")).each do |i| # rubocop:disable Style/CombinableLoops
|
|
133
|
-
i["src"] && !i["src"].empty? or next
|
|
161
|
+
(i["src"] && !i["src"].empty?) or next
|
|
134
162
|
i["src"] = Vectory::Utils::datauri(i["src"], directory)
|
|
135
163
|
end
|
|
136
164
|
docxml
|
|
137
165
|
end
|
|
138
166
|
|
|
139
167
|
def read_in_if_svg(img, localdir)
|
|
140
|
-
img["src"] or return
|
|
141
|
-
img.elements.map(&:name).include?("svg")
|
|
168
|
+
img["src"] or return
|
|
169
|
+
if img.elements.map(&:name).include?("svg")
|
|
170
|
+
img["src"] = nil
|
|
171
|
+
return
|
|
172
|
+
end
|
|
142
173
|
path = Vectory::Utils.svgmap_rewrite0_path(img["src"], localdir)
|
|
143
|
-
svg = svg_in_path?(path) or return
|
|
174
|
+
svg = svg_in_path?(path) or return
|
|
144
175
|
img.children = (Nokogiri::XML(svg).root)
|
|
176
|
+
img["src"] = nil
|
|
145
177
|
true
|
|
146
178
|
end
|
|
147
179
|
|
|
148
180
|
def svg_in_path?(path)
|
|
149
181
|
File.file?(path) or return false
|
|
150
|
-
|
|
151
|
-
|
|
182
|
+
type = Marcel::MimeType.for(name: path) or return false
|
|
183
|
+
type == "image/svg+xml" or return false
|
|
152
184
|
svg = File.read(path, encoding: "utf-8") or return false
|
|
153
185
|
svg
|
|
154
186
|
end
|
|
155
187
|
|
|
156
188
|
class PdfOptionsNode
|
|
157
189
|
def initialize(flavor, options)
|
|
158
|
-
p = Metanorma::Registry.instance.find_processor(flavor)
|
|
190
|
+
p = Metanorma::Registry.instance.find_processor(Util::taste2flavor(flavor))
|
|
159
191
|
if ::Metanorma::Util::FontistHelper.has_custom_fonts?(p, options, {})
|
|
160
192
|
@fonts_manifest =
|
|
161
193
|
::Metanorma::Util::FontistHelper.location_manifest(p, options)
|
|
162
194
|
end
|
|
195
|
+
@options = options
|
|
163
196
|
end
|
|
164
197
|
|
|
165
198
|
def attr(key)
|
|
166
199
|
if key == "fonts-manifest" && @fonts_manifest
|
|
167
200
|
@fonts_manifest
|
|
201
|
+
else @options[key.to_sym]
|
|
168
202
|
end
|
|
169
203
|
end
|
|
170
204
|
end
|
|
@@ -175,11 +209,13 @@ module Metanorma
|
|
|
175
209
|
def isodoc_populate
|
|
176
210
|
@isodoc.info(@xml, nil)
|
|
177
211
|
{ navigation: indexfile(@manifest), nav_object: index_object(@manifest),
|
|
178
|
-
bibdata: @bibdata.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
212
|
+
bibdata: YAML.safe_load(@bibdata.to_yaml,
|
|
213
|
+
permitted_classes: [Date, Symbol]),
|
|
214
|
+
docrefs: liquid_docrefs(@manifest),
|
|
215
|
+
"prefatory-content": isodoc_builder(@xml.at("//xmlns:prefatory-content")),
|
|
216
|
+
"final-content": isodoc_builder(@xml.at("//xmlns:final-content")),
|
|
217
|
+
doctitle: Array(@bibdata.title).first&.content,
|
|
218
|
+
docnumber: Array(@bibdata.docidentifier).first&.content }.each do |k, v|
|
|
183
219
|
v and @isodoc.meta.set(k, v)
|
|
184
220
|
end
|
|
185
221
|
end
|
|
@@ -187,16 +223,14 @@ module Metanorma
|
|
|
187
223
|
def isodoc_builder(node)
|
|
188
224
|
node or return
|
|
189
225
|
|
|
190
|
-
# Kludging namespace back in because of Shale brain damage
|
|
191
|
-
doc = Nokogiri::XML(node.to_xml.sub(">", " xmlns='http://www.metanorma.org'>"))
|
|
192
226
|
Nokogiri::HTML::Builder.new(encoding: "UTF-8") do |b|
|
|
193
227
|
b.div do |div|
|
|
194
|
-
|
|
228
|
+
node.children.each { |n| @isodoc.parse(n, div) }
|
|
195
229
|
end
|
|
196
230
|
end.doc.root.to_html
|
|
197
231
|
end
|
|
198
232
|
|
|
199
|
-
def eref2link(docxml,
|
|
233
|
+
def eref2link(docxml, _presxml)
|
|
200
234
|
isodoc = IsoDoc::PresentationXMLConvert.new({})
|
|
201
235
|
isodoc.bibitem_lookup(docxml)
|
|
202
236
|
isodoc.eref2link(docxml)
|
|
@@ -205,10 +239,10 @@ module Metanorma
|
|
|
205
239
|
def error_anchor(erefs, docid)
|
|
206
240
|
erefs.each do |e|
|
|
207
241
|
msg = "<strong>** Unresolved reference to document #{docid} " \
|
|
208
|
-
|
|
242
|
+
"from eref</strong>"
|
|
209
243
|
e << msg
|
|
210
244
|
strip_eref(e)
|
|
211
|
-
@log&.add("
|
|
245
|
+
@log&.add("METANORMA_3", e, params: [docid])
|
|
212
246
|
end
|
|
213
247
|
end
|
|
214
248
|
|