metanorma 1.1.1 → 1.1.6

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: 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