metanorma 1.1.1 → 1.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11c17884b5c38b4bc7b7ada32496534008bc38e6748d5b96cf67c70048054f9d
4
- data.tar.gz: 0a9339c045590799855c9e0b50729c486901ac562300fdba69b1f9285fc8b6f0
3
+ metadata.gz: aecd92b31139e0a3f76ff7485f3bde8e5b6d89be735eafadc0215962b8b1aec1
4
+ data.tar.gz: 4c00ec06b3d9a632bbdbc810aa46635fef4c85f88382c6e014a8238261987f7e
5
5
  SHA512:
6
- metadata.gz: 50a11067a41ac75ebabdb6914383ec36820332f0a74ec6fe54b4075da613949aab3a4ad041c07ae210987aa03207dfdb4b253af6e00ff5c30fed9e82682ae621
7
- data.tar.gz: 0b2eaf2a627b6fc3b359a51043ebee36784eae974e8b1a6bf75dc2fbdacb5229975131c37bb579dcf8efdb962605a4d73aa6f4e36dec4e2a6595425867e5ebc8
6
+ metadata.gz: 570efb165e57540e107f6f29acd53a373d702c825eefd1c9ee3e04fac4644432fc9347400dea3a1b48f8aa03c28ac31634bd4a3545dc86622768d2285a9cb87f
7
+ data.tar.gz: 554df7349b548f35d6fbf3f04d453964c3aa7a9067c496f4815eed42de07ceeaca4b93a7278f41709343744569c7c45b096916b184050366369597b22281ed46
data/.gitignore CHANGED
@@ -19,3 +19,4 @@
19
19
  .rubocop-https---raw-githubusercontent-com-riboseinc-oss-guides-master-ci-rubocop-yml
20
20
  Gemfile.lock
21
21
  relaton/
22
+ .vscode/
@@ -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
+ @bibdata.to_xml mc, 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,330 @@
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].each do |e|
58
+ next unless %i(presentation xml).include?(e)
59
+ ext = e == :presentation ? "presentation.xml" : e.to_s
60
+ out = col.clone
61
+ out.directives << "documents-inline"
62
+ out.documents.keys.each do |id|
63
+ filename = @files[id][:outputs][e]
64
+ out.documents[id] = Metanorma::Document.raw_file(filename)
65
+ end
66
+ File.open(File.join(@outdir, "collection.#{ext}"), "w:UTF-8") { |f| f.write(out.to_xml) }
67
+ end
68
+ end
69
+
70
+ # Dummy class
71
+ class Dummy
72
+ def attr(_xyz); end
73
+ end
74
+
75
+ # The isodoc class for the metanorma flavour we are using
76
+ def isodoc # rubocop:disable Metrics/MethodLength
77
+ x = Asciidoctor.load nil, backend: @doctype.to_sym
78
+ isodoc = x.converter.html_converter(Dummy.new)
79
+ isodoc.i18n_init(@lang, @script) # read in internationalisation
80
+ # create the @meta class of isodoc, with "navigation" set to the index bar
81
+ # extracted from the manifest
82
+ nav = indexfile(@xml.at(ns("//manifest")))
83
+ i18n = isodoc.i18n
84
+ i18n.set(:navigation, nav)
85
+ isodoc.metadata_init(@lang, @script, i18n)
86
+ # populate the @meta class of isodoc with the various metadata fields
87
+ # native to the flavour; used to populate Liquid
88
+ isodoc.info(@xml, nil)
89
+ isodoc
90
+ end
91
+
92
+ # infer the flavour from the first document identifier; relaton does that
93
+ def doctype
94
+ if (docid = @xml&.at(ns("//bibdata/docidentifier/@type"))&.text)
95
+ dt = docid.downcase
96
+ elsif (docid = @xml&.at(ns("//bibdata/docidentifier"))&.text)
97
+ dt = docid.sub(/\s.*$/, "").lowercase
98
+ else return "standoc"
99
+ end
100
+ @registry = Metanorma::Registry.instance
101
+ @registry.alias(dt.to_sym)&.to_s || dt
102
+ end
103
+
104
+ def ns(xpath)
105
+ IsoDoc::Convert.new({}).ns(xpath)
106
+ end
107
+
108
+ # hash for each document in collection of document identifier to:
109
+ # document reference (fileref or id), type of document reference,
110
+ # and bibdata entry for that file
111
+ # @param path [String] path to collection
112
+ # @return [Hash{String=>Hash}]
113
+ def read_files(path) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
114
+ files = {}
115
+ @xml.xpath(ns("//docref")).each do |d|
116
+ identifier = d.at(ns("./identifier")).text
117
+ files[identifier] = if d["fileref"]
118
+ { type: "fileref",
119
+ ref: File.join(path, d["fileref"]) }
120
+ else { type: "id", ref: d["id"] }
121
+ end
122
+ file, _filename = targetfile(files[identifier], true)
123
+ xml = Nokogiri::XML(file)
124
+ files[identifier][:anchors] = read_anchors(xml)
125
+ files[identifier][:bibdata] = xml.at(ns("//bibdata"))
126
+ end
127
+ files
128
+ end
129
+
130
+ # map locality type and label (e.g. "clause" "1") to id = anchor for
131
+ # a document
132
+ def read_anchors(xml)
133
+ ret = {}
134
+ xrefs = @isodoc.xref_init(@lang, @script, @isodoc, @isodoc.i18n, {})
135
+ xrefs.parse xml
136
+ xrefs.get.each do |k, v|
137
+ v[:label] && v[:type] || next
138
+ ret[v[:type]] ||= {}
139
+ ret[v[:type]][v[:label]] = k
140
+ end
141
+ ret
142
+ end
143
+
144
+ # populate liquid template of ARGV[1] with metadata extracted from
145
+ # collection manifest
146
+ def coverpage
147
+ File.open(File.join(@outdir, "index.html"), "w:UTF-8") do |f|
148
+ f.write @isodoc.populate_template(File.read(@coverpage))
149
+ end
150
+ end
151
+
152
+ # @param elm [Nokogiri::XML::Element]
153
+ # @return [String]
154
+ def indexfile_title(elm)
155
+ lvl = elm&.at(ns("./level"))&.text&.capitalize
156
+ lbl = elm&.at(ns("./title"))&.text
157
+ "#{lvl}#{lvl && lbl ? ': ' : ''}#{lbl}"
158
+ end
159
+
160
+ # uses the identifier to label documents; other attributes (title) can be
161
+ # looked up in @files[id][:bibdata]
162
+ #
163
+ # @param elm [Nokogiri::XML::Element]
164
+ # @param builder [Nokogiri::XML::Builder]
165
+ def indexfile_docref(elm, builder)
166
+ return "" unless elm.at(ns("./docref"))
167
+
168
+ builder.ul { |b| docrefs(elm, b) }
169
+ end
170
+
171
+ # @param elm [Nokogiri::XML::Element]
172
+ # @param builder [Nokogiri::XML::Builder]
173
+ def docrefs(elm, builder)
174
+ elm.xpath(ns("./docref")).each do |d|
175
+ identifier = d.at(ns("./identifier")).text
176
+ link = if d["fileref"] then d["fileref"].sub(/\.xml$/, ".html")
177
+ else d["id"] + ".html"
178
+ end
179
+ builder.li { builder.a identifier, href: link }
180
+ end
181
+ end
182
+
183
+ # single level navigation list, with hierarchical nesting
184
+ # if multiple lists are needed as separate HTML fragments, multiple
185
+ # instances of this function will be needed,
186
+ # and associated to different variables in the call to @isodoc.metadata_init
187
+ # (including possibly an array of HTML fragments)
188
+ #
189
+ # @param elm [Nokogiri::XML::Element]
190
+ # @return [String] XML
191
+ def indexfile(elm)
192
+ Nokogiri::HTML::Builder.new do |b|
193
+ b.ul do
194
+ b.li indexfile_title(elm)
195
+ indexfile_docref(elm, b)
196
+ elm.xpath(ns("./manifest")).each do |d|
197
+ b << indexfile(d)
198
+ end
199
+ end
200
+ end.doc.root.to_html
201
+ end
202
+
203
+ # return file contents + output filename for each file in the collection,
204
+ # given a docref entry
205
+ # @param data [Hash]
206
+ # @param read [Boolean]
207
+ # @return [Array<String, nil>]
208
+ def targetfile(data, read = false)
209
+ if data[:type] == "fileref" then ref_file data[:ref], read
210
+ else xml_file data[:id], read
211
+ end
212
+ end
213
+
214
+ # @param ref [String]
215
+ # @param read [Boolean]
216
+ # @return [Array<String, nil>]
217
+ def ref_file(ref, read)
218
+ file = File.read(ref, encoding: "utf-8") if read
219
+ filename = ref.sub(/\.xml$/, ".html")
220
+ [file, filename]
221
+ end
222
+
223
+ # @param id [String]
224
+ # @param read [Boolean]
225
+ # @return [Array<String, nil>]
226
+ def xml_file(id, read)
227
+ file = @xml.at(ns("//doc-container[@id = '#{id}']")).to_xml if read
228
+ filename = id + ".html"
229
+ [file, filename]
230
+ end
231
+
232
+ # @param bib [Nokogiri::XML::Element]
233
+ # @param identifier [String]
234
+ def update_bibitem(bib, identifier) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
235
+ docid = bib&.at(ns("./docidentifier"))&.text
236
+ unless @files[docid]
237
+ warn "Cannot find crossreference to document #{docid} in document "\
238
+ "#{identifier}!"
239
+ abort
240
+ end
241
+ id = bib["id"]
242
+ newbib = bib.replace(@files[docid][:bibdata])
243
+ newbib.name = "bibitem"
244
+ newbib["id"] = id
245
+ newbib&.at(ns("./ext"))&.remove
246
+ _file, url = targetfile(@files[docid], false)
247
+ uri_node = Nokogiri::XML::Node.new "uri", newbib
248
+ uri_node[:type] = "citation"
249
+ uri_node.content = url
250
+ newbib.at(ns("./docidentifier")).previous = uri_node
251
+ end
252
+
253
+ # TODO: update crossreferences to other files in the selection
254
+ # repo(current-metanorma-collection/ISO 17301-1:2016)
255
+ # replaced by
256
+ # bibdata of "ISO 17301-1:2016" in situ as bibitem
257
+ # Any erefs to that bibitem id are replaced with relative URL
258
+ # Preferably with anchor, and is a job to realise dynamic lookup of
259
+ # localities
260
+ # @param file [String] XML content
261
+ # @param identifier [String] docid
262
+ # @return [String] XML content
263
+ def update_xrefs(file, identifier)
264
+ docxml = Nokogiri::XML(file)
265
+ docxml.xpath(ns("//bibitem[not(ancestor::bibitem)]")).each do |b|
266
+ docid = b&.at(ns("./docidentifier[@type = 'repository']"))&.text
267
+ next unless docid && %r{^current-metanorma-collection/}.match(docid)
268
+
269
+ update_bibitem(b, identifier)
270
+ update_anchors(b, docxml, docid)
271
+ end
272
+ docxml.to_xml
273
+ end
274
+
275
+ # if there is a crossref to another document, with no anchor, retrieve the
276
+ # anchor given the locality, and insert it into the crossref
277
+ def update_anchors(bib, docxml, _id) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
278
+ docid = bib&.at(ns("./docidentifier"))&.text
279
+ docxml.xpath("//xmlns:eref[@citeas = '#{docid}']").each do |e|
280
+ e.at(ns(".//locality[@type = 'anchor']")).nil? || next
281
+ ins = e.at(ns("./localityStack")) || next
282
+ type = ins&.at(ns("./locality/@type"))&.text
283
+ ref = ins&.at(ns("./locality/referenceFrom"))&.text
284
+ (anchor = @files[docid][:anchors][type][ref]) || next
285
+ ref_from = Nokogiri::XML::Node.new "referenceFrom", bib
286
+ ref_from.content = anchor.sub(/^_/, "")
287
+ locality = Nokogiri::XML::Node.new "locality", bib
288
+ locality[:type] = "anchor"
289
+ locality.add_child ref_from
290
+ ins << locality
291
+ end
292
+ end
293
+
294
+ # process each file in the collection
295
+ # files are held in memory, and altered as postprocessing
296
+ def files # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
297
+ @files.each do |identifier, x|
298
+ file, filename = targetfile(x, true)
299
+ file = update_xrefs(file, identifier)
300
+ Tempfile.open(["collection", ".xml"], encoding: "utf-8") do |f|
301
+ f.write(file)
302
+ f.close
303
+ # warn "metanorma compile -x html #{f.path}"
304
+ c = Compile.new
305
+ c.compile f.path, format: :asciidoc, extension_keys: @format
306
+ @files[identifier][:outputs] = {}
307
+ @format.each do |e|
308
+ ext = c.processor.output_formats[e]
309
+ fn = File.basename(filename).sub(/(?<=\.)[^\.]+$/, ext.to_s)
310
+ FileUtils.mv f.path.sub(/\.xml$/, ".#{ext}"), File.join(@outdir, fn)
311
+ @files[identifier][:outputs][e] = File.join(@outdir, fn)
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ private
318
+
319
+ # @param options [Hash]
320
+ # @raise [ArgumentError]
321
+ def check_options(options)
322
+ unless options[:format].is_a?(Array) && (FORMATS & options[:format]).any?
323
+ raise ArgumentError, "Need to specify formats (xml,html,pdf,doc)"
324
+ end
325
+ return if !options[:format].include?(:html) || options[:coverpage]
326
+
327
+ raise ArgumentError, "Need to specify a coverpage to render HTML"
328
+ end
329
+ end
330
+ 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
@@ -15,7 +15,7 @@ module Metanorma
15
15
  def compile(filename, options = {})
16
16
  require_libraries(options)
17
17
  options = options_extract(filename, options)
18
- validate(options) or return nil
18
+ validate_type(options) && validate_format(options) || (return nil)
19
19
  @processor = @registry.find_processor(options[:type].to_sym)
20
20
  extensions = get_extensions(options) or return nil
21
21
  (file, isodoc = process_input(filename, options)) or return nil
@@ -47,6 +47,7 @@ module Metanorma
47
47
  o = Metanorma::Input::Asciidoc.new.extract_metanorma_options(content)
48
48
  o = o.merge(xml_options_extract(content))
49
49
  options[:type] ||= o[:type]&.to_sym
50
+ t = @registry.alias(options[:type]) and options[:type] = t
50
51
  dir = filename.sub(%r(/[^/]+$), "/")
51
52
  options[:relaton] ||= "#{dir}/#{o[:relaton]}" if o[:relaton]
52
53
  options[:sourcecode] ||= "#{dir}/#{o[:sourcecode]}" if o[:sourcecode]
@@ -57,10 +58,6 @@ module Metanorma
57
58
  options
58
59
  end
59
60
 
60
- def validate(options)
61
- validate_type(options) && validate_format(options)
62
- end
63
-
64
61
  def validate_type(options)
65
62
  unless options[:type]
66
63
  Util.log("[metanorma] Error: Please specify a standard type: #{@registry.supported_backends}.", :error)
@@ -122,7 +119,7 @@ module Metanorma
122
119
  dir = File.dirname(filename)
123
120
  dir != '.' and
124
121
  file.gsub!(/^include::/, "include::#{dir}/")
125
- [file, @processor.input_to_isodoc(file, filename)]
122
+ [file, @processor.input_to_isodoc(file, filename, options)]
126
123
  when ".xml"
127
124
  Util.log("[metanorma] Processing: Metanorma XML input.", :info)
128
125
  # TODO NN: this is a hack -- we should provide/bridge the
@@ -229,15 +226,16 @@ module Metanorma
229
226
 
230
227
  # isodoc is Raw Metanorma XML
231
228
  def process_extensions(extensions, file, isodoc, options)
232
- xml_name = options[:filename].sub(/\.[^.]+$/, ".xml")
233
- presentationxml_name = options[:filename].sub(/\.[^.]+$/, ".presentation.xml")
229
+ f = change_output_dir options
230
+ xml_name = f.sub(/\.[^.]+$/, ".xml")
231
+ presentationxml_name = f.sub(/\.[^.]+$/, ".presentation.xml")
234
232
  extensions.sort do |a, b|
235
233
  sort_extensions_execution(a) <=> sort_extensions_execution(b)
236
234
  end.each do |ext|
237
- file_extension = @processor.output_formats[ext]
238
- outfilename = options[:filename].sub(/\.[^.]+$/, ".#{file_extension}")
239
235
  isodoc_options = @processor.extract_options(file)
240
236
  isodoc_options[:datauriimage] = true if options[:datauriimage]
237
+ file_extension = @processor.output_formats[ext]
238
+ outfilename = f.sub(/\.[^.]+$/, ".#{file_extension}")
241
239
  if ext == :rxl
242
240
  options[:relaton] = outfilename
243
241
  relaton_export(isodoc, options)
@@ -246,12 +244,23 @@ module Metanorma
246
244
  @processor.use_presentation_xml(ext) ?
247
245
  @processor.output(nil, presentationxml_name, outfilename, ext, isodoc_options) :
248
246
  @processor.output(isodoc, xml_name, outfilename, ext, isodoc_options)
249
- rescue StandardError => e
247
+ rescue StandardError => e
250
248
  puts e.message
251
249
  end
252
250
  end
253
251
  wrap_html(options, file_extension, outfilename)
254
252
  end
255
253
  end
254
+
255
+ private
256
+
257
+ # @param options [Hash]
258
+ # @return [String]
259
+ def change_output_dir(options)
260
+ if options[:"output-dir"]
261
+ File.join options[:"output-dir"], File.basename(options[:filename])
262
+ else options[:filename]
263
+ end
264
+ end
256
265
  end
257
266
  end
@@ -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| @bibitem.to_xml b, bibdata: true }
90
+ end
91
+ end
11
92
  end
12
93
  end
@@ -5,16 +5,38 @@ module Metanorma
5
5
 
6
6
  class Asciidoc < Base
7
7
 
8
- def process(file, filename, type)
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,
15
14
  header_footer: true,
16
- attributes: ["nodoc", "stem", "xrefstyle=short", "docfile=#{filename}"]
17
- )
15
+ attributes: [
16
+ "nodoc", "stem", "xrefstyle=short", "docfile=#{filename}",
17
+ "output_dir=#{options[:"output-dir"]}"
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?
18
40
  end
19
41
 
20
42
  def extract_metanorma_options(file)
@@ -7,7 +7,13 @@ module Metanorma
7
7
  def convert(url_path, output_path, xsl_stylesheet)
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))
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
@@ -20,10 +20,14 @@ module Metanorma
20
20
  }
21
21
  end
22
22
 
23
- def input_to_isodoc(file, filename)
24
- raise "This is an abstract class!"
23
+ def input_to_isodoc(file, filename, options = {})
24
+ Metanorma::Input::Asciidoc.new.process(file, filename, @asciidoctor_backend, options)
25
25
  end
26
26
 
27
+ # def input_to_isodoc(file, filename)
28
+ # raise "This is an abstract class!"
29
+ # end
30
+
27
31
  def use_presentation_xml(ext)
28
32
  case ext
29
33
  when :html, :doc, :pdf then true
@@ -14,14 +14,23 @@ module Metanorma
14
14
 
15
15
  def initialize
16
16
  @processors = {}
17
+ @aliases = {csd: :cc, m3d: :m3aawg, mpfd: :mpfa, csand: :csa}
18
+ end
19
+
20
+ def alias(x)
21
+ @aliases[x]
17
22
  end
18
23
 
19
24
  def register processor
20
25
  raise Error unless processor < ::Metanorma::Processor
21
26
  p = processor.new
22
- Array(p.short).each do |s|
23
- @processors[s] = p
27
+ # p.short[-1] is the canonical name
28
+ short = Array(p.short)
29
+ @processors[short[-1]] = p
30
+ short.each do |s|
31
+ @aliases[s] = short[-1]
24
32
  end
33
+ Array(p.short)
25
34
  Util.log("[metanorma] processor \"#{Array(p.short)[0]}\" registered", :info)
26
35
  end
27
36
 
@@ -1,3 +1,3 @@
1
1
  module Metanorma
2
- VERSION = "1.1.1"
2
+ VERSION = "1.1.6"
3
3
  end
@@ -27,11 +27,15 @@ 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 "isodoc", "~> 1.2.1"
37
41
  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.1
4
+ version: 1.1.6
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-07 00:00:00.000000000 Z
11
+ date: 2020-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -142,14 +142,14 @@ 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
153
  description: Library to process any Metanorma standard.
154
154
  email:
155
155
  - open.source@ribose.com
@@ -182,6 +182,9 @@ files:
182
182
  - lib/metanorma.rb
183
183
  - lib/metanorma/asciidoctor_extensions.rb
184
184
  - lib/metanorma/asciidoctor_extensions/glob_include_processor.rb
185
+ - lib/metanorma/collection.rb
186
+ - lib/metanorma/collection_manifest.rb
187
+ - lib/metanorma/collection_renderer.rb
185
188
  - lib/metanorma/compile.rb
186
189
  - lib/metanorma/config.rb
187
190
  - lib/metanorma/document.rb