metanorma 1.7.6 → 2.0.0

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -1
  3. data/lib/metanorma/array_monkeypatch.rb +9 -0
  4. data/lib/metanorma/asciidoctor_extensions/glob_include_processor.rb +13 -11
  5. data/lib/metanorma/collection/collection.rb +225 -0
  6. data/lib/metanorma/collection/config/bibdata.rb +12 -0
  7. data/lib/metanorma/collection/config/compile_options.rb +13 -0
  8. data/lib/metanorma/collection/config/config.rb +163 -0
  9. data/lib/metanorma/collection/config/converters.rb +30 -0
  10. data/lib/metanorma/collection/config/directive.rb +10 -0
  11. data/lib/metanorma/collection/config/manifest.rb +88 -0
  12. data/lib/metanorma/collection/document/document.rb +133 -0
  13. data/lib/metanorma/collection/filelookup/filelookup.rb +250 -0
  14. data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +87 -0
  15. data/lib/metanorma/collection/manifest/manifest.rb +237 -0
  16. data/lib/metanorma/collection/renderer/fileparse.rb +247 -0
  17. data/lib/metanorma/collection/renderer/fileprocess.rb +173 -0
  18. data/lib/metanorma/collection/renderer/navigation.rb +133 -0
  19. data/lib/metanorma/collection/renderer/render_word.rb +133 -0
  20. data/lib/metanorma/collection/renderer/renderer.rb +157 -0
  21. data/lib/metanorma/collection/renderer/utils.rb +183 -0
  22. data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +218 -0
  23. data/lib/metanorma/collection/util/disambig_files.rb +37 -0
  24. data/lib/metanorma/collection/util/util.rb +72 -0
  25. data/lib/metanorma/collection/xrefprocess/xrefprocess.rb +222 -0
  26. data/lib/metanorma/{compile.rb → compile/compile.rb} +17 -11
  27. data/lib/metanorma/{compile_options.rb → compile/compile_options.rb} +9 -5
  28. data/lib/metanorma/{compile_validate.rb → compile/compile_validate.rb} +1 -1
  29. data/lib/metanorma/{extract.rb → compile/extract.rb} +2 -2
  30. data/lib/metanorma/{config.rb → config/config.rb} +1 -1
  31. data/lib/metanorma/input/asciidoc.rb +3 -3
  32. data/lib/metanorma/input/base.rb +1 -5
  33. data/lib/metanorma/processor/processor.rb +54 -0
  34. data/lib/metanorma/processor.rb +1 -49
  35. data/lib/metanorma/{registry.rb → registry/registry.rb} +0 -1
  36. data/lib/metanorma/shale_monkeypatch.rb +15 -0
  37. data/lib/metanorma/util/fontist_helper.rb +130 -0
  38. data/lib/metanorma/util/util.rb +45 -0
  39. data/lib/metanorma/util/worker_pool.rb +39 -0
  40. data/lib/metanorma/version.rb +1 -1
  41. data/lib/metanorma.rb +13 -8
  42. data/metanorma.gemspec +2 -1
  43. metadata +51 -26
  44. data/Gemfile.devel +0 -2
  45. data/lib/metanorma/collection.rb +0 -243
  46. data/lib/metanorma/collection_fileparse.rb +0 -254
  47. data/lib/metanorma/collection_fileprocess.rb +0 -157
  48. data/lib/metanorma/collection_manifest.rb +0 -139
  49. data/lib/metanorma/collection_render_utils.rb +0 -169
  50. data/lib/metanorma/collection_render_word.rb +0 -131
  51. data/lib/metanorma/collection_renderer.rb +0 -230
  52. data/lib/metanorma/document.rb +0 -133
  53. data/lib/metanorma/files_lookup.rb +0 -208
  54. data/lib/metanorma/files_lookup_sectionsplit.rb +0 -84
  55. data/lib/metanorma/fontist_utils.rb +0 -122
  56. data/lib/metanorma/sectionsplit.rb +0 -216
  57. data/lib/metanorma/sectionsplit_links.rb +0 -189
  58. data/lib/metanorma/util.rb +0 -127
  59. data/lib/metanorma/worker_pool.rb +0 -29
@@ -0,0 +1,247 @@
1
+ module Metanorma
2
+ class Collection
3
+ class Renderer
4
+ # Resolves references to other files in the collection. Three routines:
5
+ # 1. Eref to a document that has been split into multiple documents
6
+ # (sectionsplit) are resolved to direct eref to the split document
7
+ # 2. Indirect erefs to a file anchor in an unknwon file in the collection
8
+ # (bibitem[@type = 'internal'] ) are resolved to direct eref to the
9
+ # containing document
10
+ # 3. Direct erefs to other files in collection
11
+ # (repo(current-metanorma-collection/x) are resolved to hyperlinks
12
+ # @param file [String] XML content
13
+ # @param identifier [String] docid
14
+ # @param internal_refs [Hash{String=>Hash{String=>String}] schema name to
15
+ # anchor to filename
16
+ # @return [String] XML content
17
+ def update_xrefs(file, docid, internal_refs)
18
+ xml, sso = update_xrefs_prep(file, docid)
19
+ @nested || sso or
20
+ Metanorma::Collection::XrefProcess::xref_process(xml, xml, nil, docid,
21
+ @isodoc)
22
+ @nested or update_indirect_refs_to_docs(xml, docid, internal_refs)
23
+ @files.add_document_suffix(docid, xml)
24
+ @nested or update_sectionsplit_refs_to_docs(xml, internal_refs)
25
+ update_direct_refs_to_docs(xml, docid)
26
+ hide_refs(xml)
27
+ sso and eref2link(xml)
28
+ svgmap_resolve(xml, docid)
29
+ xml.to_xml
30
+ end
31
+
32
+ def update_xrefs_prep(file, docid)
33
+ docxml = file.is_a?(String) ? Nokogiri::XML(file, &:huge) : file
34
+ supply_repo_ids(docxml)
35
+ sso = @files.get(docid, :sectionsplit_output)
36
+ [docxml, sso]
37
+ end
38
+
39
+ def update_sectionsplit_refs_to_docs(docxml, internal_refs)
40
+ Util::gather_citeases(docxml).each do |k, v|
41
+ (@files.get(k) && @files.get(k, :sectionsplit)) or next
42
+ opts = { key: @files.get(k, :indirect_key),
43
+ source_suffix: docxml.root["document_suffix"],
44
+ target_suffix: @files.get(k, :document_suffix) }
45
+ refs = v.each_with_object({}) do |eref, m|
46
+ update_sectionsplit_eref_to_doc(eref, internal_refs, m, opts)
47
+ end
48
+ add_hidden_bibliography(docxml, refs)
49
+ end
50
+ end
51
+
52
+ def update_sectionsplit_eref_to_doc(eref, internal_refs, doclist, opts)
53
+ a = eref.at(ns("./localityStack/locality[@type = 'anchor']/" \
54
+ "referenceFrom")) or return
55
+ doc = internal_refs[opts[:key]]["#{a.text}_#{opts[:target_suffix]}"]
56
+ bibitemid = Metanorma::Utils::to_ncname("#{doc}_#{opts[:source_suffix]}")
57
+ eref["bibitemid"] = bibitemid
58
+ doclist[bibitemid] ||= doc
59
+ doclist
60
+ end
61
+
62
+ def new_hidden_ref(xmldoc)
63
+ ins = xmldoc.at(ns("bibliography")) or
64
+ xmldoc.root << "<bibliography/>" and ins = xmldoc.at(ns("bibliography"))
65
+ ins.at(ns("./referenced[@hidden = 'true']")) or
66
+ ins.add_child("<references hidden='true' normative='false'/>").first
67
+ end
68
+
69
+ def eref2link(docxml)
70
+ isodoc = IsoDoc::PresentationXMLConvert.new({})
71
+ isodoc.bibitem_lookup(docxml)
72
+ isodoc.eref2link(docxml)
73
+ end
74
+
75
+ BIBITEM_NOT_REPO_XPATH = "//bibitem[not(ancestor::bibitem)]" \
76
+ "[not(./docidentifier[@type = 'repository'])]".freeze
77
+
78
+ def supply_repo_ids(doc)
79
+ doc.xpath(ns(BIBITEM_NOT_REPO_XPATH)).each do |b|
80
+ b.xpath(ns("./docidentifier")).each do |docid|
81
+ id = @isodoc.docid_prefix(docid["type"], docid.children.to_xml)
82
+ @files.get(id) or next
83
+ @files.get(id, :indirect_key) and next # will resolve as indirect key
84
+ docid.next = docid_xml(id)
85
+ end
86
+ end
87
+ end
88
+
89
+ def svgmap_resolve(docxml, docid)
90
+ ids = @files.get(docid, :ids)
91
+ dir = File.join(@dirname, File.dirname(@files.get(docid, :rel_path)))
92
+ docxml = datauri_encode(docxml, dir)
93
+ isodoc = IsoDoc::PresentationXMLConvert.new({})
94
+ isodoc.bibitem_lookup(docxml)
95
+ docxml.xpath(ns("//svgmap//eref")).each do |e|
96
+ svgmap_resolve1(e, isodoc, docxml, ids)
97
+ end
98
+ Vectory::SvgMapping.new(docxml, "").call
99
+ docxml.xpath(ns("//svgmap")).each { |s| isodoc.svgmap_extract(s) }
100
+ end
101
+
102
+ def svgmap_resolve1(eref, isodoc, _docxml, ids)
103
+ href = isodoc.eref_target(eref) or return
104
+ href == "##{eref['bibitemid']}" ||
105
+ (href =~ /^#/ && !ids[href.sub(/^#/, "")]) and return
106
+ eref["target"] = href.strip
107
+ eref.name = "link"
108
+ eref.elements&.remove
109
+ end
110
+
111
+ # repo(current-metanorma-collection/ISO 17301-1:2016)
112
+ # replaced by bibdata of "ISO 17301-1:2016" in situ as bibitem.
113
+ # Any erefs to that bibitem id are replaced with relative URL
114
+ # Preferably with anchor, and is a job to realise dynamic lookup
115
+ # of localities.
116
+ def update_direct_refs_to_docs(docxml, identifier)
117
+ erefs, erefs1 = update_direct_refs_to_docs_prep(docxml)
118
+ docxml.xpath(ns("//bibitem")).each do |b|
119
+ docid = b.at(ns("./docidentifier[@type = 'repository']")) or next
120
+ strip_unresolved_repo_erefs(identifier, docid, erefs1, b) or next
121
+ update_bibitem(b, identifier)
122
+ docid = docid_to_citeas(b) or next
123
+ erefs[docid] and update_anchors(b, docid, erefs[docid])
124
+ end
125
+ end
126
+
127
+ def update_direct_refs_to_docs_prep(docxml)
128
+ @ncnames = {}
129
+ [Util::gather_citeases(docxml), Util::gather_bibitemids(docxml)]
130
+ end
131
+
132
+ # strip erefs if they are repository erefs, but do not point to a document
133
+ # within the current collection. This can happen if a collection consists
134
+ # of many documents, but not all are included in the current collection.
135
+ # Do not do this if this is a sectionsplit collection or a nested manifest.
136
+ # Return false if bibitem is not to be further processed
137
+ def strip_unresolved_repo_erefs(_document_id, bib_docid, erefs, bibitem)
138
+ %r{^current-metanorma-collection/(?!Missing:)}.match?(bib_docid.text) and
139
+ return true
140
+ @nested and return false
141
+ erefs[bibitem["id"]]&.each { |x| x.parent and strip_eref(x) }
142
+ false
143
+ end
144
+
145
+ # Resolve erefs to a container of ids in another doc,
146
+ # to an anchor eref (direct link)
147
+ def update_indirect_refs_to_docs(docxml, _docidentifier, internal_refs)
148
+ bibitems, erefs = update_indirect_refs_to_docs_prep(docxml)
149
+ internal_refs.each do |schema, ids|
150
+ ids.each do |id, file|
151
+ k = indirect_ref_key(schema, id, docxml)
152
+ update_indirect_refs_to_docs1(docxml, k, file, bibitems, erefs)
153
+ end
154
+ end
155
+ end
156
+
157
+ def update_indirect_refs_to_docs_prep(docxml)
158
+ @updated_anchors = {}
159
+ [Util::gather_bibitems(docxml), Util::gather_bibitemids(docxml)]
160
+ end
161
+
162
+ def indirect_ref_key(schema, id, docxml)
163
+ /^#{schema}_/.match?(id) and return id
164
+ ret = "#{schema}_#{id}"
165
+ suffix = docxml.root["document_suffix"]
166
+ (k = docxml.root["type"]) && k != schema && suffix and
167
+ ret = "#{ret}_#{suffix}"
168
+ ret
169
+ end
170
+
171
+ def update_indirect_refs_to_docs1(_docxml, key, file, bibitems, erefs)
172
+ erefs[key]&.each do |e|
173
+ e["citeas"] = file
174
+ update_indirect_refs_to_docs_anchor(e, file)
175
+ end
176
+ update_indirect_refs_to_docs_docid(bibitems[key], file)
177
+ end
178
+
179
+ def update_indirect_refs_to_docs_anchor(eref, file)
180
+ a = eref.at(ns(".//locality[@type = 'anchor']/referenceFrom")) or return
181
+ suffix = file
182
+ @files.get(file) && p = @files.get(file, :parentid) and
183
+ suffix = "#{p}_#{suffix}"
184
+ existing = a.text
185
+ anchor = existing
186
+ @files.url?(file) or
187
+ anchor = Metanorma::Utils::to_ncname("#{anchor}_#{suffix}")
188
+ @updated_anchors[existing] or a.children = anchor
189
+ @updated_anchors[anchor] = true
190
+ end
191
+
192
+ def update_indirect_refs_to_docs_docid(bibitem, file)
193
+ docid = bibitem&.at(ns("./docidentifier[@type = 'repository']")) or
194
+ return
195
+ docid.children = "current-metanorma-collection/#{file}"
196
+ docid.previous =
197
+ "<docidentifier type='metanorma-collection'>#{file}</docidentifier>"
198
+ end
199
+
200
+ # update crossrefences to other documents, to include
201
+ # disambiguating document suffix on id
202
+ def update_anchors(bib, docid, erefs)
203
+ erefs.each do |e|
204
+ if @files.get(docid) then update_anchor_loc(bib, e, docid)
205
+ else
206
+ msg = "<strong>** Unresolved reference to document #{docid} " \
207
+ "from eref</strong>"
208
+ e << msg
209
+ strip_eref(e)
210
+ @log&.add("Cross-References", e, msg)
211
+ end
212
+ end
213
+ end
214
+
215
+ def update_anchor_loc(bib, eref, docid)
216
+ loc = eref.at(".//xmlns:locality[@type = 'anchor']") or
217
+ return update_anchor_create_loc(bib, eref, docid)
218
+ ref = loc.at("./xmlns:referenceFrom") or return
219
+ anchor = suffix_anchor(ref, docid)
220
+ a = @files.get(docid, :anchors) or return
221
+ a.inject([]) { |m, (_, x)| m + x.values }
222
+ .include?(anchor) or return
223
+ ref.content = anchor
224
+ end
225
+
226
+ def suffix_anchor(ref, docid)
227
+ @ncnames[docid] ||= Metanorma::Utils::to_ncname(docid)
228
+ anchor = ref.text
229
+ @files.url?(docid) or anchor = "#{@ncnames[docid]}_#{anchor}"
230
+ anchor
231
+ end
232
+
233
+ # if there is a crossref to another document, with no anchor, retrieve the
234
+ # anchor given the locality, and insert it into the crossref
235
+ def update_anchor_create_loc(_bib, eref, docid)
236
+ ins = eref.at(ns("./localityStack")) or return
237
+ type = ins.at(ns("./locality/@type"))&.text
238
+ type = "clause" if type == "annex"
239
+ ref = ins.at(ns("./locality/referenceFrom"))&.text
240
+ anchor = @files.get(docid, :anchors).dig(type, ref) or return
241
+ ins << "<locality type='anchor'><referenceFrom>#{anchor.sub(/^_/,
242
+ '')}" \
243
+ "</referenceFrom></locality>"
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "isodoc"
4
+ require "metanorma-utils"
5
+ require_relative "fileparse"
6
+
7
+ module Metanorma
8
+ class Collection
9
+ class Renderer
10
+ # compile and output individual file in collection
11
+ # warn "metanorma compile -x html #{f.path}"
12
+ def file_compile(file, filename, identifier)
13
+ @files.get(identifier, :sectionsplit) and return
14
+ opts = {
15
+ format: :asciidoc,
16
+ extension_keys: @format,
17
+ output_dir: @outdir,
18
+ }.merge(compile_options_update(identifier))
19
+ @compile.compile file, opts
20
+ @files.set(identifier, :outputs, {})
21
+ file_compile_formats(filename, identifier)
22
+ end
23
+
24
+ def compile_options_update(identifier)
25
+ ret = @compile_options.dup
26
+ @directives.detect { |d| d.key == "presentation-xml" } ||
27
+ @files.get(identifier, :presentationxml) and
28
+ ret.merge!(passthrough_presentation_xml: true)
29
+ @files.get(identifier, :sectionsplit) == true and
30
+ ret.merge!(sectionsplit: true)
31
+ @files.get(identifier, :bare) == true and
32
+ ret.merge!(bare: true)
33
+ ret
34
+ end
35
+
36
+ def file_compile_formats(filename, identifier)
37
+ f = @files.get(identifier, :outputs)
38
+ @format << :presentation if @format.include?(:pdf)
39
+ @format.each do |e|
40
+ ext = @compile.processor.output_formats[e]
41
+ fn = File.basename(filename).sub(/(?<=\.)[^.]+$/, ext.to_s)
42
+ (/html$/.match?(ext) && @files.get(identifier, :sectionsplit)) or
43
+ f[e] = File.join(@outdir, fn)
44
+ end
45
+ @files.set(identifier, :outputs, f)
46
+ end
47
+
48
+ def copy_file_to_dest(identifier)
49
+ out = Pathname.new(@files.get(identifier, :out_path)).cleanpath
50
+ out.absolute? and
51
+ out = out.relative_path_from(File.expand_path(FileUtils.pwd))
52
+ dest = File.join(@outdir, @disambig.source2dest_filename(out.to_s))
53
+ FileUtils.mkdir_p(File.dirname(dest))
54
+ FileUtils.cp @files.get(identifier, :ref), dest
55
+ end
56
+
57
+ # process each file in the collection
58
+ # files are held in memory, and altered as postprocessing
59
+ def files # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
60
+ warn "\n\n\n\n\nRender Files: #{DateTime.now.strftime('%H:%M:%S')}"
61
+ internal_refs = locate_internal_refs
62
+ @files.keys.each_with_index do |ident, i|
63
+ i.positive? && @directives.detect do |d|
64
+ d.key == "bare-after-first"
65
+ end and
66
+ @compile_options.merge!(bare: true)
67
+ if @files.get(ident, :attachment) then copy_file_to_dest(ident)
68
+ else
69
+ file, fname = @files.targetfile_id(ident, read: true)
70
+ warn "\n\n\n\n\nProcess #{fname}: #{DateTime.now.strftime('%H:%M:%S')}"
71
+ collection_xml = update_xrefs(file, ident, internal_refs)
72
+ collection_filename = File.basename(fname, File.extname(fname))
73
+ collection_xml_path = File.join(Dir.tmpdir,
74
+ "#{collection_filename}.xml")
75
+ File.write collection_xml_path, collection_xml, encoding: "UTF-8"
76
+ file_compile(collection_xml_path, fname, ident)
77
+ FileUtils.rm(collection_xml_path)
78
+ end
79
+ end
80
+ end
81
+
82
+ # gather internal bibitem references
83
+ def gather_internal_refs
84
+ @files.keys.each_with_object({}) do |i, refs|
85
+ @files.get(i, :attachment) and next
86
+ file, = @files.targetfile_id(i, read: true)
87
+ gather_internal_refs1(file, i, refs)
88
+ end
89
+ end
90
+
91
+ def gather_internal_refs1(file, ident, refs)
92
+ f = Nokogiri::XML(file, &:huge)
93
+ !@files.get(ident, :sectionsplit) and
94
+ gather_internal_refs_indirect(f, refs)
95
+ key = @files.get(ident, :indirect_key) and
96
+ gather_internal_refs_sectionsplit(f, ident, key, refs)
97
+ end
98
+
99
+ def gather_internal_refs_indirect(doc, refs)
100
+ doc.xpath(ns("//bibitem[@type = 'internal']/" \
101
+ "docidentifier[@type = 'repository']")).each do |d|
102
+ a = d.text.split(%r{/}, 2)
103
+ a.size > 1 or next
104
+ refs[a[0]] ||= {}
105
+ refs[a[0]][a[1]] = false
106
+ end
107
+ end
108
+
109
+ def gather_internal_refs_sectionsplit(_doc, ident, key, refs)
110
+ refs[key] ||= {}
111
+ @files.get(ident, :ids).each_key do |k|
112
+ refs[key][k] = false
113
+ end
114
+ end
115
+
116
+ def populate_internal_refs(refs)
117
+ @files.keys.reject do |k|
118
+ @files.get(k, :attachment) || @files.get(k, :sectionsplit)
119
+ end.each do |ident|
120
+ locate_internal_refs1(refs, ident,
121
+ @isodoc.docid_prefix("", ident.dup))
122
+ end
123
+ refs
124
+ end
125
+
126
+ # resolve file location for the target of each internal reference
127
+ def locate_internal_refs
128
+ warn "\n\n\n\n\nInternal Refs: #{DateTime.now.strftime('%H:%M:%S')}"
129
+ refs = populate_internal_refs(gather_internal_refs)
130
+ refs.each do |schema, ids|
131
+ ids.each do |id, key|
132
+ key and next
133
+ refs[schema][id] = "Missing:#{schema}:#{id}"
134
+ @log&.add("Cross-References", nil, refs[schema][id])
135
+ end
136
+ end
137
+ refs
138
+ end
139
+
140
+ def locate_internal_refs1(refs, identifier, ident)
141
+ file, = @files.targetfile_id(ident, read: true)
142
+ t = locate_internal_refs1_prep(file)
143
+ refs.each do |schema, ids|
144
+ ids.keys.select { |id| t[id] }.each do |id|
145
+ t[id].at("./ancestor-or-self::*[@type = '#{schema}']") and
146
+ refs[schema][id] = identifier
147
+ end
148
+ end
149
+ end
150
+
151
+ def locate_internal_refs1_prep(file)
152
+ xml = Nokogiri::XML(file, &:huge)
153
+ r = xml.root["document_suffix"]
154
+ xml.xpath("//*[@id]").each_with_object({}) do |i, x|
155
+ /^semantic_/.match?(i.name) and next
156
+ x[i["id"]] = i
157
+ r and x[i["id"].sub(/_#{r}$/, "")] = i
158
+ end
159
+ end
160
+
161
+ def update_bibitem(bib, identifier)
162
+ docid = get_bibitem_docid(bib, identifier) or return
163
+ newbib = dup_bibitem(docid, bib)
164
+ url = @files.url(docid, relative: true,
165
+ doc: !@files.get(docid, :attachment))
166
+ dest = newbib.at("./docidentifier") || newbib.at(ns("./docidentifier"))
167
+ dest or dest = newbib.elements[-1]
168
+ dest.previous = "<uri type='citation'>#{url}</uri>"
169
+ bib.replace(newbib)
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,133 @@
1
+ module Metanorma
2
+ class Collection
3
+ class Renderer
4
+ # @param elm [Nokogiri::XML::Element]
5
+ # @return [String]
6
+ def indexfile_title(entry)
7
+ if entry.bibdata &&
8
+ x = entry.bibdata.title.detect { |t| t.type == "main" } ||
9
+ entry.bibdata.title.first
10
+ x.title.content
11
+ else
12
+ entry.title
13
+ end
14
+ end
15
+
16
+ # uses the identifier to label documents; other attributes (title) can be
17
+ # looked up in @files[id][:bibdata]
18
+ #
19
+ # @param mnf [Collection::Manifest]
20
+ # @param builder [Nokogiri::XML::Builder]
21
+ def indexfile_docref(mnf, builder)
22
+ Array(mnf.entry).detect(&:index) or return ""
23
+ builder.ul { |b| docrefs(mnf, b) }
24
+ end
25
+
26
+ def docrefs(mnf, builder)
27
+ ident = docref_ident(mnf)
28
+ builder.li do |li|
29
+ li.a href: index_link(mnf, ident) do |a|
30
+ a << ident.split(/([<>&])/).map do |x|
31
+ /[<>&]/.match?(x) ? x : @c.encode(x, :hexadecimal)
32
+ end.join
33
+ end
34
+ end
35
+ end
36
+
37
+ def docref_ident(docref)
38
+ ident = docref.identifier.dup
39
+ @c.decode(@isodoc.docid_prefix(nil, ident))
40
+ end
41
+
42
+ def index_link(docref, ident)
43
+ if docref.file
44
+ @files.get(ident, :out_path).sub(/\.xml$/, ".html")
45
+ else "#{docref.id}.html"
46
+ end
47
+ end
48
+
49
+ # single level navigation list, with hierarchical nesting
50
+ def indexfile(mnf)
51
+ mnfs = Array(mnf)
52
+ mnfs.empty? and return ""
53
+ mnfs.map { |m| "<ul>#{indexfile1(m)}</ul>" }.join("\n")
54
+ end
55
+
56
+ def index?(mnf)
57
+ mnf.index and return true
58
+ mnf.entry.detect { |e| index?(e) }
59
+ end
60
+
61
+ def indexfile1(mnf)
62
+ index?(mnf) or return ""
63
+ cleanup_indexfile1(build_indexfile1(mnf))
64
+ end
65
+
66
+ def build_indexfile1(mnf)
67
+ Nokogiri::HTML::Builder.new do |b|
68
+ if mnf.file then docrefs(mnf, b)
69
+ else
70
+ b.li do |l|
71
+ l << indexfile_title(mnf)
72
+ l.ul do |u|
73
+ Array(mnf.entry).each { |e| u << indexfile1(e) }
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def cleanup_indexfile1(ret)
81
+ ret = ret.doc.root
82
+ ret.xpath("/ul").each do |u|
83
+ if u.at("./li/ul") && !u.at("./li[text()]")
84
+ u.replace(u.xpath("./li/ul"))
85
+ end
86
+ end
87
+ ret.to_html
88
+ end
89
+
90
+ # object to construct navigation out of in Liquid
91
+ def index_object(mnf)
92
+ mnf = Array(mnf).first
93
+ ret = { title: indexfile_title(mnf), level: mnf.type,
94
+ docrefs: index_object_docrefs(mnf),
95
+ children: index_object_children(mnf) }.compact
96
+ ret.keys == [:children] and ret = ret[:children]
97
+ ret
98
+ end
99
+
100
+ def index_object_children(mnf)
101
+ nonfiles = Array(mnf.entry).reject(&:file)
102
+ c = nonfiles.each_with_object([]) do |d, b|
103
+ b << index_object(d)
104
+ end.flatten
105
+ c.empty? and c = nil
106
+ c
107
+ end
108
+
109
+ def index_object_docrefs(mnf)
110
+ files = Array(mnf.entry).select(&:file)
111
+ files.empty? and return nil
112
+ r = Nokogiri::HTML::Builder.new do |b|
113
+ b.ul do |u|
114
+ files.each { |f| docrefs(f, u) }
115
+ end
116
+ end
117
+ r.doc.root&.to_html&.gsub("\n", " ")
118
+ end
119
+
120
+ def liquid_docrefs(mnfs)
121
+ Array(mnfs).select(&:index).each_with_object([]) do |d, m|
122
+ if d.file
123
+ ident = @c.decode(@isodoc.docid_prefix(nil, d.identifier.dup))
124
+ m << { "identifier" => ident, "file" => index_link(d, ident),
125
+ "title" => indexfile_title(d), "level" => d.type }
126
+ else
127
+ liquid_docrefs(d.entry).each { |m1| m << m1 }
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,133 @@
1
+ module Metanorma
2
+ class Collection
3
+ class Renderer
4
+ def docconv
5
+ @tempfile_cache ||= []
6
+ doctype = @doctype.to_sym
7
+ x = Asciidoctor.load nil, backend: doctype
8
+ x.converter.doc_converter(DocOptionsNode.new(@directives, @dirname))
9
+ end
10
+
11
+ def concat_extract_files(filename)
12
+ xml = Nokogiri::XML(File.read(filename, encoding: "UTF-8"), &:huge)
13
+ docs = xml.xpath(ns("//doc-container")).each_with_object([]) do |x, m|
14
+ n = Nokogiri::XML::Document.new
15
+ n.add_child(x.elements.first.remove)
16
+ m << n
17
+ end
18
+ [wrapping_doc(docs.first.dup, xml), docs]
19
+ end
20
+
21
+ def wrapping_doc(doc, xml)
22
+ doc.at(ns("//bibdata")).replace(xml.at(ns("//bibdata")).to_xml)
23
+ sections = wrapping_doc_body(doc)
24
+ wrapping_doc_intro_outro(xml, sections)
25
+ set_displayorder_wrapping_doc(doc)
26
+ end
27
+
28
+ def wrapping_doc_intro_outro(xml, sections)
29
+ p = xml.at(ns("//prefatory-content")) and
30
+ sections.previous = "<preface>#{p.children.to_xml}</preface>"
31
+ p = xml.at(ns("//final-content")) and
32
+ sections.next = "<annex>#{p.children.to_xml}</annex>"
33
+ end
34
+
35
+ def wrapping_doc_body(doc)
36
+ doc.xpath(ns("//annex | //preface | //bibliography")).each(&:remove)
37
+ s = doc.at(ns("//sections"))
38
+ repl = <<~BODY
39
+ <sections><clause id='_collection_placeholder'><p>PLACEHOLDER</p></clause></sections>
40
+ BODY
41
+ s.replace(repl)
42
+ doc.at(ns("//sections"))
43
+ end
44
+
45
+ def set_displayorder_wrapping_doc(doc)
46
+ doc.xpath(ns("//preface/* | //sections/* | //annex"))
47
+ .each_with_index do |x, i|
48
+ x["displayorder"] = i + 1
49
+ end
50
+ doc
51
+ end
52
+
53
+ SECTION_BREAK = '<p class="MsoNormal"><br clear="all" class="section"/></p>'
54
+ .freeze
55
+ DIV1 = '<div class="WordSection1">&#xa0;</div>'.freeze
56
+ DIV2 = '<div class="WordSection2">&#xa0;</div>'.freeze
57
+
58
+ def docconv_convert1(docs)
59
+ docs.each_with_index.with_object([]) do |(d, i), m|
60
+ conv = docconv
61
+ conv.convert_init(d.to_xml(encoding: "UTF-8"), "xxxx", false)
62
+ html = conv.postprocess_cleanup(conv.convert1(d, "xxx", "."))
63
+ @tempfile_cache += conv.tempfile_cache # hold on to the temp img files
64
+ b = Nokogiri::XML(html).at("//body")
65
+ i == docs.size - 1 or
66
+ b << '<p class="MsoNormal"><br clear="all" class="section"/></p>'
67
+ m << b.children
68
+ end
69
+ end
70
+
71
+ def collection_coverpages(conv, docs)
72
+ conv.wordintropage and [DIV2, SECTION_BREAK].reverse.each do |s|
73
+ docs.unshift(Nokogiri::XML(s).root)
74
+ end
75
+ conv.wordcoverpage and [DIV1, SECTION_BREAK].reverse.each do |s|
76
+ docs.unshift(Nokogiri::XML(s).root)
77
+ end
78
+ docs
79
+ end
80
+
81
+ def docconv_convert(filename)
82
+ pref_file, docs = concat_extract_files(filename)
83
+ body = docconv_convert1(docs)
84
+ collection_conv = overall_docconv_converter(body)
85
+ collection_coverpages(collection_conv, body)
86
+ collection_conv.convert(filename, pref_file.to_xml, false)
87
+ end
88
+
89
+ def overall_docconv_cover(collection_conv)
90
+ p = Util::hash_key_detect(@directives, "collection-word-coverpage", nil)
91
+ collection_conv.wordcoverpage =
92
+ Util::rel_path_resolve(@dirname, p)
93
+ p = Util::hash_key_detect(@directives, "collection-word-intropage", nil)
94
+ collection_conv.wordintropage =
95
+ Util::rel_path_resolve(@dirname, p)
96
+ end
97
+
98
+ def overall_docconv_converter(body)
99
+ collection_conv = docconv
100
+ collection_conv.options[:collection_doc] = body.map(&:to_xml).join
101
+ overall_docconv_cover(collection_conv)
102
+
103
+ def collection_conv.postprocess_cleanup(result)
104
+ ret = to_xhtml(super)
105
+ b = ret.at("//div[@id = '_collection_placeholder']")
106
+ b.replace(@options[:collection_doc])
107
+ from_xhtml(ret)
108
+ end
109
+
110
+ collection_conv
111
+ end
112
+
113
+ class DocOptionsNode
114
+ def initialize(directives, dir)
115
+ @dir = dir
116
+ @wordcoverpage =
117
+ Util::hash_key_detect(directives, "document-word-coverpage",
118
+ @wordcoverpage)
119
+ @wordintropage =
120
+ Util::hash_key_detect(directives, "document-word-intropage",
121
+ @wordintropage)
122
+ end
123
+
124
+ def attr(key)
125
+ case key
126
+ when "wordcoverpage" then Util::rel_path_resolve(@dir, @wordcoverpage)
127
+ when "wordintropage" then Util::rel_path_resolve(@dir, @wordintropage)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end