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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +23 -2
  3. data/Gemfile +14 -0
  4. data/README.adoc +1 -1
  5. data/lib/metanorma/collection/artifact_store.rb +142 -0
  6. data/lib/metanorma/collection/collection.rb +140 -131
  7. data/lib/metanorma/collection/config/bibdata.rb +1 -1
  8. data/lib/metanorma/collection/config/config.rb +50 -20
  9. data/lib/metanorma/collection/config/converters.rb +89 -31
  10. data/lib/metanorma/collection/config/doc_container.rb +28 -0
  11. data/lib/metanorma/collection/config/manifest.rb +30 -6
  12. data/lib/metanorma/collection/config/namespaces.rb +12 -0
  13. data/lib/metanorma/collection/document/document.rb +54 -15
  14. data/lib/metanorma/collection/filelookup/filelookup.rb +170 -74
  15. data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +62 -27
  16. data/lib/metanorma/collection/filelookup/utils.rb +52 -0
  17. data/lib/metanorma/collection/helpers.rb +82 -0
  18. data/lib/metanorma/collection/log.rb +24 -0
  19. data/lib/metanorma/collection/manifest/manifest.rb +27 -14
  20. data/lib/metanorma/collection/multilingual/multilingual.rb +1 -1
  21. data/lib/metanorma/collection/renderer/filelocation.rb +166 -0
  22. data/lib/metanorma/collection/renderer/fileparse.rb +60 -55
  23. data/lib/metanorma/collection/renderer/fileprocess.rb +184 -40
  24. data/lib/metanorma/collection/renderer/navigation.rb +20 -6
  25. data/lib/metanorma/collection/renderer/render_word.rb +8 -4
  26. data/lib/metanorma/collection/renderer/renderer.rb +202 -19
  27. data/lib/metanorma/collection/renderer/svg.rb +60 -5
  28. data/lib/metanorma/collection/renderer/utils.rb +76 -42
  29. data/lib/metanorma/collection/sectionsplit/collection.rb +14 -4
  30. data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +141 -61
  31. data/lib/metanorma/collection/util/disambig_files.rb +4 -5
  32. data/lib/metanorma/collection/util/util.rb +147 -23
  33. data/lib/metanorma/collection/xrefprocess/xrefprocess.rb +7 -7
  34. data/lib/metanorma/compile/assets/icc-boilerplate.adoc +41 -0
  35. data/lib/metanorma/compile/compile.rb +180 -165
  36. data/lib/metanorma/compile/compile_options.rb +155 -83
  37. data/lib/metanorma/compile/extract.rb +76 -56
  38. data/lib/metanorma/compile/flavor.rb +19 -0
  39. data/lib/metanorma/compile/output_filename.rb +75 -0
  40. data/lib/metanorma/compile/output_filename_config.rb +27 -0
  41. data/lib/metanorma/compile/relaton_drop.rb +56 -0
  42. data/lib/metanorma/compile/render.rb +196 -0
  43. data/lib/metanorma/compile/validator.rb +26 -0
  44. data/lib/metanorma/compile/writeable.rb +12 -0
  45. data/lib/metanorma/util/fontist_helper.rb +35 -19
  46. data/lib/metanorma/version.rb +1 -1
  47. data/lib/metanorma.rb +1 -7
  48. data/metanorma.gemspec +11 -20
  49. data/plans/collection-rendering-architecture-review.md +516 -0
  50. metadata +42 -122
  51. data/.hound.yml +0 -5
  52. data/lib/metanorma/asciidoctor_extensions/glob_include_processor.rb +0 -22
  53. data/lib/metanorma/asciidoctor_extensions.rb +0 -1
  54. data/lib/metanorma/compile/compile_validate.rb +0 -68
  55. data/lib/metanorma/config/config.rb +0 -23
  56. data/lib/metanorma/input/asciidoc.rb +0 -88
  57. data/lib/metanorma/input/base.rb +0 -9
  58. data/lib/metanorma/input.rb +0 -7
  59. data/lib/metanorma/processor/processor.rb +0 -54
  60. data/lib/metanorma/processor.rb +0 -6
  61. data/lib/metanorma/registry/registry.rb +0 -63
  62. 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.file and read_file(e)
33
- read_files(e.entry)
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 read_file(manifest)
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[key(i)] = entry
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 = key(@isodoc.docid_prefix("", manifest.identifier.dup))
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 = key(@isodoc.docid_prefix(d.type, d.id.dup))
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
- def file_entry(ref, identifier)
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: ref.file, url: ref.url,
100
- out_path: output_file_path(ref) }
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
- def output_file_path(ref)
110
- f = File.basename(ref.file)
111
- ref.attachment and f = ref.file
112
- @disambig.source2dest_filename(f)
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).each do |s|
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
- Metanorma::Utils::anchor_attributes.each do |(tag_name, attr_name)|
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[:out_path], options[:read], options[:doc]
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, out, read, doc)
266
+ def ref_file(ref, data, read, doc)
181
267
  file = File.read(ref, encoding: "utf-8") if read
182
- filename = out.dup
183
- filename.sub!(/\.xml$/, ".html") if doc
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
- def key(ident)
231
- @c.decode(ident).gsub(/(\p{Zs})+/, " ")
232
- .sub(/^metanorma-collection /, "")
233
- end
234
-
235
- def keys
236
- @files.keys
237
- end
238
-
239
- def get(ident, attr = nil)
240
- if attr then @files[key(ident)][attr]
241
- else @files[key(ident)]
242
- end
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 section_split_instance_threads(s, manifest, key)
30
- @mutex = Mutex.new
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: file[:url], out_path: File.basename(file[:url]),
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
- @files_to_delete << file[:url]
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
- presfile = File.join(File.dirname(@files[key][:ref]),
102
- File.basename(file[:url]))
103
- newkey = key("#{key.strip} #{file[:title]}")
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: @files[ident][:out_path],
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, ident: ident, isodoc: @isodoc,
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