metanorma 1.1.2 → 1.1.7

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