metanorma 2.0.0 → 2.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +23 -2
  5. data/Gemfile +17 -0
  6. data/Gemfile.devel +10 -0
  7. data/README.adoc +1 -1
  8. data/lib/metanorma/collection/artifact_store.rb +142 -0
  9. data/lib/metanorma/collection/collection.rb +160 -122
  10. data/lib/metanorma/collection/config/bibdata.rb +3 -3
  11. data/lib/metanorma/collection/config/compile_options.rb +4 -5
  12. data/lib/metanorma/collection/config/config.rb +81 -83
  13. data/lib/metanorma/collection/config/converters.rb +92 -8
  14. data/lib/metanorma/collection/config/directive.rb +9 -3
  15. data/lib/metanorma/collection/config/doc_container.rb +28 -0
  16. data/lib/metanorma/collection/config/manifest.rb +59 -29
  17. data/lib/metanorma/collection/config/namespaces.rb +12 -0
  18. data/lib/metanorma/collection/document/document.rb +56 -15
  19. data/lib/metanorma/collection/filelookup/filelookup.rb +185 -77
  20. data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +99 -27
  21. data/lib/metanorma/collection/filelookup/utils.rb +52 -0
  22. data/lib/metanorma/collection/helpers.rb +82 -0
  23. data/lib/metanorma/collection/log.rb +24 -0
  24. data/lib/metanorma/collection/manifest/manifest.rb +45 -14
  25. data/lib/metanorma/collection/multilingual/multilingual.rb +660 -0
  26. data/lib/metanorma/collection/renderer/filelocation.rb +166 -0
  27. data/lib/metanorma/collection/renderer/fileparse.rb +135 -115
  28. data/lib/metanorma/collection/renderer/fileprocess.rb +185 -37
  29. data/lib/metanorma/collection/renderer/navigation.rb +21 -7
  30. data/lib/metanorma/collection/renderer/render_word.rb +15 -10
  31. data/lib/metanorma/collection/renderer/renderer.rb +236 -32
  32. data/lib/metanorma/collection/renderer/svg.rb +107 -0
  33. data/lib/metanorma/collection/renderer/utils.rb +110 -39
  34. data/lib/metanorma/collection/sectionsplit/collection.rb +110 -0
  35. data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +198 -115
  36. data/lib/metanorma/collection/util/disambig_files.rb +4 -5
  37. data/lib/metanorma/collection/util/util.rb +155 -18
  38. data/lib/metanorma/collection/xrefprocess/xrefprocess.rb +47 -33
  39. data/lib/metanorma/compile/assets/icc-boilerplate.adoc +41 -0
  40. data/lib/metanorma/compile/compile.rb +181 -156
  41. data/lib/metanorma/compile/compile_options.rb +186 -79
  42. data/lib/metanorma/compile/extract.rb +75 -58
  43. data/lib/metanorma/compile/flavor.rb +28 -0
  44. data/lib/metanorma/compile/output_filename.rb +75 -0
  45. data/lib/metanorma/compile/output_filename_config.rb +27 -0
  46. data/lib/metanorma/compile/relaton_drop.rb +56 -0
  47. data/lib/metanorma/compile/render.rb +226 -0
  48. data/lib/metanorma/compile/validator.rb +26 -0
  49. data/lib/metanorma/compile/writeable.rb +12 -0
  50. data/lib/metanorma/util/fontist_helper.rb +35 -19
  51. data/lib/metanorma/version.rb +1 -1
  52. data/lib/metanorma.rb +1 -7
  53. data/metanorma.gemspec +15 -21
  54. data/plans/collection-rendering-architecture-review.md +516 -0
  55. metadata +65 -113
  56. data/.hound.yml +0 -5
  57. data/lib/metanorma/asciidoctor_extensions/glob_include_processor.rb +0 -22
  58. data/lib/metanorma/asciidoctor_extensions.rb +0 -1
  59. data/lib/metanorma/compile/compile_validate.rb +0 -68
  60. data/lib/metanorma/config/config.rb +0 -23
  61. data/lib/metanorma/input/asciidoc.rb +0 -107
  62. data/lib/metanorma/input/base.rb +0 -9
  63. data/lib/metanorma/input.rb +0 -7
  64. data/lib/metanorma/processor/processor.rb +0 -54
  65. data/lib/metanorma/processor.rb +0 -6
  66. data/lib/metanorma/registry/registry.rb +0 -63
  67. data/lib/metanorma/shale_monkeypatch.rb +0 -15
  68. data/lib/metanorma/util/util.rb +0 -45
@@ -1,11 +1,30 @@
1
1
  require "yaml"
2
- require_relative "../../util/util"
2
+ require "fileutils"
3
+ require "metanorma-core"
3
4
  require_relative "../xrefprocess/xrefprocess"
5
+ require_relative "collection"
6
+ require "concurrent-ruby"
4
7
 
5
8
  module Metanorma
6
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.
7
26
  class Sectionsplit
8
- attr_accessor :filecache, :key
27
+ attr_accessor :filecache, :key, :whole_presentation_file
9
28
 
10
29
  def initialize(opts)
11
30
  @input_filename = opts[:input]
@@ -17,71 +36,76 @@ module Metanorma
17
36
  @fileslookup = opts[:fileslookup]
18
37
  @ident = opts[:ident]
19
38
  @isodoc = opts[:isodoc]
39
+ @isodoc_presxml = opts[:isodoc_presxml]
40
+ @document_suffix = opts[:document_suffix]
41
+ @sectionsplit_filename = opts[:sectionsplit_filename] ||
42
+ "{basename_legacy}.{sectionsplit-num}"
43
+ @parent_idx = opts[:parent_idx] || 0
20
44
  end
21
45
 
22
46
  def ns(xpath)
23
47
  @isodoc.ns(xpath)
24
48
  end
25
49
 
26
- def build_collection
27
- collection_setup(@base, @dir)
28
- files = sectionsplit # (@input_filename, @base, @dir, @compile_opts)
29
- input_xml = Nokogiri::XML(File.read(@input_filename,
30
- encoding: "UTF-8"), &:huge)
31
- collection_manifest(@base, files, input_xml, @xml, @dir).render(
32
- { format: %i(html), output_folder: "#{@output_filename}_collection",
33
- coverpage: File.join(@dir, "cover.html") }.merge(@compile_opts),
34
- )
35
- end
36
-
37
- def collection_manifest(filename, files, origxml, _presxml, dir)
38
- File.open(File.join(dir, "#{filename}.html.yaml"), "w:UTF-8") do |f|
39
- f.write(collectionyaml(files, origxml))
40
- end
41
- Metanorma::Collection.parse File.join(dir, "#{filename}.html.yaml")
42
- end
43
-
44
- def collection_setup(filename, dir)
45
- FileUtils.mkdir_p "#{filename}_collection" if filename
46
- FileUtils.mkdir_p dir
47
- File.open(File.join(dir, "cover.html"), "w:UTF-8") do |f|
48
- f.write(coll_cover)
49
- end
50
- end
51
-
52
- def coll_cover
53
- <<~COVER
54
- <html><head><meta charset="UTF-8"/></head><body>
55
- <h1>{{ doctitle }}</h1>
56
- <h2>{{ docnumber }}</h2>
57
- <nav>{{ navigation }}</nav>
58
- </body></html>
59
- COVER
60
- end
61
-
62
50
  SPLITSECTIONS =
63
51
  [["//preface/*", "preface"], ["//sections/*", "sections"],
64
52
  ["//annex", nil],
65
53
  ["//bibliography/*[not(@hidden = 'true')]", "bibliography"],
66
54
  ["//indexsect", nil], ["//colophon", nil]].freeze
67
55
 
68
- # Input XML is Semantic
69
- # def sectionsplit(filename, basename, dir, compile_options, fileslookup = nil, ident = nil)
56
+ # Input XML is Semantic XML
70
57
  def sectionsplit
71
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)
72
66
  @key = Metanorma::Collection::XrefProcess::xref_preprocess(xml, @isodoc)
73
- SPLITSECTIONS.each_with_object([]) do |n, ret|
67
+ empty = empty_doc(xml)
68
+ empty1 = empty_attachments(empty)
69
+ @mutex = Mutex.new
70
+ # @pool = Concurrent::FixedThreadPool.new(4)
71
+ @pool = Concurrent::FixedThreadPool.new(1)
72
+ sectionsplit1(xml, empty, empty1, 0)
73
+ end
74
+
75
+ # xml is Presentation XML
76
+ def sectionsplit1(xml, empty, empty1, idx)
77
+ ret = SPLITSECTIONS.each_with_object([]) do |n, m|
74
78
  conflate_floatingtitles(xml.xpath(ns(n[0]))).each do |s|
75
- ret << sectionfile(xml, emptydoc(xml), "#{@base}.#{ret.size}", s,
76
- n[1])
79
+ # require "debug"; binding.b
80
+ sectionsplit2(xml, idx.zero? ? empty : empty1, s, n[1],
81
+ { acc: m, idx: idx })
82
+ idx += 1
77
83
  end
78
84
  end
85
+ @pool.shutdown
86
+ @pool.wait_for_termination
87
+ ret
79
88
  end
80
89
 
90
+ def sectionsplit2(xml, empty, chunks, parentnode, opt)
91
+ @pool.post do
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
+ )
97
+ warn "Sectionsplit: #{output_filename}"
98
+ a = sectionfile(xml, empty, output_filename, chunks,
99
+ parentnode)
100
+ @mutex.synchronize { opt[:acc] << a }
101
+ end
102
+ end
103
+
104
+ # TODO move to metanorma-utils
81
105
  def block?(node)
82
106
  %w(p table formula admonition ol ul dl figure quote sourcecode example
83
- pre note pagebrreak hr bookmark requirement recommendation permission
84
- svgmap inputform toc passthrough review imagemap).include?(node.name)
107
+ pre note pagebreak hr bookmark requirement recommendation permission
108
+ svgmap inputform toc passthrough annotation imagemap).include?(node.name)
85
109
  end
86
110
 
87
111
  def conflate_floatingtitles(nodes)
@@ -96,53 +120,69 @@ module Metanorma
96
120
 
97
121
  def sectionsplit_prep(file, filename, dir)
98
122
  @splitdir = dir
99
- xml1filename, type = sectionsplit_preprocess_semxml(file, filename)
100
- Compile.new.compile(
101
- xml1filename,
102
- { format: :asciidoc, extension_keys: [:presentation], type: type }
103
- .merge(@compile_opts),
104
- )
105
- Nokogiri::XML(File.read(xml1filename.sub(/\.xml$/, ".presentation.xml"),
106
- encoding: "utf-8"), &:huge)
123
+ xml, type = sectionsplit_preprocess_semxml(file, filename)
124
+ flags = { format: :asciidoc, extension_keys: [:presentation],
125
+ type: type }.merge(@compile_opts)
126
+ Compile.new.compile(xml, flags)
127
+ f = File.open(xml.sub(/\.xml$/, ".presentation.xml"), encoding: "utf-8")
128
+ r = Nokogiri::XML(f, &:huge)
129
+ f.close
130
+ r.xpath("//xmlns:svgmap1").each { |x| x.name = "svgmap" }
131
+ r
132
+ end
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
107
142
  end
108
143
 
109
144
  def sectionsplit_preprocess_semxml(file, filename)
110
145
  xml = Nokogiri::XML(file, &:huge)
111
- type = xml.root.name.sub("-standard", "").to_sym
146
+ type = xml.root["flavor"]
147
+ type ||= xml.root.name.sub("-standard", "").to_sym
112
148
  sectionsplit_update_xrefs(xml)
113
149
  xml1 = sectionsplit_write_semxml(filename, xml)
114
- @filecache ||= []
115
- @filecache << xml1
116
- [xml1.path, type]
150
+ @tmp_filename = xml1
151
+ [xml1, type]
117
152
  end
118
153
 
119
154
  def sectionsplit_update_xrefs(xml)
120
155
  if c = @fileslookup&.parent
121
- n = c.nested
122
- c.nested = true # so unresolved erefs are not deleted
123
- c.update_xrefs(xml, @ident, {})
124
- c.nested = n
156
+ # nested mode so unresolved erefs are not deleted
157
+ c.with_nested { c.update_xrefs(xml, @ident, {}) }
158
+ xml.xpath("//xmlns:svgmap").each { |x| x.name = "svgmap1" }
159
+ # do not process svgmap until after files are split
125
160
  end
126
161
  end
127
162
 
128
163
  def sectionsplit_write_semxml(filename, xml)
129
- Tempfile.open([filename, ".xml"], encoding: "utf-8") do |f|
130
- # f.write(@isodoc.to_xml(svg_preprocess(xml)))
164
+ outname = Pathname.new("tmp_#{filename}").sub_ext(".xml").to_s
165
+ File.open(outname, "w:UTF-8") do |f|
131
166
  f.write(@isodoc.to_xml(xml))
132
- f
133
167
  end
168
+ outname
134
169
  end
135
170
 
136
- def emptydoc(xml)
171
+ def empty_doc(xml)
137
172
  out = xml.dup
138
173
  out.xpath(
139
- ns("//preface | //sections | //annex | //bibliography/clause | " \
140
- "//bibliography/references[not(@hidden = 'true')] | //indexsect | " \
141
- "//colophon"),
174
+ ns("//preface | //sections | //annex | " \
175
+ "//references/bibitem[not(@hidden = 'true')] | " \
176
+ "//indexsect | //colophon"),
142
177
  ).each(&:remove)
178
+ ::Metanorma::Collection::Util::hide_refs(out)
143
179
  out
144
180
  end
145
181
 
182
+ def empty_attachments(xml)
183
+ xml.dup
184
+ end
185
+
146
186
  def sectionfile(fulldoc, xml, file, chunks, parentnode)
147
187
  fname = create_sectionfile(fulldoc, xml.dup, file, chunks, parentnode)
148
188
  { order: chunks.last["displayorder"].to_i, url: fname,
@@ -152,12 +192,17 @@ module Metanorma
152
192
  def create_sectionfile(xml, out, file, chunks, parentnode)
153
193
  ins = out.at(ns("//metanorma-extension")) || out.at(ns("//bibdata"))
154
194
  sectionfile_insert(ins, chunks, parentnode)
155
- Metanorma::Collection::XrefProcess::xref_process(out, xml, @key, @ident, @isodoc)
156
- outname = "#{file}.xml"
157
- File.open(File.join(@splitdir, outname), "w:UTF-8") do |f|
158
- f.write(out)
159
- end
160
- outname
195
+ sectionfile_fn_filter(sectionfile_annotation_filter(out))
196
+ Metanorma::Collection::XrefProcess::xref_process(out, xml, @key,
197
+ @ident, @isodoc, true)
198
+ # XML files are always written flat to the splitdir (_files directory)
199
+ # Directory structure from sectionsplit_filename is only used for HTML output
200
+ xml_filename = "#{File.basename(file)}.xml"
201
+ full_path = File.join(@splitdir, xml_filename)
202
+ File.open(full_path, "w:UTF-8") { |f| f.write(out) }
203
+ # Return full file path (with directory) for use in manifest
204
+ # This preserves directory info for HTML rendering without affecting XML storage
205
+ "#{file}.xml"
161
206
  end
162
207
 
163
208
  def sectionfile_insert(ins, chunks, parentnode)
@@ -168,50 +213,88 @@ module Metanorma
168
213
  end
169
214
  end
170
215
 
216
+ def sectionfile_fn_filter(xml)
217
+ ids = sectionfile_fn_filter_prep(xml)
218
+ xml.root.xpath(ns("./fmt-footnote-container/fmt-fn-body")).each do |f|
219
+ ids.has_key?(f["id"]) or f.remove
220
+ end
221
+ seen = {}
222
+ xml.root.xpath(ns("/fmt-footnote-container/fmt-fn-body"))
223
+ .each_with_index do |fnbody, i|
224
+ sectionfile_fn_filter_renumber(fnbody, i, ids, seen)
225
+ end
226
+ xml
227
+ end
228
+
229
+ # map fmt-fn-body/@id = fn/@target to fn
230
+ def sectionfile_fn_filter_prep(xml)
231
+ xml.xpath(ns("//fn")).each_with_object({}) do |f, m|
232
+ m[f["target"]] ||= []
233
+ m[f["target"]] << f
234
+ end
235
+ end
236
+
237
+ FN_CAPTIONS = ".//fmt-fn-label/span[@class = 'fmt-caption-label']".freeze
238
+
239
+ def sectionfile_fn_filter_renumber(fnbody, idx, ids, seen)
240
+ sectionfile_fn_filter_fn_renumber(fnbody, idx, ids, seen)
241
+ sectionfile_fn_filter_fnbody_renumber(fnbody, idx, ids)
242
+ end
243
+
244
+ def sectionfile_fn_filter_fn_renumber(fnbody, idx, ids, seen)
245
+ ids[fnbody["id"]].each do |f|
246
+ @isodoc_presxml.renumber_document_footnote(f, idx, seen)
247
+ fnlabel = f.at(ns(FN_CAPTIONS)) and
248
+ fnlabel.children = @isodoc_presxml.fn_ref_label(f)
249
+ end
250
+ end
251
+
252
+ def sectionfile_fn_filter_fnbody_renumber(fnbody, _idx, ids)
253
+ fnlabel = fnbody.at(ns(FN_CAPTIONS)) or return
254
+ fnbody["reference"] = ids[fnbody["id"]].first["reference"]
255
+ fnlabel.children = @isodoc_presxml.fn_body_label(fnbody)
256
+ end
257
+
258
+ # map fmt-annotation-body/@id = fmt-annotation-{start/end}/@target
259
+ # to fmt-annotation-{start/end}
260
+ def sectionfile_annotation_filter_prep(xml)
261
+ xml.xpath(ns("//fmt-annotation-start | //fmt-annotation-end"))
262
+ .each_with_object({}) do |f, m|
263
+ m[f["target"]] ||= []
264
+ m[f["target"]] << f
265
+ end
266
+ end
267
+
268
+ def sectionfile_annotation_filter(xml)
269
+ ids = sectionfile_annotation_filter_prep(xml)
270
+ xml.root.xpath(ns("./annotation-container/fmt-annotation-body"))
271
+ .each do |f|
272
+ ids.has_key?(f["id"]) or f.remove
273
+ end
274
+ xml.root.xpath(ns("./annotation-container/fmt-annotation-body"))
275
+ .each_with_index do |fnbody, i|
276
+ sectionfile_annotation_filter_renumber(fnbody, i, ids)
277
+ end
278
+ xml
279
+ end
280
+
281
+ def sectionfile_annotation_filter_renumber(fnbody, _idx, ids)
282
+ ids[fnbody["id"]].each do |f|
283
+ case f.name
284
+ when "fmt-annotation-start"
285
+ f.children = @isodoc_presxml.comment_bookmark_start_label(f)
286
+ when "fmt-annotation-end"
287
+ f.children = @isodoc_presxml.comment_bookmark_end_label(f)
288
+ end
289
+ end
290
+ end
291
+
171
292
  def titlerender(section)
172
- title = section.at(ns("./title")) or return "[Untitled]"
293
+ title = section.at(ns("./fmt-title")) or return "[Untitled]"
173
294
  t = title.dup
174
295
  t.xpath(ns(".//tab | .//br")).each { |x| x.replace(" ") }
175
- t.xpath(ns(".//strong")).each { |x| x.replace(x.children) }
176
- t.children.to_xml
177
- end
178
-
179
- def collectionyaml(files, xml)
180
- ret = {
181
- directives: ["presentation-xml", "bare-after-first"],
182
- bibdata: {
183
- title: {
184
- type: "title-main", language: @lang,
185
- content: xml.at(ns("//bibdata/title")).text
186
- },
187
- type: "collection",
188
- docid: {
189
- type: xml.at(ns("//bibdata/docidentifier/@type")).text,
190
- id: xml.at(ns("//bibdata/docidentifier")).text,
191
- },
192
- },
193
- manifest: {
194
- level: "collection", title: "Collection",
195
- docref: files.sort_by { |f| f[:order] }.each.map do |f|
196
- { fileref: f[:url], identifier: f[:title] }
197
- end
198
- },
199
- }
200
- ::Metanorma::Util::recursive_string_keys(ret).to_yaml
201
- end
202
-
203
- def section_split_cover(col, ident, _one_doc_coll)
204
- dir = File.dirname(col.file)
205
- collection_setup(nil, dir)
206
- r = ::Metanorma::Collection::Renderer
207
- .new(col, dir, output_folder: "#{ident}_collection",
208
- format: %i(html), coverpage: File.join(dir, "cover.html"))
209
- r.coverpage
210
- # filename = one_doc_coll ? "#{ident}_index.html" : "index.html"
211
- filename = File.basename("#{ident}_index.html") # ident can be a directory with YAML indirection
212
- FileUtils.mv File.join(r.outdir, "index.html"), File.join(dir, filename)
213
- FileUtils.rm_rf r.outdir
214
- filename
296
+ t.xpath(ns(".//bookmark")).each(&:remove)
297
+ t.xpath(".//text()").map(&:text).join
215
298
  end
216
299
  end
217
300
  end
@@ -10,13 +10,12 @@ module Metanorma
10
10
  name.sub(%r{^(\./)?(\.\./)+}, "")
11
11
  end
12
12
 
13
- def source2dest_filename(name, disambig = true)
13
+ def source2dest_filename(name, disambig: true, preserve_dirs: false)
14
14
  n = strip_root(name)
15
- dir = File.dirname(n)
16
- base = File.basename(n)
17
- if disambig && @seen_filenames.include?(base)
15
+ dir = preserve_dirs ? "." : File.dirname(n)
16
+ base = preserve_dirs ? n : File.basename(n)
17
+ disambig && @seen_filenames.include?(base) and
18
18
  base = disambiguate_filename(base)
19
- end
20
19
  @seen_filenames << base
21
20
  dir == "." ? base : File.join(dir, base)
22
21
  end
@@ -1,42 +1,57 @@
1
+ require "marcel"
2
+
1
3
  module Metanorma
2
4
  class Collection
3
5
  module Util
4
6
  class << self
7
+ def mime_file_recognised?(filename)
8
+ mime_type = Marcel::MimeType.for name: filename
9
+ mime_type != "application/octet-stream"
10
+ end
11
+
12
+ def anchor_id_attributes
13
+ Metanorma::Utils::anchor_attributes(presxml: true) +
14
+ [%w(* id), %w(* anchor), %w(link bibitemid), %w(fmt-link bibitemid)]
15
+ end
16
+
5
17
  def gather_bibitems(xml)
6
18
  xml.xpath("//xmlns:bibitem[@id]").each_with_object({}) do |b, m|
7
- if m[b["id"]]
19
+ id = b["anchor"] || b["id"]
20
+ if m[id]
8
21
  b.remove
9
22
  next
10
23
  # we can't update duplicate bibitem, processing updates wrong one
11
24
  else
12
- m[b["id"]] = b
25
+ m[id] = b
13
26
  end
14
27
  end
15
28
  end
16
29
 
17
- def gather_bibitemids(xml)
30
+ def gather_bibitemids(xml, presxml)
18
31
  xml.xpath("//*[@bibitemid]").each_with_object({}) do |e, m|
19
- /^semantic__/.match?(e.name) and next
32
+ presxml && %w(xref eref link).include?(e.name) and next
20
33
  m[e["bibitemid"]] ||= []
21
34
  m[e["bibitemid"]] << e
22
35
  end
23
36
  end
24
37
 
25
- def gather_citeases(xml)
38
+ def gather_citeases(xml, presxml)
26
39
  xml.xpath("//*[@citeas]").each_with_object({}) do |e, m|
27
- /^semantic__/.match?(e.name) and next
28
- m[e["citeas"]] ||= []
29
- m[e["citeas"]] << e
40
+ presxml && %w(xref eref link).include?(e.name) and next
41
+ k = key(e["citeas"])
42
+ m[k] ||= []
43
+ m[k] << e
30
44
  end
31
45
  end
32
46
 
33
- def add_suffix_to_attributes(doc, suffix, tag_name, attr_name,
34
- isodoc)
47
+ # Skip SVGs, they are processed by Vectory::SvgDocument#suffix_ids
48
+ def add_suffix_to_attrs(doc, suffix, tag, attr, isodoc)
35
49
  (suffix.nil? || suffix.empty?) and return
36
- doc.xpath(isodoc.ns("//#{tag_name}[@#{attr_name}]")).each do |elem|
37
- a = elem.attributes[attr_name].value
50
+ doc.xpath(isodoc.ns("//#{tag}[@#{attr}][not(ancestor-or-self::*[local-name()='svg'])]")).each do |elem|
51
+ # doc.xpath(isodoc.ns("//#{tag}[@#{attr}]")).each do |elem|
52
+ a = elem.attributes[attr].value
38
53
  /_#{suffix}$/.match?(a) or
39
- elem.attributes[attr_name].value = "#{a}_#{suffix}"
54
+ elem.attributes[attr].value = "#{a}_#{suffix}"
40
55
  end
41
56
  end
42
57
 
@@ -53,18 +68,140 @@ isodoc)
53
68
  p.absolute? ? path : File.join(dir, path)
54
69
  end
55
70
 
71
+ def hide_refs(docxml)
72
+ p = "//xmlns:references[xmlns:bibitem]"\
73
+ "[not(./xmlns:bibitem[not(@hidden) or @hidden = 'false'])]"
74
+ docxml.xpath(p).each do |f|
75
+ f["hidden"] = "true"
76
+ end
77
+ end
78
+
56
79
  def key(ident)
57
80
  @c ||= HTMLEntities.new
58
81
  @c.decode(ident).gsub(/(\p{Zs})+/, " ")
59
82
  end
60
83
 
61
- class Dummy
62
- def attr(_key); end
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
+
100
+ def taste2flavor(taste)
101
+ ::Metanorma::Core::FlavorLoader.taste2flavor(taste)
102
+ end
103
+
104
+ def taste2isodoc_attrs(taste, format)
105
+ tastes = Metanorma::TasteRegister.instance.aliases
106
+ tastes[taste.to_sym] or return {}
107
+ Metanorma::TasteRegister.isodoc_attrs(taste.to_sym, format)
108
+ end
109
+
110
+ def taste2coverpage_pdf_portfolio(taste)
111
+ tastes = Metanorma::TasteRegister.instance.aliases
112
+ tastes[taste.to_sym] or return nil
113
+ taste = Metanorma::TasteRegister.instance.get(taste.to_sym)
114
+ ret = taste.config.base_override&.filename_attributes
115
+ &.coverpage_pdf_portfolio or return
116
+ File.join(taste.directory, ret)
117
+ end
118
+
119
+ SVG_NS = "http://www.w3.org/2000/svg".freeze
120
+
121
+ # update relative URLs, url(#...), in CSS in @style attrs (incl. SVG),
122
+ # and in include SVG url(#..) attrs,
123
+ # not processed already by add_suffix_to_attrs
124
+ def url_in_css_styles(doc, ids, document_suffix)
125
+ update_ids_css(doc.root, ids, document_suffix)
126
+ doc.xpath("//i:svg", "i" => SVG_NS).each do |s|
127
+ svg = Vectory::SvgDocument.new(s.to_xml)
128
+ svg.suffix_ids(document_suffix)
129
+ end
130
+ end
131
+
132
+ # replicates Vectory update_ids_css, but skips over svgs,
133
+ # which are processed separately by Vectory::SvgDocument#suffix_ids
134
+ def update_ids_css(document, ids, suffix)
135
+ suffix = suffix.is_a?(Integer) ? sprintf("%09d", suffix) : suffix
136
+ document.xpath(".//m:style[not(ancestor::i:svg)]",
137
+ "m" => SVG_NS, "i" => SVG_NS).each do |s|
138
+ s.children = update_ids_css_string(s.children.to_xml, ids, suffix)
139
+ end
140
+ document.xpath(".//*[@style][not(ancestor::i:svg)]",
141
+ "i" => SVG_NS).each do |s|
142
+ s["style"] = update_ids_css_string(s["style"], ids, suffix)
143
+ end
144
+ end
145
+
146
+ # Updates ID references in a CSS style string.
147
+ # Replicates Vectory update_ids_css_string
148
+ def update_ids_css_string(style, ids, suffix)
149
+ ids.each do |i|
150
+ style = style.gsub(%r[##{i}\b],
151
+ sprintf("#%<id>s_%<suffix>s", id: i,
152
+ suffix: suffix))
153
+ .gsub(%r(\[id\s*=\s*['"]?#{i}['"]?\]),
154
+ sprintf("[id='%<id>s_%<suffix>s']", id: i,
155
+ suffix: suffix))
156
+ end
157
+ style
158
+ end
159
+
160
+ # doc.xpath("//*[@style]").each do |s|
161
+ # s["style"] = url_in_css_styles1(s["style"], document_suffix)
162
+ # end
163
+ # doc.xpath("//i:svg//i:style", "i" => "http://www.w3.org/2000/svg")
164
+ # .each do |s|
165
+ # s.children = url_in_css_styles1(s.text, document_suffix)
166
+ # end
167
+ #
168
+ # # KILL
169
+ # def url_in_css_styles1(style, document_suffix)
170
+ # style.gsub(%r{url\(#([^()]+)\)}, "url(#\\1_#{document_suffix})")
171
+ # end
172
+
173
+ # @deprecated Call Metanorma::Core::Isodoc.resolve_converter directly.
174
+ def load_isodoc(flavor, presxml: false)
175
+ Metanorma::Core::Isodoc.resolve_converter(flavor, presxml: presxml)
176
+ end
177
+
178
+ # @deprecated Call Metanorma::Core::Isodoc.create directly. Kept as
179
+ # a back-compat shim so existing callers keep working unchanged
180
+ # while the wrapper is retired.
181
+ def isodoc_create(flavor, lang, script, xml, presxml: false)
182
+ Metanorma::Core::Isodoc.create(flavor, lang: lang, script: script,
183
+ xml: xml, presxml: presxml)
184
+ end
185
+
186
+ # Dummy asciidoc header used by the prefatory-content parse so we
187
+ # can run Asciidoctor against arbitrary text. If +docidentifier+
188
+ # is supplied, it is injected as +:docidentifier:+ so the flavor
189
+ # processor's metadata_id has a real value to put into the
190
+ # bibdata, instead of falling back to its (Liquid-templated)
191
+ # docid_template. This prevents the prefatory parse from feeding
192
+ # an unresolved Liquid template through Relaton/pubid in flavors
193
+ # whose relaton-* gem eagerly parses the docidentifier in
194
+ # content= (issue #558).
195
+ def asciidoc_dummy_header(docidentifier: nil)
196
+ attrs = docidentifier ? ":docidentifier: #{docidentifier}\n" : ""
197
+ "= X\nA\n#{attrs}\n"
63
198
  end
64
199
 
65
- def load_isodoc(doctype)
66
- x = Asciidoctor.load nil, backend: doctype.to_sym
67
- x.converter.html_converter(Dummy.new) # to obtain Isodoc class
200
+ def nokogiri_to_temp(xml, filename, suffix)
201
+ file = Tempfile.new([filename, suffix])
202
+ file.write(xml.to_xml(indent: 0))
203
+ file.close
204
+ [file, file.path]
68
205
  end
69
206
  end
70
207
  end