metanorma 1.1.2 → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 969ed76602cb33778779c7ab04c8c4c4be5fd63e83b448c07f96e8fafa6c7afb
4
- data.tar.gz: cf467c6615d1e5840a8c3accdd36195f269d4624307e94d2db6d56744a95cacb
3
+ metadata.gz: f6bdfc7913fb212cb141c23550aacaf2dd5c4261feea33946760a4b22af68298
4
+ data.tar.gz: 2a2fc2ae5c8b193b21063b368b8f6cac542dc98662e0376787d946020fe1e2b7
5
5
  SHA512:
6
- metadata.gz: 64d4f4a2f7375bb804a5e0acae3a538e4306a0f0a4cfa5c4b8688a320d0fb65e1b977099a5d413bcf87f74ab084abb11496ed20842daa476c92bb08aa57623d2
7
- data.tar.gz: eec76dfa55611c07335aa3c661b7926c0249338b9d6225fb3e58eb78d598b2cfee8fac19ebd1623721fa0fae441b451f04fd536dec7e728ae97d652626fca897
6
+ metadata.gz: 939e66856ea7e3bcdba7f67771606913e8510be72d52ac09dd22643cdc155b7cc2ed41c09a2a5fed32c322470ca44c2fe975c941eb0d0581d39f5cd775aac723
7
+ data.tar.gz: f6cf21861fe6154831fb4e47b93b88224bb3d65f37ea2496fd2c0c868e1836d1b590b1a67376229fefed57c019b97ec9ef5adef8a7d84d8ef0f8af9b29c322c8
@@ -4,7 +4,3 @@
4
4
 
5
5
  inherit_from:
6
6
  - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
7
- AllCops:
8
- TargetRubyVersion: 2.3
9
- Rails:
10
- Enabled: true
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "metanorma/version"
2
4
  require "asciidoctor"
3
5
  require "metanorma/util"
@@ -8,7 +10,10 @@ require "metanorma/registry"
8
10
  require "metanorma/processor"
9
11
  require "metanorma/asciidoctor_extensions"
10
12
  require "metanorma/compile"
13
+ require "metanorma/collection"
14
+ require "metanorma/collection_renderer"
15
+ require "metanorma/document"
11
16
 
17
+ # Metanorma module
12
18
  module Metanorma
13
-
14
19
  end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "relaton"
4
+ require "relaton/cli"
5
+ require "metanorma/collection_manifest"
6
+
7
+ module Metanorma
8
+ # Metanorma collection of documents
9
+ class Collection
10
+ # @return [String]
11
+ attr_reader :file
12
+
13
+ # @return [Array<String>] documents-inline to inject the XML into
14
+ # the collection manifest; documents-external to keeps them outside
15
+ attr_accessor :directives
16
+
17
+ # @return [Hash<String, Metanorma::Document>]
18
+ attr_accessor :documents
19
+
20
+ # @param file [String] path to source file
21
+ # @param directives [Array<String>] documents-inline to inject the XML into
22
+ # the collection manifest; documents-external to keeps them outside
23
+ # @param bibdata [RelatonBib::BibliographicItem]
24
+ # @param manifest [Metanorma::CollectionManifest]
25
+ # @param documents [Hash<String, Metanorma::Document>]
26
+ # @param prefatory [String]
27
+ # @param final [String]
28
+ # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
29
+ def initialize(**args)
30
+ @file = args[:file]
31
+ @directives = args[:directives] || []
32
+ @bibdata = args[:bibdata]
33
+ @manifest = args[:manifest]
34
+ @manifest.collection = self
35
+ @documents = args[:documents] || {}
36
+ if @documents.any? && !@directives.include?("documents-inline")
37
+ @directives << "documents-inline"
38
+ end
39
+ @documents.merge! @manifest.documents(File.dirname(@file))
40
+ @prefatory = args[:prefatory]
41
+ @final = args[:final]
42
+ end
43
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
44
+
45
+ # @return [String] XML
46
+ def to_xml
47
+ Nokogiri::XML::Builder.new do |xml|
48
+ xml.send("metanorma-collection",
49
+ "xmlns" => "http://metanorma.org") do |mc|
50
+ mc << @bibdata.to_xml(bibdata: true, date_format: :full)
51
+ @manifest.to_xml mc
52
+ content_to_xml "prefatory", mc
53
+ doccontainer mc
54
+ content_to_xml "final", mc
55
+ end
56
+ end.to_xml
57
+ end
58
+
59
+ def render(opts)
60
+ CollectionRenderer.render self, opts
61
+ end
62
+
63
+ class << self
64
+ # @param file [String]
65
+ # @return [RelatonBib::BibliographicItem,
66
+ # RelatonIso::IsoBibliographicItem]
67
+ def parse(file)
68
+ case file
69
+ when /\.xml$/ then parse_xml(file)
70
+ when /.ya?ml$/ then parse_yaml(file)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def parse_xml(file)
77
+ xml = Nokogiri::XML File.read(file, encoding: "UTF-8")
78
+ if (b = xml.at("/xmlns:metanorma-collection/xmlns:bibdata"))
79
+ bd = Relaton::Cli.parse_xml b
80
+ end
81
+ mnf_xml = xml.at("/xmlns:metanorma-collection/xmlns:manifest")
82
+ mnf = CollectionManifest.from_xml mnf_xml
83
+ pref = pref_final_content xml.at("//xmlns:prefatory-content")
84
+ fnl = pref_final_content xml.at("//xmlns:final-content")
85
+ new(file: file, bibdata: bd, manifest: mnf,
86
+ documents: docs_from_xml(xml, mnf), prefatory: pref, final: fnl)
87
+ end
88
+
89
+ def parse_yaml(file)
90
+ yaml = YAML.load_file file
91
+ if yaml["bibdata"]
92
+ bd = Relaton::Cli::YAMLConvertor.convert_single_file yaml["bibdata"]
93
+ end
94
+ mnf = CollectionManifest.from_yaml yaml["manifest"]
95
+ dirs = yaml["directives"]
96
+ pref = yaml["prefatory-content"]
97
+ fnl = yaml["final-content"]
98
+ new(file: file, directives: dirs, bibdata: bd, manifest: mnf,
99
+ prefatory: pref, final: fnl)
100
+ end
101
+
102
+ # @param xml [Nokogiri::XML::Document]
103
+ # @parma mnf [Metanorma::CollectionManifest]
104
+ # @return [Hash{String=>Metanorma::Document}]
105
+ def docs_from_xml(xml, mnf)
106
+ xml.xpath("//xmlns:doc-container/*/xmlns:bibdata")
107
+ .each_with_object({}) do |b, m|
108
+ bd = Relaton::Cli.parse_xml b
109
+ docref = mnf.docref_by_id bd.docidentifier.first.id
110
+ m[docref["identifier"]] = Document.new bd, docref["fileref"]
111
+ m
112
+ end
113
+ end
114
+
115
+ # @param xml [Nokogiri::XML::Element, nil]
116
+ # @return [String, nil]
117
+ def pref_final_content(xml)
118
+ return unless xml
119
+
120
+ <<~CONT
121
+
122
+ == #{xml.at('title')&.text}
123
+ #{xml.at('p')&.text}
124
+ CONT
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ # @return [String, nil]
131
+ attr_reader :prefatory, :final
132
+
133
+ # @return [String]
134
+ def dummy_header
135
+ <<~DUMMY
136
+ = X
137
+ A
138
+
139
+ DUMMY
140
+ end
141
+
142
+ # @param elm [String] 'prefatory' or 'final'
143
+ # @param builder [Nokogiri::XML::Builder]
144
+ def content_to_xml(elm, builder)
145
+ return unless (cnt = send(elm))
146
+
147
+ require "metanorma-#{doctype}"
148
+ out = sections(dummy_header + cnt)
149
+ builder.send(elm + "-content") { |b| b << out }
150
+ end
151
+
152
+ # @param cnt [String] prefatory/final content
153
+ # @return [String] XML
154
+ def sections(cnt)
155
+ c = Asciidoctor.convert(cnt, backend: doctype.to_sym, header_footer: true)
156
+ Nokogiri::XML(c).at("//xmlns:sections").children.to_xml
157
+ end
158
+
159
+ # @param builder [Nokogiri::XML::Builder]
160
+ def doccontainer(builder)
161
+ return unless Array(@directives).include? "documents-inline"
162
+
163
+ documents.each_with_index do |(_, d), i|
164
+ id = format("doc%<index>09d", index: i)
165
+ builder.send("doc-container", id: id) { |b| d.to_xml b }
166
+ end
167
+ end
168
+
169
+ # @return [String]
170
+ def doctype
171
+ @doctype ||= fetch_doctype || "standoc"
172
+ end
173
+
174
+ # @return [String]
175
+ def fetch_doctype
176
+ docid = @bibdata.docidentifier.first
177
+ return unless docid
178
+
179
+ docid.type&.downcase || docid.id&.sub(/\s.*$/, "")&.downcase
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ # Metanorma collection's manifest
5
+ class CollectionManifest
6
+ # @return [Metanorma::Collection]
7
+ attr_reader :collection
8
+
9
+ # @param level [String]
10
+ # @param title [String, nil]
11
+ # @param docref [Array<Hash{String=>String}>]
12
+ # @param manifest [Array<Metanorma::CollectionManifest>]
13
+ def initialize(level, title = nil, docref = [], manifest = [])
14
+ @level = level
15
+ @title = title
16
+ @docref = docref
17
+ @manifest = manifest
18
+ end
19
+
20
+ class << self
21
+ # @param mnf [Nokogiri::XML::Element]
22
+ # @return [Metanorma::CollectionManifest]
23
+ def from_yaml(mnf)
24
+ manifest = RelatonBib::HashConverter.array(mnf["manifest"]).map do |m|
25
+ from_yaml m
26
+ end
27
+ docref = RelatonBib::HashConverter.array mnf["docref"]
28
+ new(mnf["level"], mnf["title"], docref, manifest)
29
+ end
30
+
31
+ # @param mnf [Nokogiri::XML::Element]
32
+ # @return [Metanorma::CollectionManifest]
33
+ def from_xml(mnf)
34
+ level = mnf.at("level").text
35
+ title = mnf.at("title")&.text
36
+ manifest = mnf.xpath("xmlns:manifest").map { |m| from_xml(m) }
37
+ new(level, title, parse_docref(mnf), manifest)
38
+ end
39
+
40
+ private
41
+
42
+ # @param mnf [Nokogiri::XML::Element]
43
+ # @return [Hash{String=>String}]
44
+ def parse_docref(mnf)
45
+ mnf.xpath("xmlns:docref").map do |dr|
46
+ h = { "identifier" => dr.at("identifier").text }
47
+ h["fileref"] = dr[:fileref] if dr[:fileref]
48
+ h
49
+ end
50
+ end
51
+ end
52
+
53
+ # @param col [Metanorma::Collection]
54
+ def collection=(col)
55
+ @collection = col
56
+ @manifest.each { |mnf| mnf.collection = col }
57
+ end
58
+
59
+ # @param dir [String] path to coolection
60
+ # @return [Hash<String, Metanorma::Document>]
61
+ def documents(dir = "")
62
+ docs = @docref.each_with_object({}) do |dr, m|
63
+ next m unless dr["fileref"]
64
+
65
+ m[dr["identifier"]] = Document.parse_file File.join(dir, dr["fileref"])
66
+ m
67
+ end
68
+ @manifest.reduce(docs) do |mem, mnf|
69
+ mem.merge mnf.documents(dir)
70
+ end
71
+ end
72
+
73
+ # @param builder [Nokogiri::XML::Builder]
74
+ def to_xml(builder)
75
+ builder.manifest do |b|
76
+ b.level @level
77
+ b.title @title if @title
78
+ docref_to_xml b
79
+ @manifest.each { |m| m.to_xml b }
80
+ end
81
+ end
82
+
83
+ # @return [Array<Hash{String=>String}>]
84
+ def docrefs
85
+ return @docrefs if @docrefs
86
+
87
+ drfs = @docref.map { |dr| dr }
88
+ @manifest.reduce(drfs) { |mem, mnf| mem + mnf.docrefs }
89
+ end
90
+
91
+ def docref_by_id(docid)
92
+ refs = docrefs
93
+ dref = refs.detect { |k| k["identifier"] == docid }
94
+ dref || docrefs.detect { |k| /^#{k["identifier"]}/ =~ docid }
95
+ end
96
+
97
+ private
98
+
99
+ # @param builder [Nokogiri::XML::Builder]
100
+ def docref_to_xml(builder)
101
+ @docref.each do |dr|
102
+ drf = builder.docref { |b| b.identifier dr["identifier"] }
103
+ drf[:fileref] = dr["fileref"]
104
+ if collection.directives.include?("documents-inline")
105
+ id = collection.documents.find_index { |k, _| k == dr["identifier"] }
106
+ drf[:id] = format("doc%<index>09d", index: id)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,338 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "isodoc"
4
+
5
+ module Metanorma
6
+ # XML collection renderer
7
+ class CollectionRenderer
8
+ FORMATS = %i[html xml doc pdf].freeze
9
+
10
+ # This is only going to render the HTML collection
11
+ # @param xml [Metanorma::Collection] input XML collection
12
+ # @param folder [String] input folder
13
+ # @param options [Hash]
14
+ # @option options [String] :coverpage cover page HTML (Liquid template)
15
+ # @option options [Array<Symbol>] :format list of formats (xml,html,doc,pdf)
16
+ # @option options [String] :ourput_folder output directory
17
+ #
18
+ # We presuppose that the bibdata of the document is equivalent to that of
19
+ # the collection, and that the flavour gem can sensibly process it. We may
20
+ # need to enhance metadata in the flavour gems isodoc/metadata.rb with
21
+ # collection metadata
22
+ def initialize(xml, folder, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
23
+ check_options options
24
+ @xml = Nokogiri::XML xml # @xml is the collection manifest
25
+ @lang = @xml&.at(ns("//bibdata/language"))&.text || "en"
26
+ @script = @xml&.at(ns("//bibdata/script"))&.text || "Latn"
27
+ @doctype = doctype
28
+ require "metanorma-#{@doctype}"
29
+
30
+ # output processor for flavour
31
+ @isodoc = isodoc
32
+
33
+ @outdir = options[:output_folder]
34
+ @coverpage = options[:coverpage]
35
+ @format = options[:format]
36
+
37
+ # list of files in the collection
38
+ @files = read_files folder
39
+ FileUtils.rm_rf @outdir
40
+ FileUtils.mkdir_p @outdir
41
+ end
42
+
43
+ # @param col [Metanorma::Collection] XML collection
44
+ # @param options [Hash]
45
+ # @option options [String] :coverpage cover page HTML (Liquid template)
46
+ # @option options [Array<Synbol>] :format list of formats
47
+ # @option options [Strong] :ourput_folder output directory
48
+ def self.render(col, options = {})
49
+ folder = File.dirname col.file
50
+ cr = new(col.to_xml, folder, options)
51
+ cr.files
52
+ cr.concatenate(col, options)
53
+ cr.coverpage if options[:format]&.include?(:html)
54
+ end
55
+
56
+ def concatenate(col, options)
57
+ options[:format] << :presentation if options[:format].include?(:pdf)
58
+ options[:format].uniq.each do |e|
59
+ next unless %i(presentation xml).include?(e)
60
+ ext = e == :presentation ? "presentation.xml" : e.to_s
61
+ out = col.clone
62
+ out.directives << "documents-inline"
63
+ out.documents.keys.each do |id|
64
+ filename = @files[id][:outputs][e]
65
+ out.documents[id] = Metanorma::Document.raw_file(filename)
66
+ end
67
+ File.open(File.join(@outdir, "collection.#{ext}"), "w:UTF-8") { |f| f.write(out.to_xml) }
68
+ end
69
+ options[:format].include?(:pdf) and
70
+ pdfconv.convert(File.join(@outdir, "collection.presentation.xml"))
71
+ end
72
+
73
+ def pdfconv
74
+ x = Asciidoctor.load nil, backend: @doctype.to_sym
75
+ x.converter.pdf_converter(Dummy.new)
76
+ end
77
+
78
+ # Dummy class
79
+ class Dummy
80
+ def attr(_xyz); end
81
+ end
82
+
83
+ # The isodoc class for the metanorma flavour we are using
84
+ def isodoc # rubocop:disable Metrics/MethodLength
85
+ x = Asciidoctor.load nil, backend: @doctype.to_sym
86
+ isodoc = x.converter.html_converter(Dummy.new)
87
+ isodoc.i18n_init(@lang, @script) # read in internationalisation
88
+ # create the @meta class of isodoc, with "navigation" set to the index bar
89
+ # extracted from the manifest
90
+ nav = indexfile(@xml.at(ns("//manifest")))
91
+ i18n = isodoc.i18n
92
+ i18n.set(:navigation, nav)
93
+ isodoc.metadata_init(@lang, @script, i18n)
94
+ # populate the @meta class of isodoc with the various metadata fields
95
+ # native to the flavour; used to populate Liquid
96
+ isodoc.info(@xml, nil)
97
+ isodoc
98
+ end
99
+
100
+ # infer the flavour from the first document identifier; relaton does that
101
+ def doctype
102
+ if (docid = @xml&.at(ns("//bibdata/docidentifier/@type"))&.text)
103
+ dt = docid.downcase
104
+ elsif (docid = @xml&.at(ns("//bibdata/docidentifier"))&.text)
105
+ dt = docid.sub(/\s.*$/, "").lowercase
106
+ else return "standoc"
107
+ end
108
+ @registry = Metanorma::Registry.instance
109
+ @registry.alias(dt.to_sym)&.to_s || dt
110
+ end
111
+
112
+ def ns(xpath)
113
+ IsoDoc::Convert.new({}).ns(xpath)
114
+ end
115
+
116
+ # hash for each document in collection of document identifier to:
117
+ # document reference (fileref or id), type of document reference,
118
+ # and bibdata entry for that file
119
+ # @param path [String] path to collection
120
+ # @return [Hash{String=>Hash}]
121
+ def read_files(path) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
122
+ files = {}
123
+ @xml.xpath(ns("//docref")).each do |d|
124
+ identifier = d.at(ns("./identifier")).text
125
+ files[identifier] = if d["fileref"]
126
+ { type: "fileref",
127
+ ref: File.join(path, d["fileref"]) }
128
+ else { type: "id", ref: d["id"] }
129
+ end
130
+ file, _filename = targetfile(files[identifier], true)
131
+ xml = Nokogiri::XML(file)
132
+ files[identifier][:anchors] = read_anchors(xml)
133
+ files[identifier][:bibdata] = xml.at(ns("//bibdata"))
134
+ end
135
+ files
136
+ end
137
+
138
+ # map locality type and label (e.g. "clause" "1") to id = anchor for
139
+ # a document
140
+ def read_anchors(xml)
141
+ ret = {}
142
+ xrefs = @isodoc.xref_init(@lang, @script, @isodoc, @isodoc.i18n, {})
143
+ xrefs.parse xml
144
+ xrefs.get.each do |k, v|
145
+ v[:label] && v[:type] || next
146
+ ret[v[:type]] ||= {}
147
+ ret[v[:type]][v[:label]] = k
148
+ end
149
+ ret
150
+ end
151
+
152
+ # populate liquid template of ARGV[1] with metadata extracted from
153
+ # collection manifest
154
+ def coverpage
155
+ File.open(File.join(@outdir, "index.html"), "w:UTF-8") do |f|
156
+ f.write @isodoc.populate_template(File.read(@coverpage))
157
+ end
158
+ end
159
+
160
+ # @param elm [Nokogiri::XML::Element]
161
+ # @return [String]
162
+ def indexfile_title(elm)
163
+ lvl = elm&.at(ns("./level"))&.text&.capitalize
164
+ lbl = elm&.at(ns("./title"))&.text
165
+ "#{lvl}#{lvl && lbl ? ': ' : ''}#{lbl}"
166
+ end
167
+
168
+ # uses the identifier to label documents; other attributes (title) can be
169
+ # looked up in @files[id][:bibdata]
170
+ #
171
+ # @param elm [Nokogiri::XML::Element]
172
+ # @param builder [Nokogiri::XML::Builder]
173
+ def indexfile_docref(elm, builder)
174
+ return "" unless elm.at(ns("./docref"))
175
+
176
+ builder.ul { |b| docrefs(elm, b) }
177
+ end
178
+
179
+ # @param elm [Nokogiri::XML::Element]
180
+ # @param builder [Nokogiri::XML::Builder]
181
+ def docrefs(elm, builder)
182
+ elm.xpath(ns("./docref")).each do |d|
183
+ identifier = d.at(ns("./identifier")).text
184
+ link = if d["fileref"] then d["fileref"].sub(/\.xml$/, ".html")
185
+ else d["id"] + ".html"
186
+ end
187
+ builder.li { builder.a identifier, href: link }
188
+ end
189
+ end
190
+
191
+ # single level navigation list, with hierarchical nesting
192
+ # if multiple lists are needed as separate HTML fragments, multiple
193
+ # instances of this function will be needed,
194
+ # and associated to different variables in the call to @isodoc.metadata_init
195
+ # (including possibly an array of HTML fragments)
196
+ #
197
+ # @param elm [Nokogiri::XML::Element]
198
+ # @return [String] XML
199
+ def indexfile(elm)
200
+ Nokogiri::HTML::Builder.new do |b|
201
+ b.ul do
202
+ b.li indexfile_title(elm)
203
+ indexfile_docref(elm, b)
204
+ elm.xpath(ns("./manifest")).each do |d|
205
+ b << indexfile(d)
206
+ end
207
+ end
208
+ end.doc.root.to_html
209
+ end
210
+
211
+ # return file contents + output filename for each file in the collection,
212
+ # given a docref entry
213
+ # @param data [Hash]
214
+ # @param read [Boolean]
215
+ # @return [Array<String, nil>]
216
+ def targetfile(data, read = false)
217
+ if data[:type] == "fileref" then ref_file data[:ref], read
218
+ else xml_file data[:id], read
219
+ end
220
+ end
221
+
222
+ # @param ref [String]
223
+ # @param read [Boolean]
224
+ # @return [Array<String, nil>]
225
+ def ref_file(ref, read)
226
+ file = File.read(ref, encoding: "utf-8") if read
227
+ filename = ref.sub(/\.xml$/, ".html")
228
+ [file, filename]
229
+ end
230
+
231
+ # @param id [String]
232
+ # @param read [Boolean]
233
+ # @return [Array<String, nil>]
234
+ def xml_file(id, read)
235
+ file = @xml.at(ns("//doc-container[@id = '#{id}']")).to_xml if read
236
+ filename = id + ".html"
237
+ [file, filename]
238
+ end
239
+
240
+ # @param bib [Nokogiri::XML::Element]
241
+ # @param identifier [String]
242
+ def update_bibitem(bib, identifier) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
243
+ docid = bib&.at(ns("./docidentifier"))&.text
244
+ unless @files[docid]
245
+ warn "Cannot find crossreference to document #{docid} in document "\
246
+ "#{identifier}!"
247
+ abort
248
+ end
249
+ id = bib["id"]
250
+ newbib = bib.replace(@files[docid][:bibdata])
251
+ newbib.name = "bibitem"
252
+ newbib["id"] = id
253
+ newbib&.at(ns("./ext"))&.remove
254
+ _file, url = targetfile(@files[docid], false)
255
+ uri_node = Nokogiri::XML::Node.new "uri", newbib
256
+ uri_node[:type] = "citation"
257
+ uri_node.content = url
258
+ newbib.at(ns("./docidentifier")).previous = uri_node
259
+ end
260
+
261
+ # TODO: update crossreferences to other files in the selection
262
+ # repo(current-metanorma-collection/ISO 17301-1:2016)
263
+ # replaced by
264
+ # bibdata of "ISO 17301-1:2016" in situ as bibitem
265
+ # Any erefs to that bibitem id are replaced with relative URL
266
+ # Preferably with anchor, and is a job to realise dynamic lookup of
267
+ # localities
268
+ # @param file [String] XML content
269
+ # @param identifier [String] docid
270
+ # @return [String] XML content
271
+ def update_xrefs(file, identifier)
272
+ docxml = Nokogiri::XML(file)
273
+ docxml.xpath(ns("//bibitem[not(ancestor::bibitem)]")).each do |b|
274
+ docid = b&.at(ns("./docidentifier[@type = 'repository']"))&.text
275
+ next unless docid && %r{^current-metanorma-collection/}.match(docid)
276
+
277
+ update_bibitem(b, identifier)
278
+ update_anchors(b, docxml, docid)
279
+ end
280
+ docxml.to_xml
281
+ end
282
+
283
+ # if there is a crossref to another document, with no anchor, retrieve the
284
+ # anchor given the locality, and insert it into the crossref
285
+ def update_anchors(bib, docxml, _id) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
286
+ docid = bib&.at(ns("./docidentifier"))&.text
287
+ docxml.xpath("//xmlns:eref[@citeas = '#{docid}']").each do |e|
288
+ e.at(ns(".//locality[@type = 'anchor']")).nil? || next
289
+ ins = e.at(ns("./localityStack")) || next
290
+ type = ins&.at(ns("./locality/@type"))&.text
291
+ ref = ins&.at(ns("./locality/referenceFrom"))&.text
292
+ (anchor = @files[docid][:anchors][type][ref]) || next
293
+ ref_from = Nokogiri::XML::Node.new "referenceFrom", bib
294
+ ref_from.content = anchor.sub(/^_/, "")
295
+ locality = Nokogiri::XML::Node.new "locality", bib
296
+ locality[:type] = "anchor"
297
+ locality.add_child ref_from
298
+ ins << locality
299
+ end
300
+ end
301
+
302
+ # process each file in the collection
303
+ # files are held in memory, and altered as postprocessing
304
+ def files # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
305
+ @files.each do |identifier, x|
306
+ file, filename = targetfile(x, true)
307
+ file = update_xrefs(file, identifier)
308
+ Tempfile.open(["collection", ".xml"], encoding: "utf-8") do |f|
309
+ f.write(file)
310
+ f.close
311
+ # warn "metanorma compile -x html #{f.path}"
312
+ c = Compile.new
313
+ c.compile f.path, format: :asciidoc, extension_keys: @format
314
+ @files[identifier][:outputs] = {}
315
+ @format.each do |e|
316
+ ext = c.processor.output_formats[e]
317
+ fn = File.basename(filename).sub(/(?<=\.)[^\.]+$/, ext.to_s)
318
+ FileUtils.mv f.path.sub(/\.xml$/, ".#{ext}"), File.join(@outdir, fn)
319
+ @files[identifier][:outputs][e] = File.join(@outdir, fn)
320
+ end
321
+ end
322
+ end
323
+ end
324
+
325
+ private
326
+
327
+ # @param options [Hash]
328
+ # @raise [ArgumentError]
329
+ def check_options(options)
330
+ unless options[:format].is_a?(Array) && (FORMATS & options[:format]).any?
331
+ raise ArgumentError, "Need to specify formats (xml,html,pdf,doc)"
332
+ end
333
+ return if !options[:format].include?(:html) || options[:coverpage]
334
+
335
+ raise ArgumentError, "Need to specify a coverpage to render HTML"
336
+ end
337
+ end
338
+ end
@@ -5,7 +5,7 @@ require "htmlentities"
5
5
  module Metanorma
6
6
  class Compile
7
7
  # @return [Array<String>]
8
- attr_reader :errors
8
+ attr_reader :errors, :processor
9
9
 
10
10
  def initialize
11
11
  @registry = Metanorma::Registry.instance
@@ -1,12 +1,93 @@
1
1
  module Metanorma
2
2
  class Document
3
+ # @return [Strin]
4
+ attr_reader :file
3
5
 
4
- def initialize
5
- @input_file
6
- @input_format
7
- @document_type
8
- @output_formats
6
+ # @param bibitem [RelatonBib::BibliographicItem]
7
+ def initialize(bibitem, file, options = {})
8
+ @bibitem = bibitem
9
+ @file = file
10
+ @raw = options[:raw]
9
11
  end
10
12
 
13
+ class << self
14
+ # @param file [String] file path
15
+ # @return [Metanorma::Document]
16
+ def parse_file(file)
17
+ new bibitem(file), file
18
+ end
19
+
20
+ # #param xml [Nokogiri::XML::Document, Nokogiri::XML::Element]
21
+ # @return [Metanorma::Document]
22
+ def parse_xml(xml)
23
+ new from_xml(xml)
24
+ end
25
+
26
+ # raw XML file, can be used to put in entire file instead of just bibitem
27
+ def raw_file(filename)
28
+ doc = Nokogiri::XML(File.read(filename, encoding: "UTF-8"))
29
+ new(doc, filename, raw: true)
30
+ end
31
+
32
+ private
33
+
34
+ # #param xml [Nokogiri::XML::Document, Nokogiri::XML::Element]
35
+ # @return [RelatonBib::BibliographicItem,RelatonIso::IsoBibliographicItem]
36
+ def from_xml(xml)
37
+ Relaton::Cli.parse_xml xml.at("//xmlns:bibitem|//xmlns:bibdata")
38
+ end
39
+
40
+ # @param file [String]
41
+ # @return [Symbol] file type
42
+ def format(file)
43
+ case file
44
+ when /\.xml$/ then :xml
45
+ when /.ya?ml$/ then :yaml
46
+ end
47
+ end
48
+
49
+ # @param file [String]
50
+ # @return [RelatonBib::BibliographicItem,
51
+ # RelatonIso::IsoBibliographicItem]
52
+ def bibitem(file)
53
+ case format(file)
54
+ when :xml
55
+ from_xml Nokogiri::XML(File.read(file, encoding: "UTF-8"))
56
+ when :yaml
57
+ yaml = File.read(file, encoding: "UTF-8")
58
+ Relaton::Cli::YAMLConvertor.convert_single_file(yaml)
59
+ end
60
+ end
61
+ end
62
+
63
+ # @param builder [Nokogiri::XML::Builder, nil]
64
+ # @return [Nokogiri::XML::Builder, String]
65
+ def to_xml(builder = nil)
66
+ if builder
67
+ render_xml builder
68
+ else
69
+ Nokogiri::XML::Builder.new do |b|
70
+ root = render_xml b
71
+ root["xmlns"] = "http://metanorma.org"
72
+ end.to_xml
73
+ end
74
+ end
75
+
76
+ # @return [String]
77
+ def type
78
+ @type ||= (@bibitem.docidentifier.first&.type&.downcase ||
79
+ @bibitem.docidentifier.first&.id&.match(/^[^\s]+/)&.to_s)&.downcase ||
80
+ "standoc"
81
+ end
82
+
83
+ private
84
+
85
+ def render_xml(builder)
86
+ if @raw
87
+ builder << @bibitem.root.to_xml
88
+ else
89
+ builder.send(type + "-standard") { |b| b << @bibitem.to_xml(bibdata: true) }
90
+ end
91
+ end
11
92
  end
12
93
  end
@@ -7,8 +7,7 @@ module Metanorma
7
7
 
8
8
  def process(file, filename, type, options = {})
9
9
  require "asciidoctor"
10
- ::Asciidoctor.convert(
11
- file,
10
+ out_opts = {
12
11
  to_file: false,
13
12
  safe: :safe,
14
13
  backend: type,
@@ -16,8 +15,28 @@ module Metanorma
16
15
  attributes: [
17
16
  "nodoc", "stem", "xrefstyle=short", "docfile=#{filename}",
18
17
  "output_dir=#{options[:"output-dir"]}"
19
- ],
20
- )
18
+ ]
19
+ }
20
+ unless asciidoctor_validate(file, filename, out_opts)
21
+ warn "Cannot continue compiling Asciidoctor document"
22
+ abort
23
+ end
24
+ ::Asciidoctor.convert(file, out_opts)
25
+ end
26
+
27
+ def asciidoctor_validate(file, filename, options)
28
+ err = nil
29
+ begin
30
+ previous_stderr, $stderr = $stderr, StringIO.new
31
+ ::Asciidoctor.load(file, options)
32
+ %r{(\n|^)asciidoctor: ERROR: ['"]?#{Regexp.escape(filename ||
33
+ "<empty>")}['"]?: line \d+: include file not found: }.match($stderr.string) and
34
+ err = $stderr.string
35
+ ensure
36
+ $stderr = previous_stderr
37
+ end
38
+ warn err unless err.nil?
39
+ err.nil?
21
40
  end
22
41
 
23
42
  def extract_metanorma_options(file)
@@ -4,10 +4,16 @@ require_relative "./utils.rb"
4
4
  module Metanorma
5
5
  module Output
6
6
  class XslfoPdf < Base
7
- def convert(url_path, output_path, xsl_stylesheet)
7
+ def convert(url_path, output_path, xsl_stylesheet, options = "")
8
8
  return if url_path.nil? || output_path.nil? || xsl_stylesheet.nil?
9
9
 
10
- Mn2pdf.convert(url_path, output_path, xsl_stylesheet)
10
+ Mn2pdf.convert(quote(url_path), quote(output_path), quote(xsl_stylesheet), options)
11
+ end
12
+
13
+ def quote(x)
14
+ return x if /^'.*'$/.match(x)
15
+ return x if /^".*"$/.match(x)
16
+ %("#{x}")
11
17
  end
12
18
  end
13
19
  end
@@ -1,3 +1,3 @@
1
1
  module Metanorma
2
- VERSION = "1.1.2"
2
+ VERSION = "1.1.7"
3
3
  end
@@ -27,11 +27,16 @@ Gem::Specification.new do |spec|
27
27
  spec.add_runtime_dependency 'htmlentities'
28
28
  spec.add_runtime_dependency 'nokogiri'
29
29
  spec.add_runtime_dependency 'mn2pdf', "~> 1"
30
+ # get relaton-cli to avoic circular reference with metanorma-standoc
31
+ #spec.add_dependency "relaton-cli"
32
+ #spec.add_dependency "metanorma-standoc", "~> 1.5.3"
30
33
 
31
34
  spec.add_development_dependency "rake", "~> 12.0"
32
35
  spec.add_development_dependency "rspec", "~> 3.0"
33
36
  spec.add_development_dependency "byebug", "~> 10.0"
34
37
  spec.add_development_dependency "rspec-command", "~> 1.0"
35
38
  spec.add_development_dependency "equivalent-xml", "~> 0.6"
36
- spec.add_development_dependency "metanorma-iso", "~> 1.4"
39
+ spec.add_development_dependency "metanorma-iso", "~> 1.5.8"
40
+ spec.add_development_dependency "sassc", "~> 2.4.0"
41
+ #spec.add_development_dependency "isodoc", "~> 1.2.1"
37
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metanorma
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-10 00:00:00.000000000 Z
11
+ date: 2020-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -142,14 +142,28 @@ dependencies:
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '1.4'
145
+ version: 1.5.8
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '1.4'
152
+ version: 1.5.8
153
+ - !ruby/object:Gem::Dependency
154
+ name: sassc
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 2.4.0
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 2.4.0
153
167
  description: Library to process any Metanorma standard.
154
168
  email:
155
169
  - open.source@ribose.com
@@ -182,6 +196,9 @@ files:
182
196
  - lib/metanorma.rb
183
197
  - lib/metanorma/asciidoctor_extensions.rb
184
198
  - lib/metanorma/asciidoctor_extensions/glob_include_processor.rb
199
+ - lib/metanorma/collection.rb
200
+ - lib/metanorma/collection_manifest.rb
201
+ - lib/metanorma/collection_renderer.rb
185
202
  - lib/metanorma/compile.rb
186
203
  - lib/metanorma/config.rb
187
204
  - lib/metanorma/document.rb