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,7 +1,9 @@
|
|
|
1
1
|
require "isodoc"
|
|
2
2
|
require "htmlentities"
|
|
3
3
|
require "metanorma-utils"
|
|
4
|
+
require "marcel"
|
|
4
5
|
require_relative "filelookup_sectionsplit"
|
|
6
|
+
require_relative "utils"
|
|
5
7
|
|
|
6
8
|
module Metanorma
|
|
7
9
|
class Collection
|
|
@@ -18,38 +20,54 @@ module Metanorma
|
|
|
18
20
|
@parent = parent
|
|
19
21
|
@xml = parent.xml
|
|
20
22
|
@isodoc = parent.isodoc
|
|
23
|
+
@isodoc_presxml = parent.isodoc_presxml
|
|
21
24
|
@path = path
|
|
22
25
|
@compile = parent.compile
|
|
23
26
|
@documents = parent.documents
|
|
24
27
|
@files_to_delete = []
|
|
25
28
|
@disambig = Util::DisambigFiles.new
|
|
26
29
|
@manifest = parent.manifest
|
|
27
|
-
read_files(@manifest.entry)
|
|
30
|
+
read_files(@manifest.entry, parent.manifest)
|
|
28
31
|
end
|
|
29
32
|
|
|
30
|
-
def read_files(entries)
|
|
33
|
+
def read_files(entries, parent, idx = 0)
|
|
31
34
|
Array(entries).each do |e|
|
|
32
|
-
e
|
|
33
|
-
|
|
35
|
+
derive_format(e, parent)
|
|
36
|
+
if e.file
|
|
37
|
+
read_file(e, idx)
|
|
38
|
+
idx += 1
|
|
39
|
+
end
|
|
40
|
+
idx = read_files(e.entry, e, idx)
|
|
34
41
|
end
|
|
42
|
+
idx
|
|
35
43
|
end
|
|
36
44
|
|
|
37
|
-
def
|
|
45
|
+
def derive_format(entry, parent)
|
|
46
|
+
entry.attachment and return
|
|
47
|
+
if Array(entry.format).empty?
|
|
48
|
+
parent_fmt = Array(parent.format)
|
|
49
|
+
entry.format = parent_fmt.empty? ? %w(xml presentation html) : parent_fmt.dup
|
|
50
|
+
end
|
|
51
|
+
entry.format |= ["xml", "presentation"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def read_file(manifest, idx)
|
|
38
55
|
i, k = read_file_idents(manifest)
|
|
39
|
-
entry = file_entry(manifest, k) or return
|
|
56
|
+
entry = file_entry(manifest, k, idx) or return
|
|
40
57
|
bibdata_process(entry, i)
|
|
41
58
|
bibitem_process(entry)
|
|
42
|
-
@files[
|
|
59
|
+
@files[entry_key(i)] = entry
|
|
43
60
|
end
|
|
44
61
|
|
|
45
62
|
def read_file_idents(manifest)
|
|
46
63
|
id = manifest.identifier
|
|
47
|
-
sanitised_id =
|
|
64
|
+
sanitised_id =
|
|
65
|
+
entry_key(@isodoc.docid_prefix("", manifest.identifier.dup))
|
|
48
66
|
# if manifest.bibdata and # NO, DO NOT FISH FOR THE GENUINE IDENTIFIER IN BIBDATA
|
|
49
67
|
# d = manifest.bibdata.docidentifier.detect { |x| x.primary } ||
|
|
50
68
|
# manifest.bibdata.docidentifier.first
|
|
51
69
|
# k = d.id
|
|
52
|
-
# i =
|
|
70
|
+
# i = entry_key(@isodoc.docid_prefix(d.type, d.id.dup))
|
|
53
71
|
# end
|
|
54
72
|
[id, sanitised_id]
|
|
55
73
|
end
|
|
@@ -66,12 +84,6 @@ module Metanorma
|
|
|
66
84
|
end
|
|
67
85
|
end
|
|
68
86
|
|
|
69
|
-
def anchors_lookup(anchors)
|
|
70
|
-
anchors.values.each_with_object({}) do |v, m|
|
|
71
|
-
v.each_value { |v1| m[v1] = true }
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
87
|
def bibdata_extract(xml)
|
|
76
88
|
anchors = read_anchors(xml)
|
|
77
89
|
{ anchors: anchors, anchors_lookup: anchors_lookup(anchors),
|
|
@@ -92,29 +104,117 @@ module Metanorma
|
|
|
92
104
|
# out_path is the destination file address, with any references outside
|
|
93
105
|
# the working directory (../../...) truncated, and based on relative path
|
|
94
106
|
# identifier is the id with only spaces, no nbsp
|
|
95
|
-
|
|
107
|
+
# idx is the index of the document in the manifest
|
|
108
|
+
def file_entry(ref, identifier, idx)
|
|
96
109
|
ref.file or return
|
|
97
110
|
abs = @documents[Util::key identifier].file
|
|
111
|
+
# For sectionsplit outputs from YAML manifest, we need to compute the full path
|
|
112
|
+
# by combining sectionsplit_filename directory with ref.file basename
|
|
113
|
+
sso = ref.respond_to?(:sectionsplit_output) && ref.sectionsplit_output
|
|
114
|
+
out_path, rel_path = file_entry_paths(ref, idx, sso)
|
|
98
115
|
ret = if ref.file
|
|
99
|
-
{ type: "fileref", ref: abs, rel_path:
|
|
100
|
-
out_path:
|
|
116
|
+
{ type: "fileref", ref: abs, rel_path: rel_path, url: ref.url,
|
|
117
|
+
out_path: out_path, idx: idx,
|
|
118
|
+
output_filename: ref.output_filename,
|
|
119
|
+
sectionsplit_filename: ref.sectionsplit_filename,
|
|
120
|
+
pdffile: ref.pdffile, format: ref.format&.map(&:to_sym) }
|
|
121
|
+
.compact
|
|
101
122
|
else { type: "id", ref: ref.id }
|
|
102
123
|
end
|
|
103
124
|
file_entry_copy(ref, ret)
|
|
104
125
|
ret.compact
|
|
105
126
|
end
|
|
106
127
|
|
|
128
|
+
|
|
129
|
+
# ref is the absolute source file address
|
|
130
|
+
# rel_path is the relative source file address, relative to the YAML location
|
|
131
|
+
# out_path is the destination file address, with any references outside
|
|
132
|
+
# the working directory (../../...) truncated, and based on relative path
|
|
133
|
+
# identifier is the id with only spaces, no nbsp
|
|
134
|
+
# extract_opts are the compilation options extracted as document attributes
|
|
135
|
+
def file_entry_struct(ref, abs)
|
|
136
|
+
adoc = abs.sub(/\.xml$/, ".adoc")
|
|
137
|
+
if adoc.end_with?(".adoc") && File.exist?(adoc)
|
|
138
|
+
opts = Metanorma::Input::Asciidoc.new.extract_options(File.read(adoc))
|
|
139
|
+
end
|
|
140
|
+
{ type: "fileref", ref: abs, rel_path: ref.file, url: ref.url,
|
|
141
|
+
out_path: output_file_path(ref), pdffile: ref.pdffile,
|
|
142
|
+
format: ref.format&.map(&:to_sym), extract_opts: opts }.compact
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def file_entry_paths(ref, idx, sso)
|
|
146
|
+
base = File.basename(ref.file, ".xml")
|
|
147
|
+
if sso && ref.respond_to?(:sectionsplit_filename) &&
|
|
148
|
+
ref.sectionsplit_filename
|
|
149
|
+
# Extract directory from sectionsplit_filename
|
|
150
|
+
dir = File.dirname(ref.sectionsplit_filename)
|
|
151
|
+
if dir == "." # No directory in pattern
|
|
152
|
+
[output_file_path(ref, idx), ref.file]
|
|
153
|
+
else # Pattern has directory, prepend it
|
|
154
|
+
full_path = File.join(dir, base)
|
|
155
|
+
[full_path, "#{full_path}.xml"]
|
|
156
|
+
end
|
|
157
|
+
else [output_file_path(ref, idx), ref.file]
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Substitute special strings in filename patterns.
|
|
162
|
+
# See Util.substitute_filename_pattern.
|
|
163
|
+
def substitute_filename_pattern(pattern, options = {})
|
|
164
|
+
Util.substitute_filename_pattern(pattern, options)
|
|
165
|
+
end
|
|
166
|
+
|
|
107
167
|
# TODO make the output file location reflect source location universally,
|
|
108
168
|
# not just for attachments: no File.basename
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
169
|
+
#
|
|
170
|
+
# For files with custom directory structure, construct path with directory
|
|
171
|
+
# For files with output_filename, use that (with substitutions)
|
|
172
|
+
# For others, use basename of ref.file
|
|
173
|
+
def output_file_path(ref, idx)
|
|
174
|
+
has_custom_dir, file_has_dir, params = output_file_path_prep(ref, idx)
|
|
175
|
+
# Apply sectionsplit_filename directory structure if:
|
|
176
|
+
# 1. File has sectionsplit enabled (parent document being split), OR
|
|
177
|
+
# 2. File is a sectionsplit output (from collection or single-file sectionsplit)
|
|
178
|
+
# Regular files that inherit sectionsplit_filename from collection level
|
|
179
|
+
# but are not sectionsplit outputs should NOT use it
|
|
180
|
+
is_sectionsplit_output = ref.respond_to?(:sectionsplit_output) && ref.sectionsplit_output
|
|
181
|
+
use_sectionsplit_dir = ref.sectionsplit_filename && has_custom_dir &&
|
|
182
|
+
(ref.sectionsplit || is_sectionsplit_output || file_has_dir)
|
|
183
|
+
f = if use_sectionsplit_dir
|
|
184
|
+
# For sectionsplit outputs, return just the basename
|
|
185
|
+
# The directory will be applied during file_compile_format
|
|
186
|
+
# via preserve_directory_structure?
|
|
187
|
+
File.basename(ref.file)
|
|
188
|
+
elsif ref.output_filename
|
|
189
|
+
substitute_filename_pattern(ref.output_filename, **params)
|
|
190
|
+
elsif file_has_dir
|
|
191
|
+
ref.file # Preserve directory structure already in ref.file
|
|
192
|
+
elsif ref.attachment
|
|
193
|
+
ref.file
|
|
194
|
+
else File.basename(ref.file)
|
|
195
|
+
end
|
|
196
|
+
ret = @disambig.source2dest_filename(f, preserve_dirs: ref.attachment)
|
|
197
|
+
warn ret
|
|
198
|
+
ret
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def output_file_path_prep(ref, idx)
|
|
202
|
+
b = File.basename(ref.file)
|
|
203
|
+
b_no_ext = File.basename(ref.file, ".*")
|
|
204
|
+
# Check for sectionsplit_filename (for both parent and split output files)
|
|
205
|
+
# or output_filename
|
|
206
|
+
custom_filename = ref.sectionsplit_filename || ref.output_filename
|
|
207
|
+
has_custom_dir = custom_filename && File.dirname(custom_filename) != "."
|
|
208
|
+
# Also check if ref.file itself contains a directory
|
|
209
|
+
file_has_dir = File.dirname(ref.file) != "."
|
|
210
|
+
params = { document_num: idx, basename: b_no_ext, basename_legacy: b }
|
|
211
|
+
[has_custom_dir, file_has_dir, params]
|
|
113
212
|
end
|
|
114
213
|
|
|
115
214
|
def file_entry_copy(ref, ret)
|
|
116
215
|
%w(attachment sectionsplit index presentation-xml url
|
|
117
|
-
bare-after-first
|
|
216
|
+
bare-after-first output_filename sectionsplit_filename
|
|
217
|
+
sectionsplit_output).each do |s|
|
|
118
218
|
ref.respond_to?(s.to_sym) and
|
|
119
219
|
ret[s.delete("-").to_sym] = ref.send(s)
|
|
120
220
|
end
|
|
@@ -122,23 +222,16 @@ module Metanorma
|
|
|
122
222
|
|
|
123
223
|
def add_document_suffix(identifier, doc)
|
|
124
224
|
document_suffix = Metanorma::Utils::to_ncname(identifier)
|
|
125
|
-
|
|
225
|
+
ids = doc.xpath("./@id | .//@id").map(&:value)
|
|
226
|
+
Util::anchor_id_attributes.each do |(tag_name, attr_name)|
|
|
126
227
|
Util::add_suffix_to_attrs(doc, document_suffix, tag_name, attr_name,
|
|
127
228
|
@isodoc)
|
|
128
229
|
end
|
|
129
|
-
url_in_css_styles(doc, document_suffix)
|
|
230
|
+
Util::url_in_css_styles(doc, ids, document_suffix)
|
|
130
231
|
doc.root["document_suffix"] ||= ""
|
|
131
232
|
doc.root["document_suffix"] += document_suffix
|
|
132
233
|
end
|
|
133
234
|
|
|
134
|
-
# update relative URLs, url(#...), in CSS in @style attrs (including SVG)
|
|
135
|
-
def url_in_css_styles(doc, document_suffix)
|
|
136
|
-
doc.xpath("//*[@style]").each do |s|
|
|
137
|
-
s["style"] = s["style"]
|
|
138
|
-
.gsub(%r{url\(#([^()]+)\)}, "url(#\\1_#{document_suffix})")
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
235
|
# return citation url for file
|
|
143
236
|
# @param doc [Boolean] I am a Metanorma document,
|
|
144
237
|
# so my URL should end with html or pdf or whatever
|
|
@@ -147,13 +240,6 @@ module Metanorma
|
|
|
147
240
|
data[:url] || targetfile(data, options)[1]
|
|
148
241
|
end
|
|
149
242
|
|
|
150
|
-
# are references to the file to be linked to a file in the collection,
|
|
151
|
-
# or externally? Determines whether file suffix anchors are to be used
|
|
152
|
-
def url?(ident)
|
|
153
|
-
data = get(ident) or return false
|
|
154
|
-
data[:url]
|
|
155
|
-
end
|
|
156
|
-
|
|
157
243
|
# return file contents + output filename for each file in the collection,
|
|
158
244
|
# given a docref entry
|
|
159
245
|
# @param data [Hash] docref entry
|
|
@@ -167,7 +253,7 @@ module Metanorma
|
|
|
167
253
|
options = { read: false, doc: true, relative: false }.merge(options)
|
|
168
254
|
path = options[:relative] ? data[:rel_path] : data[:ref]
|
|
169
255
|
if data[:type] == "fileref"
|
|
170
|
-
ref_file path, data
|
|
256
|
+
ref_file path, data, options[:read], options[:doc]
|
|
171
257
|
else
|
|
172
258
|
xml_file data[:id], options[:read]
|
|
173
259
|
end
|
|
@@ -177,13 +263,40 @@ module Metanorma
|
|
|
177
263
|
targetfile(get(ident), options)
|
|
178
264
|
end
|
|
179
265
|
|
|
180
|
-
def ref_file(ref,
|
|
266
|
+
def ref_file(ref, data, read, doc)
|
|
181
267
|
file = File.read(ref, encoding: "utf-8") if read
|
|
182
|
-
|
|
183
|
-
|
|
268
|
+
# Use the actual output path from :outputs if available (set after compilation)
|
|
269
|
+
# Otherwise fall back to :out_path (set at initialization)
|
|
270
|
+
filename = if doc && data[:outputs] && data[:outputs][:html]
|
|
271
|
+
data[:outputs][:html].sub(
|
|
272
|
+
%r{^#{Regexp.escape(@parent.outdir)}/}, ""
|
|
273
|
+
)
|
|
274
|
+
else
|
|
275
|
+
data[:out_path].dup
|
|
276
|
+
end
|
|
277
|
+
if doc && !data[:outputs]
|
|
278
|
+
filename = ref_file_xml2html(filename)
|
|
279
|
+
end
|
|
184
280
|
[file, filename]
|
|
185
281
|
end
|
|
186
282
|
|
|
283
|
+
# Check if file has a recognized MIME type (other than XML)
|
|
284
|
+
# If so, don't append .html (e.g., .svg, .png, .jpg, etc.)
|
|
285
|
+
# Only process if it doesn't have a recognized non-XML extension
|
|
286
|
+
# If filename ends in .xml, replace with .html
|
|
287
|
+
# Otherwise (including sectionsplit files like "file.xml.0" or
|
|
288
|
+
# custom titles), append .html
|
|
289
|
+
def ref_file_xml2html(filename)
|
|
290
|
+
unless Util::mime_file_recognised?(filename) &&
|
|
291
|
+
!filename.end_with?(".xml")
|
|
292
|
+
filename = if filename.end_with?(".xml")
|
|
293
|
+
filename.sub(/\.xml$/, ".html")
|
|
294
|
+
else "#{filename}.html"
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
filename
|
|
298
|
+
end
|
|
299
|
+
|
|
187
300
|
def xml_file(id, read)
|
|
188
301
|
file = @xml.at(ns("//doc-container[@id = '#{id}']")).to_xml if read
|
|
189
302
|
filename = "#{id}.html"
|
|
@@ -221,41 +334,24 @@ module Metanorma
|
|
|
221
334
|
ret = {}
|
|
222
335
|
xml.traverse do |x|
|
|
223
336
|
x.text? and next
|
|
224
|
-
/^semantic__/.match?(x.name) and next
|
|
225
337
|
x["id"] and ret[x["id"]] = true
|
|
226
338
|
end
|
|
227
339
|
ret
|
|
228
340
|
end
|
|
229
341
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def set(ident, attr, value)
|
|
246
|
-
@files[key(ident)][attr] = value
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def each
|
|
250
|
-
@files.each
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def each_with_index
|
|
254
|
-
@files.each_with_index
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def ns(xpath)
|
|
258
|
-
@isodoc.ns(xpath)
|
|
342
|
+
# Check if we should preserve directory structure for an identifier
|
|
343
|
+
# Returns the custom filename if directory structure should be preserved,
|
|
344
|
+
# nil otherwise
|
|
345
|
+
def preserve_directory_structure?(ident)
|
|
346
|
+
ret = if get(ident, :sectionsplit_output)
|
|
347
|
+
# For sectionsplit outputs, use rel_path which has the directory
|
|
348
|
+
get(ident, :rel_path) || get(ident, :out_path)
|
|
349
|
+
elsif get(ident, :sectionsplit)
|
|
350
|
+
get(ident, :sectionsplit_filename)
|
|
351
|
+
else get(ident, :output_filename)
|
|
352
|
+
end
|
|
353
|
+
# Return the custom filename only if it contains a directory
|
|
354
|
+
ret && File.dirname(ret) != "." ? ret : nil
|
|
259
355
|
end
|
|
260
356
|
end
|
|
261
357
|
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require_relative "../sectionsplit/sectionsplit"
|
|
2
|
-
# require "concurrent-ruby"
|
|
3
2
|
|
|
4
3
|
module Metanorma
|
|
5
4
|
class Collection
|
|
@@ -7,8 +6,8 @@ module Metanorma
|
|
|
7
6
|
def add_section_split
|
|
8
7
|
ret = @files.keys.each_with_object({}) do |k, m|
|
|
9
8
|
if @files[k][:sectionsplit] && !@files[k][:attachment]
|
|
10
|
-
process_section_split_instance(k, m)
|
|
11
|
-
cleanup_section_split_instance(k, m)
|
|
9
|
+
original_out_path = process_section_split_instance(k, m)
|
|
10
|
+
cleanup_section_split_instance(k, m, original_out_path)
|
|
12
11
|
end
|
|
13
12
|
m[k] = @files[k]
|
|
14
13
|
end
|
|
@@ -16,32 +15,40 @@ module Metanorma
|
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def process_section_split_instance(key, manifest)
|
|
18
|
+
# Save the original out_path before it gets modified
|
|
19
|
+
original_out_path = @files[key][:out_path]
|
|
19
20
|
s, sectionsplit_manifest = sectionsplit(key)
|
|
20
|
-
# section_split_instance_threads(s, manifest, key)
|
|
21
21
|
s.each_with_index do |f1, i|
|
|
22
22
|
add_section_split_instance(f1, manifest, key, i)
|
|
23
23
|
end
|
|
24
24
|
a = add_section_split_attachments(sectionsplit_manifest, key) and
|
|
25
25
|
manifest["#{key}:attachments"] = a
|
|
26
26
|
add_section_split_cover(manifest, sectionsplit_manifest, key)
|
|
27
|
+
# Return the original path for cleanup
|
|
28
|
+
original_out_path
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
pool = Concurrent::FixedThreadPool.new(4)
|
|
32
|
-
s.each_with_index do |f1, i|
|
|
33
|
-
pool.post do
|
|
34
|
-
add_section_split_instance(f1, manifest, key, i)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
pool.shutdown
|
|
38
|
-
pool.wait_for_termination
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def cleanup_section_split_instance(key, manifest)
|
|
31
|
+
def cleanup_section_split_instance(key, manifest, original_out_path)
|
|
32
|
+
# Delete the sectionsplit index.html from source directory after it's copied to output
|
|
42
33
|
@files_to_delete << manifest["#{key}:index.html"][:ref]
|
|
34
|
+
# Delete the original files when sectionsplit happens (all formats: html, xml, presentation.xml)
|
|
35
|
+
# Use the saved original out_path (before it was changed to index.html)
|
|
36
|
+
if original_out_path
|
|
37
|
+
base = File.join(@parent.outdir, original_out_path.sub(/\.xml$/, ""))
|
|
38
|
+
@files_to_delete << "#{base}.html"
|
|
39
|
+
@files_to_delete << "#{base}.xml"
|
|
40
|
+
@files_to_delete << "#{base}.presentation.xml"
|
|
41
|
+
end
|
|
43
42
|
# @files[key].delete(:ids).delete(:anchors)
|
|
44
43
|
@files[key][:indirect_key] = @sectionsplit.key
|
|
44
|
+
# sectionsplit is HTML-only; hand the parent its whole Presentation XML
|
|
45
|
+
# so the collection PDF/presentation concatenation inlines the document
|
|
46
|
+
# intact (raw_file) instead of degrading to a bibdata-only, namespace-
|
|
47
|
+
# less stub. file_compile early-returns for sectionsplit, so this
|
|
48
|
+
# :outputs entry is what concatenate1 consumes.
|
|
49
|
+
# https://github.com/metanorma/iso-10303/issues/208
|
|
50
|
+
@files[key][:outputs] =
|
|
51
|
+
{ presentation: @sectionsplit.whole_presentation_file }
|
|
45
52
|
end
|
|
46
53
|
|
|
47
54
|
def add_section_split_cover(manifest, sectionsplit_manifest, ident)
|
|
@@ -86,32 +93,60 @@ module Metanorma
|
|
|
86
93
|
def add_section_split_instance(file, manifest, key, idx)
|
|
87
94
|
presfile, newkey, xml = add_section_split_instance_prep(file, key)
|
|
88
95
|
anchors = read_anchors(xml)
|
|
96
|
+
# Preserve directory structure in out_path if parent has custom sectionsplit_filename with directory
|
|
97
|
+
sectionsplit_fname = @files[key][:sectionsplit_filename]
|
|
98
|
+
|
|
99
|
+
# file[:url] from sectionsplit.rb already has placeholders substituted and includes full path
|
|
100
|
+
# Use it directly for out_path (without .xml extension)
|
|
101
|
+
base_filename = File.basename(file[:url], ".xml")
|
|
102
|
+
|
|
103
|
+
# Get the directory from file[:url] which already has placeholders substituted
|
|
104
|
+
file_dir = File.dirname(file[:url])
|
|
105
|
+
|
|
106
|
+
# If file[:url] has a directory (i.e., placeholders were substituted), use it
|
|
107
|
+
out_path_value = if file_dir == "."
|
|
108
|
+
base_filename
|
|
109
|
+
else
|
|
110
|
+
File.join(file_dir, base_filename)
|
|
111
|
+
end
|
|
112
|
+
|
|
89
113
|
m = { parentid: key, presentationxml: true, type: "fileref",
|
|
90
|
-
rel_path:
|
|
114
|
+
rel_path: out_path_value, out_path: out_path_value,
|
|
91
115
|
anchors: anchors, anchors_lookup: anchors_lookup(anchors),
|
|
92
|
-
ids: read_ids(xml),
|
|
116
|
+
ids: read_ids(xml), format: @files[key][:format],
|
|
93
117
|
sectionsplit_output: true, indirect_key: @sectionsplit.key,
|
|
94
|
-
bibdata: @files[key][:bibdata], ref: presfile
|
|
118
|
+
bibdata: @files[key][:bibdata], ref: presfile,
|
|
119
|
+
sectionsplit_filename: sectionsplit_fname,
|
|
120
|
+
idx: @files[key][:idx] }
|
|
95
121
|
m[:bare] = true unless idx.zero?
|
|
96
122
|
manifest[newkey] = m
|
|
97
|
-
|
|
123
|
+
# Don't delete split output files - we want to keep them!
|
|
124
|
+
# The original parent HTML file is deleted in cleanup_section_split_instance
|
|
98
125
|
end
|
|
99
126
|
|
|
100
127
|
def add_section_split_instance_prep(file, key)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
128
|
+
# XML files are always stored flat in the _files directory (no subdirectories)
|
|
129
|
+
# file[:url] contains full path with directory for HTML output, but XML is basename only
|
|
130
|
+
xml_basename = File.basename(file[:url])
|
|
131
|
+
presfile = File.join(File.dirname(@files[key][:ref]), xml_basename)
|
|
132
|
+
newkey = entry_key("#{key.strip} #{file[:title]}")
|
|
104
133
|
xml = Nokogiri::XML(File.read(presfile), &:huge)
|
|
105
134
|
[presfile, newkey, xml]
|
|
106
135
|
end
|
|
107
136
|
|
|
108
137
|
def sectionsplit(ident)
|
|
109
138
|
file = @files[ident][:ref]
|
|
139
|
+
# @base must always be just basename, never contain directory components
|
|
140
|
+
# Directory structure comes from sectionsplit_filename pattern only
|
|
141
|
+
base = File.basename(@files[ident][:out_path] || file)
|
|
110
142
|
@sectionsplit = ::Metanorma::Collection::Sectionsplit
|
|
111
|
-
.new(input: file, base:
|
|
143
|
+
.new(input: file, base: base,
|
|
112
144
|
dir: File.dirname(file), output: @files[ident][:out_path],
|
|
113
|
-
compile_opts: @parent.compile_options,
|
|
114
|
-
fileslookup: self,
|
|
145
|
+
compile_opts: @parent.compile_options, ident: ident,
|
|
146
|
+
fileslookup: self, isodoc: @isodoc,
|
|
147
|
+
parent_idx: @files[ident][:idx],
|
|
148
|
+
sectionsplit_filename: @files[ident][:sectionsplit_filename],
|
|
149
|
+
isodoc_presxml: @isodoc_presxml,
|
|
115
150
|
document_suffix: @files[ident][:document_suffix])
|
|
116
151
|
coll = @sectionsplit.sectionsplit.sort_by { |f| f[:order] }
|
|
117
152
|
xml = Nokogiri::XML(File.read(file, encoding: "UTF-8"), &:huge)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Metanorma
|
|
2
|
+
class Collection
|
|
3
|
+
class FileLookup
|
|
4
|
+
def anchors_lookup(anchors)
|
|
5
|
+
anchors.values.each_with_object({}) do |v, m|
|
|
6
|
+
v.each_value { |v1| m[v1] = true }
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# are references to the file to be linked to a file in the collection,
|
|
11
|
+
# or externally? Determines whether file suffix anchors are to be used
|
|
12
|
+
def url?(ident)
|
|
13
|
+
data = get(ident) or return false
|
|
14
|
+
data[:url]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Normalise an identifier to its @files hash key: decode entities, squeeze
|
|
18
|
+
# whitespace, and strip a leading "metanorma-collection " prefix. Distinct
|
|
19
|
+
# from Util::key, which does NOT strip that prefix.
|
|
20
|
+
def entry_key(ident)
|
|
21
|
+
@c.decode(ident).gsub(/(\p{Zs})+/, " ")
|
|
22
|
+
.sub(/^metanorma-collection /, "")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def keys
|
|
26
|
+
@files.keys
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get(ident, attr = nil)
|
|
30
|
+
if attr then @files[entry_key(ident)][attr]
|
|
31
|
+
else @files[entry_key(ident)]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def set(ident, attr, value)
|
|
36
|
+
@files[entry_key(ident)][attr] = value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def each
|
|
40
|
+
@files.each
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def each_with_index
|
|
44
|
+
@files.each_with_index
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def ns(xpath)
|
|
48
|
+
@isodoc.ns(xpath)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Metanorma
|
|
2
|
+
class Collection
|
|
3
|
+
class << self
|
|
4
|
+
# @param Block [Proc]
|
|
5
|
+
# @note allow user-specific function to run in pre-parse model stage
|
|
6
|
+
def set_pre_parse_model(&block)
|
|
7
|
+
@pre_parse_model_proc = block
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# @param Block [Proc]
|
|
11
|
+
# @note allow user-specific function to resolve identifier
|
|
12
|
+
def set_identifier_resolver(&block)
|
|
13
|
+
@identifier_resolver = block
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param Block [Proc]
|
|
17
|
+
# @note allow user-specific function to resolve fileref
|
|
18
|
+
# NOTE: MUST ALWAYS RETURN PATH relative to working directory
|
|
19
|
+
# (initial YAML file location). @fileref_resolver.call(ref_folder, fileref)
|
|
20
|
+
# fileref is not what is in the YAML, but the resolved path
|
|
21
|
+
# relative to the working directory
|
|
22
|
+
def set_fileref_resolver(&block)
|
|
23
|
+
@fileref_resolver = block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def unset_fileref_resolver
|
|
27
|
+
@fileref_resolver = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param collection_model [Hash{String=>String}]
|
|
31
|
+
def pre_parse_model(collection_model)
|
|
32
|
+
@pre_parse_model_proc or return
|
|
33
|
+
@pre_parse_model_proc.call(collection_model)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param identifier [String]
|
|
37
|
+
# @return [String]
|
|
38
|
+
def resolve_identifier(identifier)
|
|
39
|
+
@identifier_resolver or return identifier
|
|
40
|
+
@identifier_resolver.call(identifier)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param fileref [String]
|
|
44
|
+
# @return [String]
|
|
45
|
+
def resolve_fileref(ref_folder, fileref)
|
|
46
|
+
warn ref_folder
|
|
47
|
+
warn fileref
|
|
48
|
+
unless @fileref_resolver
|
|
49
|
+
(Pathname.new fileref).absolute? or
|
|
50
|
+
fileref = File.join(ref_folder, fileref)
|
|
51
|
+
return fileref
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@fileref_resolver.call(ref_folder, fileref)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @param filepath
|
|
58
|
+
# @raise [FileNotFoundException]
|
|
59
|
+
def check_file_existence(filepath)
|
|
60
|
+
unless File.exist?(filepath)
|
|
61
|
+
error_message = "#{filepath} not found!"
|
|
62
|
+
::Metanorma::Util.log("[metanorma] Error: #{error_message}", :error)
|
|
63
|
+
raise FileNotFoundException.new error_message.to_s
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parse(file)
|
|
68
|
+
# need @dirname initialised before collection object initialisation
|
|
69
|
+
@dirname = File.expand_path(File.dirname(file))
|
|
70
|
+
config = case file
|
|
71
|
+
when /\.xml$/
|
|
72
|
+
::Metanorma::Collection::Config::Config.from_xml(File.read(file))
|
|
73
|
+
when /.ya?ml$/
|
|
74
|
+
y = YAML.safe_load(File.read(file))
|
|
75
|
+
pre_parse_model(y)
|
|
76
|
+
::Metanorma::Collection::Config::Config.from_yaml(y.to_yaml)
|
|
77
|
+
end
|
|
78
|
+
new(file: file, config: config)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Metanorma
|
|
2
|
+
class Collection
|
|
3
|
+
METANORMA_LOG_MESSAGES = {
|
|
4
|
+
# rubocop:disable Naming/VariableNumber
|
|
5
|
+
"METANORMA_1": { category: "Cross-References",
|
|
6
|
+
error: "Missing cross-reference: %s",
|
|
7
|
+
severity: 2 },
|
|
8
|
+
"METANORMA_2": { category: "Cross-References",
|
|
9
|
+
error: "[metanorma] Cannot find crossreference to document %s in document %s.",
|
|
10
|
+
severity: 2 },
|
|
11
|
+
"METANORMA_3": { category: "Cross-References",
|
|
12
|
+
error: "<strong>** Unresolved reference to document %s from eref</strong>",
|
|
13
|
+
severity: 2 },
|
|
14
|
+
"METANORMA_4": { category: "Rendering",
|
|
15
|
+
error: "Collection member %s: expected output " \
|
|
16
|
+
"file not generated: %s",
|
|
17
|
+
severity: 0 },
|
|
18
|
+
"METANORMA_5": { category: "Rendering",
|
|
19
|
+
error: "Collection member %s: rendering error: %s",
|
|
20
|
+
severity: 0 },
|
|
21
|
+
}.freeze
|
|
22
|
+
# rubocop:enable Naming/VariableNumber
|
|
23
|
+
end
|
|
24
|
+
end
|