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,25 +1,31 @@
1
+ require "marcel"
2
+
1
3
  module Metanorma
2
4
  class Collection
3
5
  class Renderer
4
6
  def dir_name_cleanse(name)
5
7
  path = Pathname.new(name)
6
- clean_regex = /[<>:"|?*]/
8
+ clean_regex = /[<>:"|?*\p{Zs}]/
7
9
  fallback_sym = "_"
8
- return name.gsub(clean_regex, fallback_sym) unless path.absolute?
9
-
10
+ path.absolute? or return name.gsub(clean_regex, fallback_sym)
10
11
  File.join(path.dirname,
11
12
  path.basename.to_s.gsub(clean_regex, fallback_sym))
12
13
  end
13
14
 
14
15
  def dup_bibitem(docid, bib)
15
- newbib = @files.get(docid, :bibdata).dup
16
+ newbib = deep_detached_clone(@files.get(docid, :bibdata))
16
17
  newbib.name = "bibitem"
17
18
  newbib["hidden"] = "true"
18
19
  newbib&.at("./*[local-name() = 'ext']")&.remove
19
20
  newbib["id"] = bib["id"]
21
+ bib["anchor"] and newbib["anchor"] = bib["anchor"]
20
22
  newbib
21
23
  end
22
24
 
25
+ def deep_detached_clone(node)
26
+ Nokogiri::XML(node.to_xml).root
27
+ end
28
+
23
29
  def get_bibitem_docid(bib, identifier)
24
30
  docid =
25
31
  bib.at(ns("./docidentifier[@type = 'metanorma-collection']")) ||
@@ -33,11 +39,11 @@ module Metanorma
33
39
  end
34
40
  end
35
41
 
36
- def hide_refs(docxml)
37
- docxml.xpath(ns("//references[bibitem][not(./bibitem[not(@hidden) or " \
38
- "@hidden = 'false'])]")).each do |f|
39
- f["hidden"] = "true"
40
- end
42
+ def new_hidden_ref(xml)
43
+ ins = xml.at(ns("bibliography")) or
44
+ xml.root << "<bibliography/>" and ins = xml.at(ns("bibliography"))
45
+ ins.at(ns("./references[@hidden = 'true']")) or
46
+ ins.add_child("<references hidden='true' normative='false'/>").first
41
47
  end
42
48
 
43
49
  def strip_eref(eref)
@@ -48,11 +54,12 @@ module Metanorma
48
54
  def docid_to_citeas(bib)
49
55
  docid = bib.at(ns("./docidentifier[@primary = 'true']")) ||
50
56
  bib.at(ns("./docidentifier")) or return
51
- docid_prefix(docid)
57
+ ::Metanorma::Collection::Util::key(docid_prefix(docid))
52
58
  end
53
59
 
54
- def collect_erefs(docxml)
55
- docxml.xpath(ns("//eref"))
60
+ def collect_erefs(docxml, presxml)
61
+ tag = presxml ? "fmt-eref" : "eref"
62
+ docxml.xpath(ns("//#{tag}"))
56
63
  .each_with_object({ citeas: {}, bibitemid: {} }) do |i, m|
57
64
  m[:citeas][i["citeas"]] = true
58
65
  m[:bibitemid][i["bibitemid"]] = true
@@ -64,16 +71,35 @@ module Metanorma
64
71
  "#{val}</docidentifier>"
65
72
  end
66
73
 
67
- def add_hidden_bibliography(xmldoc, refs)
68
- ins = new_hidden_ref(xmldoc)
74
+ def add_hidden_bibliography(xml, refs, current_id = nil)
75
+ ins = new_hidden_ref(xml)
76
+ current_html = if current_id
77
+ @files.get(current_id,
78
+ :outputs)&.dig(:html)
79
+ else
80
+ nil
81
+ end
69
82
  refs.each do |k, v|
70
83
  url = @files.url(v, {})
84
+ url = make_relative_path(current_html, url) if current_html
71
85
  ins << <<~XML
72
- <bibitem id="#{k}">#{docid_xml(v)}<uri type='citation'>#{url}</uri></bibitem>
86
+ <bibitem id="#{k}" anchor="#{k}">#{docid_xml(v)}<uri type='citation'>#{url}</uri></bibitem>
73
87
  XML
74
88
  end
75
89
  end
76
90
 
91
+ def make_relative_path(from_file, to_file)
92
+ return to_file if to_file.nil? || to_file.to_s.empty?
93
+ return to_file if to_file.start_with?("http://", "https://", "#")
94
+
95
+ from_dir = File.dirname(from_file)
96
+ return to_file if from_dir == "."
97
+
98
+ from_path = Pathname.new(from_dir)
99
+ to_path = Pathname.new(to_file)
100
+ to_path.relative_path_from(from_path).to_s
101
+ end
102
+
77
103
  private
78
104
 
79
105
  def docid_prefix(docid)
@@ -100,65 +126,96 @@ module Metanorma
100
126
  # @raise [ArgumentError]
101
127
  def check_options(options)
102
128
  (options[:format].is_a?(Array) && (FORMATS & options[:format]).any?) or
103
- raise ArgumentError, "Need to specify formats (xml,html,pdf,doc)"
129
+ raise ArgumentError,
130
+ "Need to specify formats (xml,html,pdf,pdf-portfolio,doc)"
104
131
  end
105
132
 
106
- def pdfconv
107
- doctype = @doctype.to_sym
108
- x = Asciidoctor.load nil, backend: doctype
109
- x.converter.pdf_converter(PdfOptionsNode.new(doctype,
110
- @compile_options))
133
+ def pdfconv(added_options)
134
+ flavor, taste_opts, x = pdfconv_prep
135
+ fonts = [taste_opts[:fonts], added_options[:fonts]].compact.flatten
136
+ .join(";")
137
+ opts = @compile_options.merge(added_options.merge(taste_opts))
138
+ opts[:fonts] = fonts
139
+ x.converter.pdf_converter(PdfOptionsNode.new(flavor, opts))
140
+ end
141
+
142
+ def pdfconv_prep
143
+ flavor = Util::taste2flavor(@flavor).to_sym
144
+ taste_opts = Util::taste2isodoc_attrs(@flavor, :pdf)
145
+ x = Asciidoctor.load nil, backend: flavor
146
+ [flavor, taste_opts, x]
111
147
  end
112
148
 
113
149
  def fail_update_bibitem(docid, identifier)
114
150
  error = "[metanorma] Cannot find crossreference to document #{docid} " \
115
151
  "in document #{identifier}."
116
- @log&.add("Cross-References", nil, error)
152
+ @log&.add("METANORMA_2", nil, params: [docid, identifier])
117
153
  ::Metanorma::Util.log(error, :warning)
118
154
  end
119
155
 
120
156
  def datauri_encode(docxml, directory)
121
157
  docxml.xpath(ns("//image")).each do |i|
158
+ read_in_if_svg(i, directory.sub(%r{(?<!=/)$}, "/"))
159
+ end
160
+ docxml.xpath(ns("//image")).each do |i| # rubocop:disable Style/CombinableLoops
161
+ (i["src"] && !i["src"].empty?) or next
122
162
  i["src"] = Vectory::Utils::datauri(i["src"], directory)
123
163
  end
124
164
  docxml
125
165
  end
126
166
 
167
+ def read_in_if_svg(img, localdir)
168
+ img["src"] or return
169
+ if img.elements.map(&:name).include?("svg")
170
+ img["src"] = nil
171
+ return
172
+ end
173
+ path = Vectory::Utils.svgmap_rewrite0_path(img["src"], localdir)
174
+ svg = svg_in_path?(path) or return
175
+ img.children = (Nokogiri::XML(svg).root)
176
+ img["src"] = nil
177
+ true
178
+ end
179
+
180
+ def svg_in_path?(path)
181
+ File.file?(path) or return false
182
+ type = Marcel::MimeType.for(name: path) or return false
183
+ type == "image/svg+xml" or return false
184
+ svg = File.read(path, encoding: "utf-8") or return false
185
+ svg
186
+ end
187
+
127
188
  class PdfOptionsNode
128
- def initialize(doctype, options)
129
- p = Metanorma::Registry.instance.find_processor(doctype)
189
+ def initialize(flavor, options)
190
+ p = Metanorma::Registry.instance.find_processor(Util::taste2flavor(flavor))
130
191
  if ::Metanorma::Util::FontistHelper.has_custom_fonts?(p, options, {})
131
192
  @fonts_manifest =
132
193
  ::Metanorma::Util::FontistHelper.location_manifest(p, options)
133
194
  end
195
+ @options = options
134
196
  end
135
197
 
136
198
  def attr(key)
137
199
  if key == "fonts-manifest" && @fonts_manifest
138
200
  @fonts_manifest
201
+ else @options[key.to_sym]
139
202
  end
140
203
  end
141
204
  end
142
205
 
143
- def isodoc_create
144
- isodoc = Util::load_isodoc(@doctype)
145
- isodoc.i18n_init(@lang, @script, @locale) # read in internationalisation
146
- isodoc.metadata_init(@lang, @script, @locale, isodoc.i18n)
147
- isodoc.info(@xml, nil)
148
- isodoc
149
- end
150
-
151
206
  # create the @meta class of isodoc, for populating Liquid,
152
207
  # with "navigation" set to the index bar.
153
208
  # extracted from the manifest
154
209
  def isodoc_populate
155
210
  @isodoc.info(@xml, nil)
156
211
  { navigation: indexfile(@manifest), nav_object: index_object(@manifest),
212
+ bibdata: YAML.safe_load(@bibdata.to_yaml,
213
+ permitted_classes: [Date, Symbol]),
157
214
  docrefs: liquid_docrefs(@manifest),
158
- "prefatory-content": isodoc_builder(@xml.at("//prefatory-content")),
159
- "final-content": isodoc_builder(@xml.at("//final-content")),
160
- doctitle: @bibdata.title.first.title.content,
161
- docnumber: @bibdata.docidentifier.first.id }.each do |k, v|
215
+ "prefatory-content": isodoc_builder(@xml.at("//xmlns:prefatory-content")),
216
+ "final-content": isodoc_builder(@xml.at("//xmlns:final-content")),
217
+ doctitle: Array(@bibdata.title).first&.content,
218
+ docnumber: Array(@bibdata.docidentifier).first&.content }.each do |k, v|
162
219
  v and @isodoc.meta.set(k, v)
163
220
  end
164
221
  end
@@ -166,15 +223,29 @@ module Metanorma
166
223
  def isodoc_builder(node)
167
224
  node or return
168
225
 
169
- # Kludging namespace back in because of Shale brain damage
170
- doc = Nokogiri::XML(node.to_xml.sub(">", " xmlns='http://www.metanorma.org'>"))
171
226
  Nokogiri::HTML::Builder.new(encoding: "UTF-8") do |b|
172
227
  b.div do |div|
173
- doc.root.children&.each { |n| @isodoc.parse(n, div) }
228
+ node.children.each { |n| @isodoc.parse(n, div) }
174
229
  end
175
230
  end.doc.root.to_html
176
231
  end
177
232
 
233
+ def eref2link(docxml, _presxml)
234
+ isodoc = IsoDoc::PresentationXMLConvert.new({})
235
+ isodoc.bibitem_lookup(docxml)
236
+ isodoc.eref2link(docxml)
237
+ end
238
+
239
+ def error_anchor(erefs, docid)
240
+ erefs.each do |e|
241
+ msg = "<strong>** Unresolved reference to document #{docid} " \
242
+ "from eref</strong>"
243
+ e << msg
244
+ strip_eref(e)
245
+ @log&.add("METANORMA_3", e, params: [docid])
246
+ end
247
+ end
248
+
178
249
  def ns(xpath)
179
250
  @isodoc.ns(xpath)
180
251
  end
@@ -0,0 +1,110 @@
1
+ module Metanorma
2
+ class Collection
3
+ class Sectionsplit
4
+ def build_collection
5
+ collection_setup(@base, @dir)
6
+ files = sectionsplit
7
+ input_xml = Nokogiri::XML(File.read(@input_filename,
8
+ encoding: "UTF-8"), &:huge)
9
+ collection_manifest(@base, files, input_xml, @xml, @dir).render(
10
+ { format: %i(html), output_folder: "#{@output_filename}_collection",
11
+ coverpage: File.join(@dir, "cover.html") }.merge(@compile_opts),
12
+ )
13
+ section_split_attachments(out: "#{@output_filename}_collection")
14
+ end
15
+
16
+ def collection_setup(filename, dir)
17
+ FileUtils.mkdir_p "#{filename}_collection" if filename
18
+ FileUtils.mkdir_p dir
19
+ File.open(File.join(dir, "cover.html"), "w:UTF-8") do |f|
20
+ f.write(coll_cover)
21
+ end
22
+ end
23
+
24
+ def coll_cover
25
+ <<~COVER
26
+ <html><head><meta charset="UTF-8"/></head><body>
27
+ <h1>{{ doctitle }}</h1>
28
+ <h2>{{ docnumber }}</h2>
29
+ <nav>{{ navigation }}</nav>
30
+ </body></html>
31
+ COVER
32
+ end
33
+
34
+ def collection_manifest(filename, files, origxml, _presxml, dir)
35
+ File.open(File.join(dir, "#{filename}.html.yaml"), "w:UTF-8") do |f|
36
+ f.write(collectionyaml(files, origxml))
37
+ end
38
+ Metanorma::Collection.parse File.join(dir, "#{filename}.html.yaml")
39
+ end
40
+
41
+ def collectionyaml(files, xml)
42
+ ret = {
43
+ directives: ["presentation-xml", "bare-after-first"],
44
+ bibdata: {
45
+ title: {
46
+ type: "title-main", language: @lang,
47
+ content: xml.at(ns("//bibdata/title"))&.text || "[TITLE]"
48
+ },
49
+ type: "collection",
50
+ docid: {
51
+ type: xml.at(ns("//bibdata/docidentifier/@type"))&.text || "[FLAVOR]",
52
+ id: xml.at(ns("//bibdata/docidentifier"))&.text || "[DOCID]",
53
+ },
54
+ },
55
+ manifest: {
56
+ level: "collection", title: "Collection",
57
+ docref: files.sort_by { |f| f[:order] }.each.map do |f|
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
69
+ end
70
+ },
71
+ }
72
+ ::Metanorma::Util::recursive_string_keys(ret).to_yaml
73
+ end
74
+
75
+ def att_dir(file)
76
+ "_#{File.basename(file, '.*')}_attachments"
77
+ end
78
+
79
+ def section_split_attachments(out: nil)
80
+ attachments = att_dir(@tmp_filename)
81
+ File.directory?(attachments) or return
82
+ dir = out || File.dirname(@input_filename)
83
+ ret = File.join(dir, att_dir(@output_filename))
84
+ FileUtils.rm_rf ret
85
+ FileUtils.mv attachments, ret
86
+ File.basename(ret)
87
+ end
88
+
89
+ def section_split_cover(col, ident, one_doc_coll)
90
+ dir = File.dirname(col.file)
91
+ collection_setup(nil, dir)
92
+ r = ::Metanorma::Collection::Renderer
93
+ .new(col, dir, output_folder: "#{ident}_collection",
94
+ format: %i(html),
95
+ coverpage: File.join(dir, "cover.html"))
96
+ r.coverpage
97
+ section_split_cover1(ident, r, dir, one_doc_coll)
98
+ end
99
+
100
+ def section_split_cover1(ident, renderer, dir, _one_doc_coll)
101
+ filename = File.basename("#{ident}_index.html")
102
+ # ident can be a directory with YAML indirection
103
+ dest = File.join(dir, filename)
104
+ FileUtils.mv File.join(renderer.outdir, "index.html"), dest
105
+ FileUtils.rm_rf renderer.outdir
106
+ filename
107
+ end
108
+ end
109
+ end
110
+ end