metanorma 1.7.7 → 2.0.1

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 (58) 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 +226 -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 +254 -0
  17. data/lib/metanorma/collection/renderer/fileprocess.rb +174 -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 +211 -0
  22. data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +219 -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} +19 -12
  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 +50 -24
  44. data/lib/metanorma/collection.rb +0 -244
  45. data/lib/metanorma/collection_fileparse.rb +0 -257
  46. data/lib/metanorma/collection_fileprocess.rb +0 -168
  47. data/lib/metanorma/collection_manifest.rb +0 -168
  48. data/lib/metanorma/collection_render_utils.rb +0 -169
  49. data/lib/metanorma/collection_render_word.rb +0 -131
  50. data/lib/metanorma/collection_renderer.rb +0 -237
  51. data/lib/metanorma/collection_xref_process.rb +0 -217
  52. data/lib/metanorma/document.rb +0 -133
  53. data/lib/metanorma/files_lookup.rb +0 -224
  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/util.rb +0 -127
  58. data/lib/metanorma/worker_pool.rb +0 -29
@@ -0,0 +1,254 @@
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
+ @nested or 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 eref2link(docxml)
63
+ isodoc = IsoDoc::PresentationXMLConvert.new({})
64
+ isodoc.bibitem_lookup(docxml)
65
+ isodoc.eref2link(docxml)
66
+ end
67
+
68
+ BIBITEM_NOT_REPO_XPATH = "//bibitem[not(ancestor::bibitem)]" \
69
+ "[not(./docidentifier[@type = 'repository'])]".freeze
70
+
71
+ def supply_repo_ids(doc)
72
+ doc.xpath(ns(BIBITEM_NOT_REPO_XPATH)).each do |b|
73
+ b.xpath(ns("./docidentifier")).each do |docid|
74
+ id = @isodoc.docid_prefix(docid["type"], docid.children.to_xml)
75
+ @files.get(id) or next
76
+ @files.get(id, :indirect_key) and next # will resolve as indirect key
77
+ docid.next = docid_xml(id)
78
+ end
79
+ end
80
+ end
81
+
82
+ def svg_datauri(docxml, docid)
83
+ rel = @files.get(docid, :rel_path)
84
+ parent = @files.get(docid, :parentid) and
85
+ rel = @files.get(parent, :rel_path)
86
+ # if sectionsplit, use orig file dir
87
+ dir = File.join(@dirname, File.dirname(rel))
88
+ datauri_encode(docxml, dir)
89
+ end
90
+
91
+ def svgmap_resolve(docxml, docid)
92
+ ids = @files.get(docid, :ids)
93
+ docxml = svg_unnest(svg_datauri(docxml, docid))
94
+ isodoc = IsoDoc::PresentationXMLConvert.new({})
95
+ isodoc.bibitem_lookup(docxml)
96
+ docxml.xpath(ns("//svgmap//eref")).each do |e|
97
+ svgmap_resolve_eref(e, isodoc, docxml, ids)
98
+ end
99
+ Vectory::SvgMapping.new(docxml, "").call
100
+ docxml.xpath(ns("//svgmap")).each { |s| isodoc.svgmap_extract(s) }
101
+ end
102
+
103
+ def svg_unnest(docxml)
104
+ docxml.xpath(ns("//svgmap//image[.//*[name() = 'image']]")).each do |i|
105
+ s = i.elements.detect { |e| e.name == "svg" } and
106
+ i.replace(s)
107
+ end
108
+ docxml
109
+ end
110
+
111
+ def svgmap_resolve_eref(eref, isodoc, _docxml, ids)
112
+ href = isodoc.eref_target(eref) or return
113
+ href == "##{eref['bibitemid']}" ||
114
+ (href =~ /^#/ && !ids[href.sub(/^#/, "")]) and return
115
+ eref["target"] = href.strip
116
+ eref.name = "link"
117
+ eref.elements&.remove
118
+ end
119
+
120
+ # repo(current-metanorma-collection/ISO 17301-1:2016)
121
+ # replaced by bibdata of "ISO 17301-1:2016" in situ as bibitem.
122
+ # Any erefs to that bibitem id are replaced with relative URL
123
+ # Preferably with anchor, and is a job to realise dynamic lookup
124
+ # of localities.
125
+ def update_direct_refs_to_docs(docxml, identifier)
126
+ erefs, erefs1 = update_direct_refs_to_docs_prep(docxml)
127
+ docxml.xpath(ns("//bibitem")).each do |b|
128
+ docid = b.at(ns("./docidentifier[@type = 'repository']")) or next
129
+ strip_unresolved_repo_erefs(identifier, docid, erefs1, b) or next
130
+ update_bibitem(b, identifier)
131
+ docid = docid_to_citeas(b) or next
132
+ erefs[docid] and update_anchors(b, docid, erefs[docid])
133
+ end
134
+ end
135
+
136
+ def update_direct_refs_to_docs_prep(docxml)
137
+ @ncnames = {}
138
+ [Util::gather_citeases(docxml), Util::gather_bibitemids(docxml)]
139
+ end
140
+
141
+ # strip erefs if they are repository erefs, but do not point to a document
142
+ # within the current collection. This can happen if a collection consists
143
+ # of many documents, but not all are included in the current collection.
144
+ # Do not do this if this is a sectionsplit collection or a nested manifest.
145
+ # Return false if bibitem is not to be further processed
146
+ def strip_unresolved_repo_erefs(_document_id, bib_docid, erefs, bibitem)
147
+ %r{^current-metanorma-collection/(?!Missing:)}.match?(bib_docid.text) and
148
+ return true
149
+ @nested and return false
150
+ erefs[bibitem["id"]]&.each { |x| x.parent and strip_eref(x) }
151
+ false
152
+ end
153
+
154
+ # Resolve erefs to a container of ids in another doc,
155
+ # to an anchor eref (direct link)
156
+ def update_indirect_refs_to_docs(docxml, _docidentifier, internal_refs)
157
+ bibitems, erefs = update_indirect_refs_to_docs_prep(docxml)
158
+ internal_refs.each do |schema, ids|
159
+ ids.each do |id, file|
160
+ k = indirect_ref_key(schema, id, docxml)
161
+ update_indirect_refs_to_docs1(docxml, k, file, bibitems, erefs)
162
+ end
163
+ end
164
+ end
165
+
166
+ def update_indirect_refs_to_docs_prep(docxml)
167
+ @updated_anchors = {}
168
+ [Util::gather_bibitems(docxml), Util::gather_bibitemids(docxml)]
169
+ end
170
+
171
+ def indirect_ref_key(schema, id, docxml)
172
+ /^#{schema}_/.match?(id) and return id
173
+ ret = "#{schema}_#{id}"
174
+ suffix = docxml.root["document_suffix"]
175
+ (k = docxml.root["type"]) && k != schema && suffix and
176
+ ret = "#{ret}_#{suffix}"
177
+ ret
178
+ end
179
+
180
+ def update_indirect_refs_to_docs1(_docxml, key, file, bibitems, erefs)
181
+ erefs[key]&.each do |e|
182
+ e["citeas"] = file
183
+ update_indirect_refs_to_docs_anchor(e, file)
184
+ end
185
+ update_indirect_refs_to_docs_docid(bibitems[key], file)
186
+ end
187
+
188
+ def update_indirect_refs_to_docs_anchor(eref, file)
189
+ a = eref.at(ns(".//locality[@type = 'anchor']/referenceFrom")) or return
190
+ suffix = file
191
+ @files.get(file) && p = @files.get(file, :parentid) and
192
+ suffix = "#{p}_#{suffix}"
193
+ existing = a.text
194
+ anchor = existing
195
+ @files.url?(file) or
196
+ anchor = Metanorma::Utils::to_ncname("#{anchor}_#{suffix}")
197
+ @updated_anchors[existing] or a.children = anchor
198
+ @updated_anchors[anchor] = true
199
+ end
200
+
201
+ def update_indirect_refs_to_docs_docid(bib, file)
202
+ docid = bib&.at(ns("./docidentifier[@type = 'repository']")) or return
203
+ docid.children = "current-metanorma-collection/#{file}"
204
+ docid.previous =
205
+ "<docidentifier type='metanorma-collection'>#{file}</docidentifier>"
206
+ end
207
+
208
+ # update crossrefences to other documents, to include
209
+ # disambiguating document suffix on id
210
+ def update_anchors(bib, docid, erefs)
211
+ erefs.each do |e|
212
+ if @files.get(docid) then update_anchor_loc(bib, e, docid)
213
+ else
214
+ msg = "<strong>** Unresolved reference to document #{docid} " \
215
+ "from eref</strong>"
216
+ e << msg
217
+ strip_eref(e)
218
+ @log&.add("Cross-References", e, msg)
219
+ end
220
+ end
221
+ end
222
+
223
+ def update_anchor_loc(bib, eref, docid)
224
+ loc = eref.at(".//xmlns:locality[@type = 'anchor']") or
225
+ return update_anchor_create_loc(bib, eref, docid)
226
+ ref = loc.at("./xmlns:referenceFrom") or return
227
+ anchor = suffix_anchor(ref, docid)
228
+ a = @files.get(docid, :anchors) or return
229
+ a.inject([]) { |m, (_, x)| m + x.values }
230
+ .include?(anchor) or return
231
+ ref.content = anchor
232
+ end
233
+
234
+ def suffix_anchor(ref, docid)
235
+ @ncnames[docid] ||= Metanorma::Utils::to_ncname(docid)
236
+ anchor = ref.text
237
+ @files.url?(docid) or anchor = "#{@ncnames[docid]}_#{anchor}"
238
+ anchor
239
+ end
240
+
241
+ # if there is a crossref to another document, with no anchor, retrieve the
242
+ # anchor given the locality, and insert it into the crossref
243
+ def update_anchor_create_loc(_bib, eref, docid)
244
+ ins = eref.at(ns("./localityStack")) or return
245
+ type = ins.at(ns("./locality/@type"))&.text
246
+ type = "clause" if type == "annex"
247
+ ref = ins.at(ns("./locality/referenceFrom"))&.text
248
+ a = @files.get(docid, :anchors).dig(type, ref) or return
249
+ ins << "<locality type='anchor'><referenceFrom>#{a.sub(/^_/, '')}" \
250
+ "</referenceFrom></locality>"
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,174 @@
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
+ source = @files.get(identifier, :ref)
55
+ source != dest and FileUtils.cp source, dest
56
+ end
57
+
58
+ # process each file in the collection
59
+ # files are held in memory, and altered as postprocessing
60
+ def files # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
61
+ warn "\n\n\n\n\nRender Files: #{DateTime.now.strftime('%H:%M:%S')}"
62
+ internal_refs = locate_internal_refs
63
+ @files.keys.each_with_index do |ident, i|
64
+ i.positive? && @directives.detect do |d|
65
+ d.key == "bare-after-first"
66
+ end and
67
+ @compile_options.merge!(bare: true)
68
+ if @files.get(ident, :attachment) then copy_file_to_dest(ident)
69
+ else
70
+ file, fname = @files.targetfile_id(ident, read: true)
71
+ warn "\n\n\n\n\nProcess #{fname}: #{DateTime.now.strftime('%H:%M:%S')}"
72
+ collection_xml = update_xrefs(file, ident, internal_refs)
73
+ collection_filename = File.basename(fname, File.extname(fname))
74
+ collection_xml_path = File.join(Dir.tmpdir,
75
+ "#{collection_filename}.xml")
76
+ File.write collection_xml_path, collection_xml, encoding: "UTF-8"
77
+ file_compile(collection_xml_path, fname, ident)
78
+ FileUtils.rm(collection_xml_path)
79
+ end
80
+ end
81
+ end
82
+
83
+ # gather internal bibitem references
84
+ def gather_internal_refs
85
+ @files.keys.each_with_object({}) do |i, refs|
86
+ @files.get(i, :attachment) and next
87
+ file, = @files.targetfile_id(i, read: true)
88
+ gather_internal_refs1(file, i, refs)
89
+ end
90
+ end
91
+
92
+ def gather_internal_refs1(file, ident, refs)
93
+ f = Nokogiri::XML(file, &:huge)
94
+ !@files.get(ident, :sectionsplit) and
95
+ gather_internal_refs_indirect(f, refs)
96
+ key = @files.get(ident, :indirect_key) and
97
+ gather_internal_refs_sectionsplit(f, ident, key, refs)
98
+ end
99
+
100
+ def gather_internal_refs_indirect(doc, refs)
101
+ doc.xpath(ns("//bibitem[@type = 'internal']/" \
102
+ "docidentifier[@type = 'repository']")).each do |d|
103
+ a = d.text.split(%r{/}, 2)
104
+ a.size > 1 or next
105
+ refs[a[0]] ||= {}
106
+ refs[a[0]][a[1]] = false
107
+ end
108
+ end
109
+
110
+ def gather_internal_refs_sectionsplit(_doc, ident, key, refs)
111
+ refs[key] ||= {}
112
+ @files.get(ident, :ids).each_key do |k|
113
+ refs[key][k] = false
114
+ end
115
+ end
116
+
117
+ def populate_internal_refs(refs)
118
+ @files.keys.reject do |k|
119
+ @files.get(k, :attachment) || @files.get(k, :sectionsplit)
120
+ end.each do |ident|
121
+ locate_internal_refs1(refs, ident,
122
+ @isodoc.docid_prefix("", ident.dup))
123
+ end
124
+ refs
125
+ end
126
+
127
+ # resolve file location for the target of each internal reference
128
+ def locate_internal_refs
129
+ warn "\n\n\n\n\nInternal Refs: #{DateTime.now.strftime('%H:%M:%S')}"
130
+ refs = populate_internal_refs(gather_internal_refs)
131
+ refs.each do |schema, ids|
132
+ ids.each do |id, key|
133
+ key and next
134
+ refs[schema][id] = "Missing:#{schema}:#{id}"
135
+ @log&.add("Cross-References", nil, refs[schema][id])
136
+ end
137
+ end
138
+ refs
139
+ end
140
+
141
+ def locate_internal_refs1(refs, identifier, ident)
142
+ file, = @files.targetfile_id(ident, read: true)
143
+ t = locate_internal_refs1_prep(file)
144
+ refs.each do |schema, ids|
145
+ ids.keys.select { |id| t[id] }.each do |id|
146
+ t[id].at("./ancestor-or-self::*[@type = '#{schema}']") and
147
+ refs[schema][id] = identifier
148
+ end
149
+ end
150
+ end
151
+
152
+ def locate_internal_refs1_prep(file)
153
+ xml = Nokogiri::XML(file, &:huge)
154
+ r = xml.root["document_suffix"]
155
+ xml.xpath("//*[@id]").each_with_object({}) do |i, x|
156
+ /^semantic_/.match?(i.name) and next
157
+ x[i["id"]] = i
158
+ r and x[i["id"].sub(/_#{r}$/, "")] = i
159
+ end
160
+ end
161
+
162
+ def update_bibitem(bib, identifier)
163
+ docid = get_bibitem_docid(bib, identifier) or return
164
+ newbib = dup_bibitem(docid, bib)
165
+ url = @files.url(docid, relative: true,
166
+ doc: !@files.get(docid, :attachment))
167
+ dest = newbib.at("./docidentifier") || newbib.at(ns("./docidentifier"))
168
+ dest or dest = newbib.elements[-1]
169
+ dest.previous = "<uri type='citation'>#{url}</uri>"
170
+ bib.replace(newbib)
171
+ end
172
+ end
173
+ end
174
+ 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