metanorma 2.4.3 → 2.5.2
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 +13 -1
- data/lib/metanorma/collection/artifact_store.rb +142 -0
- data/lib/metanorma/collection/collection.rb +20 -1
- data/lib/metanorma/collection/filelookup/filelookup.rb +7 -21
- data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +9 -14
- data/lib/metanorma/collection/filelookup/utils.rb +7 -48
- data/lib/metanorma/collection/log.rb +7 -0
- data/lib/metanorma/collection/manifest/manifest.rb +6 -0
- data/lib/metanorma/collection/renderer/filelocation.rb +7 -3
- data/lib/metanorma/collection/renderer/fileparse.rb +32 -5
- data/lib/metanorma/collection/renderer/fileprocess.rb +133 -16
- data/lib/metanorma/collection/renderer/renderer.rb +75 -20
- data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +41 -10
- data/lib/metanorma/collection/util/util.rb +16 -0
- data/lib/metanorma/compile/render.rb +24 -9
- data/lib/metanorma/version.rb +1 -1
- data/metanorma.gemspec +1 -1
- data/plans/collection-rendering-architecture-review.md +516 -0
- metadata +7 -10
- data/.hound.yml +0 -5
- data/lib/metanorma/collection/filelookup/base.rb +0 -43
|
@@ -4,6 +4,8 @@ require "isodoc"
|
|
|
4
4
|
require "metanorma-utils"
|
|
5
5
|
require_relative "fileparse"
|
|
6
6
|
require_relative "filelocation"
|
|
7
|
+
require_relative "../artifact_store"
|
|
8
|
+
require "json"
|
|
7
9
|
|
|
8
10
|
module Metanorma
|
|
9
11
|
class Collection
|
|
@@ -16,9 +18,61 @@ module Metanorma
|
|
|
16
18
|
opts = compile_options_base(identifier)
|
|
17
19
|
.merge(compile_options_update(identifier))
|
|
18
20
|
|
|
21
|
+
err_count = @compile.errors.size
|
|
19
22
|
@compile.compile file, opts
|
|
20
23
|
@files.set(identifier, :outputs, {})
|
|
21
24
|
file_compile_formats(filename, identifier, opts)
|
|
25
|
+
file_compile_verify(identifier, @compile.errors[err_count..] || [])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# A member whose rendering raised produces no output file, but the
|
|
29
|
+
# collection build used to continue, exit 0, and link the absent
|
|
30
|
+
# document from index.html -- silent data loss (#586). Two
|
|
31
|
+
# complementary checks close that hole. (1) Compile pools rescued
|
|
32
|
+
# error messages in @compile.errors for its caller to surface; the
|
|
33
|
+
# standalone CLI does, but the collection pipeline never read the
|
|
34
|
+
# pool. The pool is shared across members and never reset, so the
|
|
35
|
+
# slice appended during this member's compile is this member's
|
|
36
|
+
# errors -- sound while members compile sequentially (the current
|
|
37
|
+
# design; @compile's own format-level parallelism is joined before
|
|
38
|
+
# compile returns). (2) Independently of whether anything was
|
|
39
|
+
# pooled, verify the outputs registered for the member exist on
|
|
40
|
+
# disk. Failures are reported here and accumulated; the collection
|
|
41
|
+
# renders every remaining member before Collection#render aborts,
|
|
42
|
+
# so one run reports all failed members.
|
|
43
|
+
def file_compile_verify(identifier, errors)
|
|
44
|
+
# Section-split parts (marked with :parentid) register format
|
|
45
|
+
# slots they never produce -- their :xml slot in particular is
|
|
46
|
+
# never written, and under filename renaming it aliases to the
|
|
47
|
+
# parent's name -- so verifying them is a false positive. #586
|
|
48
|
+
# was scoped to the non-sectionsplit path; parts are excluded
|
|
49
|
+
# like their parents (which return early above).
|
|
50
|
+
@files.get(identifier, :parentid) and return
|
|
51
|
+
missing = (@files.get(identifier, :outputs) || {})
|
|
52
|
+
.reject { |_fmt, path| File.exist?(path) }
|
|
53
|
+
errors.empty? && missing.empty? and return
|
|
54
|
+
file_compile_failure_log(identifier, errors, missing)
|
|
55
|
+
msg = file_compile_failure_msg(identifier, errors, missing)
|
|
56
|
+
::Metanorma::Util.log("[metanorma] Error: #{msg}", :error)
|
|
57
|
+
fatal_errors << msg
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def file_compile_failure_log(identifier, errors, missing)
|
|
61
|
+
missing.each_value do |path|
|
|
62
|
+
@log&.add("METANORMA_4", nil, params: [identifier, path])
|
|
63
|
+
end
|
|
64
|
+
errors.each do |e|
|
|
65
|
+
@log&.add("METANORMA_5", nil, params: [identifier, e])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def file_compile_failure_msg(identifier, errors, missing)
|
|
70
|
+
ret = []
|
|
71
|
+
missing.empty? or
|
|
72
|
+
ret << "expected output not generated: " \
|
|
73
|
+
"#{missing.values.join(', ')}"
|
|
74
|
+
ret += errors
|
|
75
|
+
"Failed to render collection member #{identifier}: #{ret.join('; ')}"
|
|
22
76
|
end
|
|
23
77
|
|
|
24
78
|
def compile_options_base(identifier)
|
|
@@ -67,8 +121,11 @@ module Metanorma
|
|
|
67
121
|
if @files.get(ident, :attachment) then copy_file_to_dest(ident)
|
|
68
122
|
else
|
|
69
123
|
file, fname = @files.targetfile_id(ident, read: true)
|
|
124
|
+
@preserve_unresolved && member_stored?(ident, file) and next
|
|
70
125
|
warn "\n\n\n\n\nProcess #{fname}: #{DateTime.now.strftime('%H:%M:%S')}"
|
|
71
126
|
collection_xml = update_xrefs(file, ident, internal_refs)
|
|
127
|
+
@preserve_unresolved and
|
|
128
|
+
store_member_artefacts(ident, file, collection_xml)
|
|
72
129
|
# Strip .xml or .html extension, but NOT section numbers like .0
|
|
73
130
|
fname_base = File.basename(fname)
|
|
74
131
|
collection_filename = fname_base
|
|
@@ -83,6 +140,31 @@ module Metanorma
|
|
|
83
140
|
end
|
|
84
141
|
end
|
|
85
142
|
|
|
143
|
+
# Persist an isolated member's stub-bearing Semantic XML and its anchor
|
|
144
|
+
# contribution to the content-addressed store, so a later reinflation pass
|
|
145
|
+
# can relink the stubs and a re-run can resume without recompiling.
|
|
146
|
+
# Within-campaign content key for a member. (Cross-build soundness needs
|
|
147
|
+
# the full input closure -- source + includes + templates + schemas +
|
|
148
|
+
# versions; see the plan gotcha. Here the source bytes + tool version
|
|
149
|
+
# suffice because inputs are frozen across a single build campaign.)
|
|
150
|
+
def member_content_hash(source)
|
|
151
|
+
ArtifactStore.content_hash(source, ::Metanorma::VERSION)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Resume predicate: is this member already compiled and stored for its
|
|
155
|
+
# current input content? If so the isolated compile can skip it.
|
|
156
|
+
def member_stored?(ident, source)
|
|
157
|
+
@artifact_store.key?(ident, member_content_hash(source), :semantic)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def store_member_artefacts(ident, source, semantic_xml)
|
|
161
|
+
hash = member_content_hash(source)
|
|
162
|
+
@artifact_store.write(ident, hash, :semantic, semantic_xml)
|
|
163
|
+
@artifact_store.write(ident, hash, :anchors,
|
|
164
|
+
JSON.generate(@files.get(ident, :anchors) || {}))
|
|
165
|
+
@keep_cache or @artifact_store.prune_superseded(ident, hash)
|
|
166
|
+
end
|
|
167
|
+
|
|
86
168
|
# gather internal bibitem references
|
|
87
169
|
def gather_internal_refs
|
|
88
170
|
@files.keys.each_with_object({}) do |i, refs|
|
|
@@ -168,7 +250,7 @@ module Metanorma
|
|
|
168
250
|
end
|
|
169
251
|
|
|
170
252
|
def update_bibitem(bib, identifier)
|
|
171
|
-
newbib, url = update_bibitem_prep(bib, identifier)
|
|
253
|
+
newbib, url, attachment = update_bibitem_prep(bib, identifier)
|
|
172
254
|
newbib or return
|
|
173
255
|
dest = begin
|
|
174
256
|
newbib.at("./docidentifier") || newbib.at(ns("./docidentifier"))
|
|
@@ -176,28 +258,63 @@ module Metanorma
|
|
|
176
258
|
nil
|
|
177
259
|
end
|
|
178
260
|
dest or dest = newbib.elements[-1]
|
|
261
|
+
# An attachment carries both a <uri type="attachment"> (which drives
|
|
262
|
+
# isodoc's fmt-link attachment="true") and the citation uri; the
|
|
263
|
+
# attachment uri goes first. metanorma/iso-10303#208.
|
|
264
|
+
attachment and dest.previous = "<uri type='attachment'>#{url}</uri>"
|
|
179
265
|
dest.previous = "<uri type='citation'>#{url}</uri>"
|
|
180
266
|
bib.replace(newbib)
|
|
181
267
|
end
|
|
182
268
|
|
|
183
269
|
def update_bibitem_prep(bib, identifier)
|
|
184
|
-
docid = get_bibitem_docid(bib, identifier) or return [nil, nil]
|
|
270
|
+
docid = get_bibitem_docid(bib, identifier) or return [nil, nil, nil]
|
|
185
271
|
newbib = dup_bibitem(docid, bib)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# otherwise convert :out_path to HTML (before compilation)
|
|
190
|
-
current_html = @files.get(identifier, :outputs)&.dig(:html)
|
|
191
|
-
if !current_html && (out_path = @files.get(identifier, :out_path))
|
|
192
|
-
# Convert .xml to .html, following same logic as ref_file_xml2html
|
|
193
|
-
current_html = if out_path.end_with?(".xml")
|
|
194
|
-
out_path.sub(/\.xml$/, ".html")
|
|
195
|
-
else
|
|
196
|
-
"#{out_path}.html"
|
|
197
|
-
end
|
|
198
|
-
end
|
|
272
|
+
attachment = @files.get(docid, :attachment)
|
|
273
|
+
url = @files.url(docid, relative: true, doc: !attachment)
|
|
274
|
+
current_html = referencing_html_location(identifier, attachment)
|
|
199
275
|
url = make_relative_path(current_html, url) if current_html
|
|
200
|
-
[newbib, url]
|
|
276
|
+
[newbib, url, attachment]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Location of the HTML that will carry the cross-reference to the target,
|
|
280
|
+
# used as the base for relativising the attachment/citation URL.
|
|
281
|
+
#
|
|
282
|
+
# Normally this is the referencing document's own output (:outputs[:html]
|
|
283
|
+
# after compilation, else :out_path converted to .html before it).
|
|
284
|
+
#
|
|
285
|
+
# But when the referencing document is sectionsplit, its content is not
|
|
286
|
+
# emitted at its own out_path: it is split into section files written at
|
|
287
|
+
# the sectionsplit output location (the collection root, unless a
|
|
288
|
+
# sectionsplit_filename directory is configured). Relativising an
|
|
289
|
+
# attachment URL against the unsplit out_path then overshoots -- the
|
|
290
|
+
# ../../ climbs above the split file and the attachment link breaks
|
|
291
|
+
# (https://github.com/metanorma/iso-10303/issues/208). So for attachment
|
|
292
|
+
# targets of a sectionsplit document, base the relative path on the
|
|
293
|
+
# sectionsplit output directory instead.
|
|
294
|
+
def referencing_html_location(identifier, attachment)
|
|
295
|
+
attachment && @files.get(identifier, :sectionsplit) and
|
|
296
|
+
return sectionsplit_ref_html(identifier)
|
|
297
|
+
document_ref_html(identifier)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# The split section files sit at the sectionsplit output directory: the
|
|
301
|
+
# collection root, or the directory of a configured sectionsplit_filename.
|
|
302
|
+
def sectionsplit_ref_html(identifier)
|
|
303
|
+
d = @files.preserve_directory_structure?(identifier)
|
|
304
|
+
d ? File.join(File.dirname(d), "x.html") : "x.html"
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Use :outputs[:html] if available (after compilation), otherwise convert
|
|
308
|
+
# :out_path to HTML (before compilation), following ref_file_xml2html.
|
|
309
|
+
def document_ref_html(identifier)
|
|
310
|
+
@files.get(identifier, :outputs)&.dig(:html) ||
|
|
311
|
+
out_path_to_html(@files.get(identifier, :out_path))
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def out_path_to_html(out_path)
|
|
315
|
+
out_path or return nil
|
|
316
|
+
out_path.end_with?(".xml") and return out_path.sub(/\.xml$/, ".html")
|
|
317
|
+
"#{out_path}.html"
|
|
201
318
|
end
|
|
202
319
|
end
|
|
203
320
|
end
|
|
@@ -17,9 +17,35 @@ module Metanorma
|
|
|
17
17
|
class Renderer
|
|
18
18
|
FORMATS = %i[html xml doc pdf pdf-portfolio].freeze
|
|
19
19
|
|
|
20
|
-
attr_accessor :isodoc, :isodoc_presxml
|
|
20
|
+
attr_accessor :isodoc, :isodoc_presxml
|
|
21
21
|
attr_reader :xml, :compile, :compile_options, :documents, :outdir,
|
|
22
|
-
: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
|
|
23
49
|
|
|
24
50
|
# This is only going to render the HTML collection
|
|
25
51
|
# @param xml [Metanorma::Collection] input XML collection
|
|
@@ -67,10 +93,39 @@ module Metanorma
|
|
|
67
93
|
@final = collection.final
|
|
68
94
|
@c = HTMLEntities.new
|
|
69
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 = []
|
|
70
100
|
@nested = options[:nested]
|
|
71
101
|
# if false, this is the root instance of Renderer
|
|
72
102
|
# if true, then this is not the last time Renderer will be run
|
|
73
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]
|
|
74
129
|
|
|
75
130
|
@coverpage = options[:coverpage] || collection.coverpage
|
|
76
131
|
@coverpage_pdf_portflio = options[:coverpage_pdf_portfolio] ||
|
|
@@ -90,21 +145,6 @@ module Metanorma
|
|
|
90
145
|
directives_normalise_keystore_pdf_portfolio(directives)
|
|
91
146
|
end
|
|
92
147
|
|
|
93
|
-
# KILL
|
|
94
|
-
def directives_normalise_coverpage_pdf_portfolio(directives)
|
|
95
|
-
directives.reject! { |d| d.key == "coverpage-pdf-portfolio" }
|
|
96
|
-
absolute_coverpage_pdf_portflio = @coverpage_pdf_portflio &&
|
|
97
|
-
Pathname.new(@coverpage_pdf_portflio).absolute?
|
|
98
|
-
@coverpage_pdf_portflio =
|
|
99
|
-
Util::rel_path_resolve(@dirname, @coverpage_pdf_portflio)
|
|
100
|
-
absolute_coverpage_pdf_portflio or
|
|
101
|
-
@coverpage_pdf_portflio = Pathname.new(@coverpage_pdf_portflio)
|
|
102
|
-
.relative_path_from(Pathname.new(@outdir)).to_s
|
|
103
|
-
directives << ::Metanorma::Collection::Config::Directive
|
|
104
|
-
.new(key: "coverpage-pdf-portfolio", value: @coverpage_pdf_portflio)
|
|
105
|
-
directives
|
|
106
|
-
end
|
|
107
|
-
|
|
108
148
|
def directives_normalise_coverpage_pdf_portfolio(directives)
|
|
109
149
|
directives_resolve_filepath(directives, "coverpage-pdf-portfolio",
|
|
110
150
|
@coverpage_pdf_portflio)
|
|
@@ -258,10 +298,25 @@ module Metanorma
|
|
|
258
298
|
.new(key: "documents-inline")
|
|
259
299
|
out.bibdatas.each_key do |ident|
|
|
260
300
|
id = @isodoc.docid_prefix(nil, ident.dup)
|
|
261
|
-
@files.get(id, :attachment)
|
|
301
|
+
@files.get(id, :attachment) and next
|
|
302
|
+
# A non-attachment document with no compiled output for THIS format
|
|
303
|
+
# cannot be inlined: leaving it in place serialises as a bibdata-only,
|
|
304
|
+
# namespace-less <metanorma> that mn2pdf silently drops. Skip loudly
|
|
305
|
+
# rather than crash on a nil path or corrupt the collection PDF.
|
|
306
|
+
# (Sectionsplit parents get a :presentation output upstream, so they
|
|
307
|
+
# inline whole for the PDF/presentation path.)
|
|
308
|
+
outputs = @files.get(id, :outputs)
|
|
309
|
+
# The registered path may exist without the file: a member whose
|
|
310
|
+
# rendering failed registers its intended outputs but writes
|
|
311
|
+
# nothing (#586), so check the disk, not just the registration.
|
|
312
|
+
if outputs.nil? || outputs[ext].nil? || !File.exist?(outputs[ext])
|
|
313
|
+
warn "[metanorma] collection document '#{id}' has no compiled " \
|
|
314
|
+
"#{ext} output to inline; skipping (its doc-container would " \
|
|
315
|
+
"otherwise be bibdata-only)."
|
|
316
|
+
next
|
|
317
|
+
end
|
|
262
318
|
out.documents[Util::key id] =
|
|
263
|
-
Metanorma::Collection::Document
|
|
264
|
-
.raw_file(@files.get(id, :outputs)[ext])
|
|
319
|
+
Metanorma::Collection::Document.raw_file(outputs[ext])
|
|
265
320
|
end
|
|
266
321
|
out
|
|
267
322
|
end
|
|
@@ -7,8 +7,24 @@ require "concurrent-ruby"
|
|
|
7
7
|
|
|
8
8
|
module Metanorma
|
|
9
9
|
class Collection
|
|
10
|
+
# Sectionsplit is a collection render nested inside a collection render:
|
|
11
|
+
# a document is split into one output file per section, and those files are
|
|
12
|
+
# then rendered as their own (sub-)collection.
|
|
13
|
+
#
|
|
14
|
+
# Two output-location invariants matter (both load-bearing -- see the
|
|
15
|
+
# collection architecture review plan and metanorma/iso-10303#208):
|
|
16
|
+
#
|
|
17
|
+
# * XML section files are always written FLAT to the split directory
|
|
18
|
+
# (basename only). HTML output may instead carry a directory taken from
|
|
19
|
+
# sectionsplit_filename, reattached only at HTML compile time
|
|
20
|
+
# (preserve_directory_structure?).
|
|
21
|
+
# * A sectionsplit document's content is emitted at the sectionsplit output
|
|
22
|
+
# location (the collection root, unless sectionsplit_filename sets a
|
|
23
|
+
# directory), NOT at the document's own :out_path. Anything relativising a
|
|
24
|
+
# URL for such a document (e.g. attachment/citation links) must base it on
|
|
25
|
+
# the split location, or the ../../ overshoots.
|
|
10
26
|
class Sectionsplit
|
|
11
|
-
attr_accessor :filecache, :key
|
|
27
|
+
attr_accessor :filecache, :key, :whole_presentation_file
|
|
12
28
|
|
|
13
29
|
def initialize(opts)
|
|
14
30
|
@input_filename = opts[:input]
|
|
@@ -40,6 +56,13 @@ module Metanorma
|
|
|
40
56
|
# Input XML is Semantic XML
|
|
41
57
|
def sectionsplit
|
|
42
58
|
xml = sectionsplit_prep(File.read(@input_filename), @base, @dir)
|
|
59
|
+
# Retain the whole (un-split) Presentation XML. sectionsplit is an
|
|
60
|
+
# HTML-only mechanism, but the collection PDF/presentation path must
|
|
61
|
+
# inline each document intact; without this the sectionsplit parent
|
|
62
|
+
# reaches concatenation with no presentation output and degrades to a
|
|
63
|
+
# bibdata-only, namespace-less <metanorma> stub that mn2pdf drops.
|
|
64
|
+
# https://github.com/metanorma/iso-10303/issues/208
|
|
65
|
+
@whole_presentation_file = write_whole_presentation(xml)
|
|
43
66
|
@key = Metanorma::Collection::XrefProcess::xref_preprocess(xml, @isodoc)
|
|
44
67
|
empty = empty_doc(xml)
|
|
45
68
|
empty1 = empty_attachments(empty)
|
|
@@ -66,11 +89,11 @@ module Metanorma
|
|
|
66
89
|
|
|
67
90
|
def sectionsplit2(xml, empty, chunks, parentnode, opt)
|
|
68
91
|
@pool.post do
|
|
69
|
-
output_filename =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
92
|
+
output_filename = Util.substitute_filename_pattern(
|
|
93
|
+
@sectionsplit_filename,
|
|
94
|
+
document_num: @parent_idx, basename: File.basename(@base, ".*"),
|
|
95
|
+
basename_legacy: @base, sectionsplit_num: opt[:idx]
|
|
96
|
+
)
|
|
74
97
|
warn "Sectionsplit: #{output_filename}"
|
|
75
98
|
a = sectionfile(xml, empty, output_filename, chunks,
|
|
76
99
|
parentnode)
|
|
@@ -108,6 +131,16 @@ module Metanorma
|
|
|
108
131
|
r
|
|
109
132
|
end
|
|
110
133
|
|
|
134
|
+
# Persist the whole, svgmap-restored Presentation XML (the in-memory doc,
|
|
135
|
+
# not the on-disk tmp which still carries the internal svgmap1 marker) so
|
|
136
|
+
# the collection PDF path can inline the document whole. Written flat to
|
|
137
|
+
# the split output directory, where the per-section files also live.
|
|
138
|
+
def write_whole_presentation(xml)
|
|
139
|
+
fname = File.join(@splitdir, "#{@base}.whole.presentation.xml")
|
|
140
|
+
File.write(fname, xml.to_xml, encoding: "UTF-8")
|
|
141
|
+
fname
|
|
142
|
+
end
|
|
143
|
+
|
|
111
144
|
def sectionsplit_preprocess_semxml(file, filename)
|
|
112
145
|
xml = Nokogiri::XML(file, &:huge)
|
|
113
146
|
type = xml.root["flavor"]
|
|
@@ -120,10 +153,8 @@ module Metanorma
|
|
|
120
153
|
|
|
121
154
|
def sectionsplit_update_xrefs(xml)
|
|
122
155
|
if c = @fileslookup&.parent
|
|
123
|
-
|
|
124
|
-
c.
|
|
125
|
-
c.update_xrefs(xml, @ident, {})
|
|
126
|
-
c.nested = n
|
|
156
|
+
# nested mode so unresolved erefs are not deleted
|
|
157
|
+
c.with_nested { c.update_xrefs(xml, @ident, {}) }
|
|
127
158
|
xml.xpath("//xmlns:svgmap").each { |x| x.name = "svgmap1" }
|
|
128
159
|
# do not process svgmap until after files are split
|
|
129
160
|
end
|
|
@@ -81,6 +81,22 @@ module Metanorma
|
|
|
81
81
|
@c.decode(ident).gsub(/(\p{Zs})+/, " ")
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
# Substitute special strings in filename patterns. Single source for
|
|
85
|
+
# both FileLookup#substitute_filename_pattern and sectionsplit.
|
|
86
|
+
# @param pattern [String] filename pattern with placeholders
|
|
87
|
+
# @param options [Hash] substitution values:
|
|
88
|
+
# :document_num, :basename, :basename_legacy, :sectionsplit_num
|
|
89
|
+
def substitute_filename_pattern(pattern, options = {})
|
|
90
|
+
pattern or return pattern
|
|
91
|
+
subs = { "{document-num}" => options[:document_num],
|
|
92
|
+
"{basename}" => options[:basename],
|
|
93
|
+
"{basename_legacy}" => options[:basename_legacy],
|
|
94
|
+
"{sectionsplit-num}" => options[:sectionsplit_num] }
|
|
95
|
+
subs.each_with_object(pattern.dup) do |(token, val), result|
|
|
96
|
+
val and result.gsub!(token, val.to_s)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
84
100
|
def taste2flavor(taste)
|
|
85
101
|
tastes = Metanorma::TasteRegister.instance.aliases
|
|
86
102
|
tastes[taste.to_sym] and taste = tastes[taste.to_sym]
|
|
@@ -135,8 +135,7 @@ module Metanorma
|
|
|
135
135
|
output_paths[:out], ext, isodoc_options)
|
|
136
136
|
wrap_html(options, output_paths[:ext], output_paths[:out])
|
|
137
137
|
rescue StandardError => e
|
|
138
|
-
|
|
139
|
-
isodoc_error_process(e, strict, false)
|
|
138
|
+
isodoc_error_process(e, strict_error?(ext, isodoc_options), false)
|
|
140
139
|
end
|
|
141
140
|
|
|
142
141
|
# Process format directly from semantic XML
|
|
@@ -145,8 +144,7 @@ module Metanorma
|
|
|
145
144
|
ext, isodoc_options)
|
|
146
145
|
true # Return as Thread equivalent
|
|
147
146
|
rescue StandardError => e
|
|
148
|
-
|
|
149
|
-
isodoc_error_process(e, strict, true)
|
|
147
|
+
isodoc_error_process(e, strict_error?(ext, isodoc_options), true)
|
|
150
148
|
ext != :presentation
|
|
151
149
|
end
|
|
152
150
|
|
|
@@ -168,12 +166,29 @@ module Metanorma
|
|
|
168
166
|
|
|
169
167
|
private
|
|
170
168
|
|
|
169
|
+
# A presentation failure is always strict: every downstream format
|
|
170
|
+
# needs the presentation XML. The :strict option arrives as a
|
|
171
|
+
# boolean from the CLI but as a string from document attributes, so
|
|
172
|
+
# accept both (the two rescue sites previously each checked only
|
|
173
|
+
# one form).
|
|
174
|
+
def strict_error?(ext, isodoc_options)
|
|
175
|
+
ext == :presentation ||
|
|
176
|
+
[true, "true"].include?(isodoc_options[:strict])
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# On the strict path the message is pooled into @errors for the
|
|
180
|
+
# caller to surface (the standalone CLI reports the pool and
|
|
181
|
+
# aborts; the collection renderer attributes it to the failed
|
|
182
|
+
# member, #586). The class+message line is also printed here in all
|
|
183
|
+
# cases, so it sits adjacent to its backtrace in the build log: a
|
|
184
|
+
# pooled message surfaces only at end of run, and a bare backtrace
|
|
185
|
+
# mid-log is undiagnosable. The exception class is retained because
|
|
186
|
+
# for errors like Encoding::UndefinedConversionError it carries
|
|
187
|
+
# half the diagnosis.
|
|
171
188
|
def isodoc_error_process(err, strict, must_abort)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
puts err.message
|
|
176
|
-
end
|
|
189
|
+
msg = "#{err.class}: #{err.message}"
|
|
190
|
+
strict || err.message.include?("Fatal:") and @errors << msg
|
|
191
|
+
puts msg
|
|
177
192
|
puts err.backtrace.join("\n")
|
|
178
193
|
must_abort and 1
|
|
179
194
|
end
|
data/lib/metanorma/version.rb
CHANGED
data/metanorma.gemspec
CHANGED
|
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
spec.bindir = "bin"
|
|
23
23
|
# spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
24
24
|
spec.require_paths = ["lib"]
|
|
25
|
-
spec.required_ruby_version = ">= 3.
|
|
25
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.3.0")
|
|
26
26
|
|
|
27
27
|
spec.add_runtime_dependency "asciidoctor"
|
|
28
28
|
spec.add_runtime_dependency "concurrent-ruby"
|