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
@@ -44,18 +44,28 @@ module Metanorma
44
44
  bibdata: {
45
45
  title: {
46
46
  type: "title-main", language: @lang,
47
- content: xml.at(ns("//bibdata/title")).text
47
+ content: xml.at(ns("//bibdata/title"))&.text || "[TITLE]"
48
48
  },
49
49
  type: "collection",
50
50
  docid: {
51
- type: xml.at(ns("//bibdata/docidentifier/@type")).text,
52
- id: xml.at(ns("//bibdata/docidentifier")).text,
51
+ type: xml.at(ns("//bibdata/docidentifier/@type"))&.text || "[FLAVOR]",
52
+ id: xml.at(ns("//bibdata/docidentifier"))&.text || "[DOCID]",
53
53
  },
54
54
  },
55
55
  manifest: {
56
56
  level: "collection", title: "Collection",
57
57
  docref: files.sort_by { |f| f[:order] }.each.map do |f|
58
- { fileref: f[:url], identifier: f[:title] }
58
+ # XML files are always stored flat, so fileref is always the basename
59
+ # f[:url] may include directory for HTML output, but XML is basename only
60
+ fileref = File.basename(f[:url])
61
+ entry = { fileref: fileref, identifier: f[:title] }
62
+ # Include sectionsplit_filename and sectionsplit-output when there's a directory structure
63
+ # This tells the renderer to preserve directory structure in HTML output
64
+ if @sectionsplit_filename && File.dirname(@sectionsplit_filename) != "."
65
+ entry[:"sectionsplit-filename"] = @sectionsplit_filename
66
+ entry[:"sectionsplit-output"] = true
67
+ end
68
+ entry
59
69
  end
60
70
  },
61
71
  }
@@ -1,13 +1,30 @@
1
1
  require "yaml"
2
- require_relative "../../util/util"
2
+ require "fileutils"
3
+ require "metanorma-core"
3
4
  require_relative "../xrefprocess/xrefprocess"
4
5
  require_relative "collection"
5
6
  require "concurrent-ruby"
6
7
 
7
8
  module Metanorma
8
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.
9
26
  class Sectionsplit
10
- attr_accessor :filecache, :key
27
+ attr_accessor :filecache, :key, :whole_presentation_file
11
28
 
12
29
  def initialize(opts)
13
30
  @input_filename = opts[:input]
@@ -19,7 +36,11 @@ module Metanorma
19
36
  @fileslookup = opts[:fileslookup]
20
37
  @ident = opts[:ident]
21
38
  @isodoc = opts[:isodoc]
39
+ @isodoc_presxml = opts[:isodoc_presxml]
22
40
  @document_suffix = opts[:document_suffix]
41
+ @sectionsplit_filename = opts[:sectionsplit_filename] ||
42
+ "{basename_legacy}.{sectionsplit-num}"
43
+ @parent_idx = opts[:parent_idx] || 0
23
44
  end
24
45
 
25
46
  def ns(xpath)
@@ -32,21 +53,30 @@ module Metanorma
32
53
  ["//bibliography/*[not(@hidden = 'true')]", "bibliography"],
33
54
  ["//indexsect", nil], ["//colophon", nil]].freeze
34
55
 
35
- # Input XML is Semantic
56
+ # Input XML is Semantic XML
36
57
  def sectionsplit
37
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)
38
66
  @key = Metanorma::Collection::XrefProcess::xref_preprocess(xml, @isodoc)
39
67
  empty = empty_doc(xml)
40
68
  empty1 = empty_attachments(empty)
41
69
  @mutex = Mutex.new
42
- #@pool = Concurrent::FixedThreadPool.new(4)
70
+ # @pool = Concurrent::FixedThreadPool.new(4)
43
71
  @pool = Concurrent::FixedThreadPool.new(1)
44
72
  sectionsplit1(xml, empty, empty1, 0)
45
73
  end
46
74
 
75
+ # xml is Presentation XML
47
76
  def sectionsplit1(xml, empty, empty1, idx)
48
77
  ret = SPLITSECTIONS.each_with_object([]) do |n, m|
49
78
  conflate_floatingtitles(xml.xpath(ns(n[0]))).each do |s|
79
+ # require "debug"; binding.b
50
80
  sectionsplit2(xml, idx.zero? ? empty : empty1, s, n[1],
51
81
  { acc: m, idx: idx })
52
82
  idx += 1
@@ -59,16 +89,23 @@ module Metanorma
59
89
 
60
90
  def sectionsplit2(xml, empty, chunks, parentnode, opt)
61
91
  @pool.post do
62
- a = sectionfile(xml, empty, "#{@base}.#{opt[:idx]}", chunks,
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,
63
99
  parentnode)
64
100
  @mutex.synchronize { opt[:acc] << a }
65
101
  end
66
102
  end
67
103
 
104
+ # TODO move to metanorma-utils
68
105
  def block?(node)
69
106
  %w(p table formula admonition ol ul dl figure quote sourcecode example
70
107
  pre note pagebreak hr bookmark requirement recommendation permission
71
- svgmap inputform toc passthrough review imagemap).include?(node.name)
108
+ svgmap inputform toc passthrough annotation imagemap).include?(node.name)
72
109
  end
73
110
 
74
111
  def conflate_floatingtitles(nodes)
@@ -86,7 +123,6 @@ module Metanorma
86
123
  xml, type = sectionsplit_preprocess_semxml(file, filename)
87
124
  flags = { format: :asciidoc, extension_keys: [:presentation],
88
125
  type: type }.merge(@compile_opts)
89
- #require "debug"; binding.b
90
126
  Compile.new.compile(xml, flags)
91
127
  f = File.open(xml.sub(/\.xml$/, ".presentation.xml"), encoding: "utf-8")
92
128
  r = Nokogiri::XML(f, &:huge)
@@ -95,6 +131,16 @@ module Metanorma
95
131
  r
96
132
  end
97
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
+
98
144
  def sectionsplit_preprocess_semxml(file, filename)
99
145
  xml = Nokogiri::XML(file, &:huge)
100
146
  type = xml.root["flavor"]
@@ -107,11 +153,8 @@ module Metanorma
107
153
 
108
154
  def sectionsplit_update_xrefs(xml)
109
155
  if c = @fileslookup&.parent
110
- n = c.nested
111
- c.nested = true # so unresolved erefs are not deleted
112
- #require "debug"; binding.b
113
- c.update_xrefs(xml, @ident, {})
114
- c.nested = n
156
+ # nested mode so unresolved erefs are not deleted
157
+ c.with_nested { c.update_xrefs(xml, @ident, {}) }
115
158
  xml.xpath("//xmlns:svgmap").each { |x| x.name = "svgmap1" }
116
159
  # do not process svgmap until after files are split
117
160
  end
@@ -125,35 +168,19 @@ module Metanorma
125
168
  outname
126
169
  end
127
170
 
128
- def emptydoc(xml, ordinal)
129
- out = xml.dup
130
- out.xpath(
131
- ns("//preface | //sections | //annex | //bibliography/clause | " \
132
- "//bibliography/references[not(@hidden = 'true')] | " \
133
- "//indexsect | //colophon"),
134
- ).each(&:remove)
135
- ordinal.zero? or out.xpath(ns("//metanorma-ext//attachment | " \
136
- "//semantic__metanorma-ext//semantic__attachment"))
137
- .each(&:remove) # keep only one copy of attachments
138
- out
139
- end
140
-
141
171
  def empty_doc(xml)
142
172
  out = xml.dup
143
173
  out.xpath(
144
- ns("//preface | //sections | //annex | //bibliography/clause | " \
145
- "//bibliography/references[not(@hidden = 'true')] | " \
146
- "//indexsect | //colophon"),
174
+ ns("//preface | //sections | //annex | " \
175
+ "//references/bibitem[not(@hidden = 'true')] | " \
176
+ "//indexsect | //colophon"),
147
177
  ).each(&:remove)
178
+ ::Metanorma::Collection::Util::hide_refs(out)
148
179
  out
149
180
  end
150
181
 
151
182
  def empty_attachments(xml)
152
- out = xml.dup
153
- out.xpath(ns("//metanorma-ext//attachment | " \
154
- "//semantic__metanorma-ext//semantic__attachment"))
155
- .each(&:remove) # keep only one copy of attachments
156
- out
183
+ xml.dup
157
184
  end
158
185
 
159
186
  def sectionfile(fulldoc, xml, file, chunks, parentnode)
@@ -164,48 +191,101 @@ module Metanorma
164
191
 
165
192
  def create_sectionfile(xml, out, file, chunks, parentnode)
166
193
  ins = out.at(ns("//metanorma-extension")) || out.at(ns("//bibdata"))
167
- #require "debug"; binding.b
168
194
  sectionfile_insert(ins, chunks, parentnode)
195
+ sectionfile_fn_filter(sectionfile_annotation_filter(out))
169
196
  Metanorma::Collection::XrefProcess::xref_process(out, xml, @key,
170
197
  @ident, @isodoc, true)
171
- #truncate_semxml(out, chunks)
172
- outname = "#{file}.xml"
173
- File.open(File.join(@splitdir, outname), "w:UTF-8") do |f|
174
- f.write(out)
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"
206
+ end
207
+
208
+ def sectionfile_insert(ins, chunks, parentnode)
209
+ if parentnode
210
+ ins.next = "<#{parentnode}/>"
211
+ chunks.each { |c| ins.next.add_child(c.dup) }
212
+ else chunks.each { |c| ins.next = c.dup }
175
213
  end
176
- outname
177
214
  end
178
215
 
179
- # KILL
180
- def semantic_xml_ids_gather(out)
181
- out.at(ns("//semantic__bibdata")) or return
182
- SPLITSECTIONS.each_with_object({}) do |s, m|
183
- out.xpath(ns(s[0].sub("//", "//semantic__"))).each do |x|
184
- x["id"] or next
185
- m[x["id"].sub(/^semantic__/, "")] = x
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)
186
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
187
234
  end
188
235
  end
189
236
 
190
- # KILL
191
- def semxml_presxml_nodes_match(nodes, chunks)
192
- chunks.each do |x|
193
- nodes[x["id"]] and nodes.delete(x["id"])
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)
194
249
  end
195
250
  end
196
251
 
197
- # KILL
198
- def truncate_semxml(out, chunks)
199
- nodes = semantic_xml_ids_gather(out) or return
200
- semxml_presxml_nodes_match(nodes, chunks)
201
- nodes.each_value(&:remove)
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)
202
256
  end
203
257
 
204
- def sectionfile_insert(ins, chunks, parentnode)
205
- if parentnode
206
- ins.next = "<#{parentnode}/>"
207
- chunks.each { |c| ins.next.add_child(c.dup) }
208
- else chunks.each { |c| ins.next = c.dup }
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
209
289
  end
210
290
  end
211
291
 
@@ -214,7 +294,7 @@ module Metanorma
214
294
  t = title.dup
215
295
  t.xpath(ns(".//tab | .//br")).each { |x| x.replace(" ") }
216
296
  t.xpath(ns(".//bookmark")).each(&:remove)
217
- t.xpath('.//text()').map(&:text).join
297
+ t.xpath(".//text()").map(&:text).join
218
298
  end
219
299
  end
220
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,22 +1,34 @@
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
30
  def gather_bibitemids(xml, presxml)
18
31
  xml.xpath("//*[@bibitemid]").each_with_object({}) do |e, m|
19
- #/^semantic__/.match?(e.name) and next
20
32
  presxml && %w(xref eref link).include?(e.name) and next
21
33
  m[e["bibitemid"]] ||= []
22
34
  m[e["bibitemid"]] << e
@@ -25,7 +37,6 @@ module Metanorma
25
37
 
26
38
  def gather_citeases(xml, presxml)
27
39
  xml.xpath("//*[@citeas]").each_with_object({}) do |e, m|
28
- #/^semantic__/.match?(e.name) and next
29
40
  presxml && %w(xref eref link).include?(e.name) and next
30
41
  k = key(e["citeas"])
31
42
  m[k] ||= []
@@ -33,12 +44,14 @@ module Metanorma
33
44
  end
34
45
  end
35
46
 
36
- def add_suffix_to_attrs(doc, suffix, tag_name, attr_name, isodoc)
47
+ # Skip SVGs, they are processed by Vectory::SvgDocument#suffix_ids
48
+ def add_suffix_to_attrs(doc, suffix, tag, attr, isodoc)
37
49
  (suffix.nil? || suffix.empty?) and return
38
- doc.xpath(isodoc.ns("//#{tag_name}[@#{attr_name}]")).each do |elem|
39
- 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
40
53
  /_#{suffix}$/.match?(a) or
41
- elem.attributes[attr_name].value = "#{a}_#{suffix}"
54
+ elem.attributes[attr].value = "#{a}_#{suffix}"
42
55
  end
43
56
  end
44
57
 
@@ -55,32 +68,143 @@ module Metanorma
55
68
  p.absolute? ? path : File.join(dir, path)
56
69
  end
57
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
+
58
79
  def key(ident)
59
80
  @c ||= HTMLEntities.new
60
81
  @c.decode(ident).gsub(/(\p{Zs})+/, " ")
61
82
  end
62
83
 
63
- class Dummy
64
- 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
65
98
  end
66
99
 
67
- def load_isodoc(flavor, presxml: false)
68
- x = Asciidoctor.load nil, backend: flavor.to_sym
69
- if presxml
70
- x.converter.presentation_xml_converter(Dummy.new)
71
- else
72
- x.converter.html_converter(Dummy.new) # to obtain Isodoc class
100
+ def taste2flavor(taste)
101
+ tastes = Metanorma::TasteRegister.instance.aliases
102
+ tastes[taste.to_sym] and taste = tastes[taste.to_sym]
103
+ taste
104
+ end
105
+
106
+ def taste2isodoc_attrs(taste, format)
107
+ tastes = Metanorma::TasteRegister.instance.aliases
108
+ tastes[taste.to_sym] or return {}
109
+ Metanorma::TasteRegister.isodoc_attrs(taste.to_sym, format)
110
+ end
111
+
112
+ def taste2coverpage_pdf_portfolio(taste)
113
+ tastes = Metanorma::TasteRegister.instance.aliases
114
+ tastes[taste.to_sym] or return nil
115
+ taste = Metanorma::TasteRegister.instance.get(taste.to_sym)
116
+ ret = taste.config.base_override&.filename_attributes
117
+ &.coverpage_pdf_portfolio or return
118
+ File.join(taste.directory, ret)
119
+ end
120
+
121
+ SVG_NS = "http://www.w3.org/2000/svg".freeze
122
+
123
+ # update relative URLs, url(#...), in CSS in @style attrs (incl. SVG),
124
+ # and in include SVG url(#..) attrs,
125
+ # not processed already by add_suffix_to_attrs
126
+ def url_in_css_styles(doc, ids, document_suffix)
127
+ update_ids_css(doc.root, ids, document_suffix)
128
+ doc.xpath("//i:svg", "i" => SVG_NS).each do |s|
129
+ svg = Vectory::SvgDocument.new(s.to_xml)
130
+ svg.suffix_ids(document_suffix)
73
131
  end
74
132
  end
75
133
 
134
+ # replicates Vectory update_ids_css, but skips over svgs,
135
+ # which are processed separately by Vectory::SvgDocument#suffix_ids
136
+ def update_ids_css(document, ids, suffix)
137
+ suffix = suffix.is_a?(Integer) ? sprintf("%09d", suffix) : suffix
138
+ document.xpath(".//m:style[not(ancestor::i:svg)]",
139
+ "m" => SVG_NS, "i" => SVG_NS).each do |s|
140
+ s.children = update_ids_css_string(s.children.to_xml, ids, suffix)
141
+ end
142
+ document.xpath(".//*[@style][not(ancestor::i:svg)]",
143
+ "i" => SVG_NS).each do |s|
144
+ s["style"] = update_ids_css_string(s["style"], ids, suffix)
145
+ end
146
+ end
147
+
148
+ # Updates ID references in a CSS style string.
149
+ # Replicates Vectory update_ids_css_string
150
+ def update_ids_css_string(style, ids, suffix)
151
+ ids.each do |i|
152
+ style = style.gsub(%r[##{i}\b],
153
+ sprintf("#%<id>s_%<suffix>s", id: i,
154
+ suffix: suffix))
155
+ .gsub(%r(\[id\s*=\s*['"]?#{i}['"]?\]),
156
+ sprintf("[id='%<id>s_%<suffix>s']", id: i,
157
+ suffix: suffix))
158
+ end
159
+ style
160
+ end
161
+
162
+ # doc.xpath("//*[@style]").each do |s|
163
+ # s["style"] = url_in_css_styles1(s["style"], document_suffix)
164
+ # end
165
+ # doc.xpath("//i:svg//i:style", "i" => "http://www.w3.org/2000/svg")
166
+ # .each do |s|
167
+ # s.children = url_in_css_styles1(s.text, document_suffix)
168
+ # end
169
+ #
170
+ # # KILL
171
+ # def url_in_css_styles1(style, document_suffix)
172
+ # style.gsub(%r{url\(#([^()]+)\)}, "url(#\\1_#{document_suffix})")
173
+ # end
174
+
175
+ # @deprecated Call Metanorma::Core::Isodoc.resolve_converter directly.
176
+ def load_isodoc(flavor, presxml: false)
177
+ Metanorma::Core::Isodoc.resolve_converter(flavor, presxml: presxml)
178
+ end
179
+
180
+ # @deprecated Call Metanorma::Core::Isodoc.create directly. Kept as
181
+ # a back-compat shim so existing callers keep working unchanged
182
+ # while the wrapper is retired.
76
183
  def isodoc_create(flavor, lang, script, xml, presxml: false)
77
- isodoc = Util::load_isodoc(flavor, presxml: presxml)
78
- isodoc.i18n_init(lang, script, nil) # read in internationalisation
79
- # TODO locale?
80
- isodoc.metadata_init(lang, script, nil, isodoc.i18n)
81
- isodoc.info(xml, nil)
82
- isodoc
83
- end
184
+ Metanorma::Core::Isodoc.create(flavor, lang: lang, script: script,
185
+ xml: xml, presxml: presxml)
186
+ end
187
+
188
+ # Dummy asciidoc header used by the prefatory-content parse so we
189
+ # can run Asciidoctor against arbitrary text. If +docidentifier+
190
+ # is supplied, it is injected as +:docidentifier:+ so the flavor
191
+ # processor's metadata_id has a real value to put into the
192
+ # bibdata, instead of falling back to its (Liquid-templated)
193
+ # docid_template. This prevents the prefatory parse from feeding
194
+ # an unresolved Liquid template through Relaton/pubid in flavors
195
+ # whose relaton-* gem eagerly parses the docidentifier in
196
+ # content= (issue #558).
197
+ def asciidoc_dummy_header(docidentifier: nil)
198
+ attrs = docidentifier ? ":docidentifier: #{docidentifier}\n" : ""
199
+ "= X\nA\n#{attrs}\n"
200
+ end
201
+
202
+ def nokogiri_to_temp(xml, filename, suffix)
203
+ file = Tempfile.new([filename, suffix])
204
+ file.write(xml.to_xml(indent: 0))
205
+ file.close
206
+ [file, file.path]
207
+ end
84
208
  end
85
209
  end
86
210
  end
@@ -10,8 +10,7 @@ module Metanorma
10
10
  @isodoc = isodoc
11
11
  key = (0...8).map { rand(65..90).chr }.join # random string
12
12
  xml.root["type"] = key
13
- Metanorma::Utils::anchor_attributes
14
- .each do |(tag_name, attr_name)|
13
+ Util::anchor_id_attributes.each do |(tag_name, attr_name)|
15
14
  #tag_name == "xref" and tag_name = "fmt-xref"
16
15
  ::Metanorma::Collection::Util::add_suffix_to_attrs(
17
16
  xml, xml.root["document_suffix"], tag_name, attr_name, isodoc
@@ -37,7 +36,7 @@ module Metanorma
37
36
  def svg_preprocess(xml, doc_suffix, presxml)
38
37
  suffix = doc_suffix.nil? || doc_suffix.blank? ? "" : "_#{doc_suffix}"
39
38
  xml.xpath("//m:svg", "m" => "http://www.w3.org/2000/svg").each do |s|
40
- m = svgmap_wrap(s)
39
+ m = svgmap_wrap(s) or next
41
40
  svg_xrefs(s, m, suffix, presxml)
42
41
  end
43
42
  xml
@@ -45,7 +44,7 @@ module Metanorma
45
44
 
46
45
  def svgmap_wrap(svg)
47
46
  ret = svg.at("./ancestor::xmlns:svgmap") and return ret
48
- ret = svg.at("./ancestor::xmlns:figure")
47
+ ret = svg.at("./ancestor::xmlns:figure") or return
49
48
  ret.wrap("<svgmap/>")
50
49
  svg.at("./ancestor::xmlns:svgmap")
51
50
  end
@@ -76,7 +75,7 @@ module Metanorma
76
75
  xref_prefix_key(x, key, indirect)
77
76
  x["bibitemid"] = x["target"]
78
77
  m[x["bibitemid"]] = true
79
- xref_to_internal_eref_anchor(x, key, bibitems,
78
+ xref_to_internal_eref_anchor(x, bibitems,
80
79
  xml.root["document_suffix"], presxml)
81
80
  end.keys
82
81
  end
@@ -88,8 +87,9 @@ module Metanorma
88
87
  [bibitems, indirect_bibitems]
89
88
  end
90
89
 
91
- def xref_to_internal_eref_anchor(xref, key, bibitems, document_suffix, presxml)
90
+ def xref_to_internal_eref_anchor(xref, bibitems, document_suffix, presxml)
92
91
  t = xref["target"]
92
+ key = xref["type"]
93
93
  if d = bibitems[t]&.at(ns("./docidentifier[@type = 'repository']"))
94
94
  m = %r{^([^/]+)}.match(d.text) and
95
95
  t.sub!(%r(#{m[0]}_), "")
@@ -225,7 +225,7 @@ module Metanorma
225
225
 
226
226
  def new_indirect_bibitem(ident, prefix)
227
227
  <<~BIBENTRY
228
- <bibitem id="#{ident}" type="internal">
228
+ <bibitem id="#{ident}" anchor="#{ident}" type="internal">
229
229
  <docidentifier type="repository">#{ident.sub(/^#{prefix}_/, "#{prefix}/")}</docidentifier>
230
230
  </bibitem>
231
231
  BIBENTRY