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