metanorma 1.0.6 → 1.1.4

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: 0b51503237da3dfd54719591c47f599bca877d414528c4cf9a69abdb19aec9e9
4
- data.tar.gz: 1588206551663889040f94194789cee7f2fb9b095775591a08f7b613ce59fbfe
3
+ metadata.gz: 72a6cdf0d2785bbc70cc907a024ab4e03ce4bdf5cd744c73ccc24ee0e928e092
4
+ data.tar.gz: bae5a06c2b0b71ecc46ed52699607eb235aa05d327f474328cce113510d4616e
5
5
  SHA512:
6
- metadata.gz: 6f7dd77c9a6bf129594e3bfdabc1626bcb4c58a9b68454b09e1d216f7c9e43d4ebce62ce980ed762e0ad3e9f5c3c3456b9473fe7b1670e13bdc7c2d286edc1ec
7
- data.tar.gz: 9c4125021226bdd561436e37e2fbacd4bd3c3bfa48845607282dd7ce3b4789e218e2aaa835364de7268c684bbf667e5267a6224db5a4c268fce180136b835391
6
+ metadata.gz: 78f0012cc133961e454a58e28a67c47a527a766ef85d8e995b7495f5095ec149bc7b89849ca565c8b668487e43ef90d1cc2d0c00658deee546d15295280352cd
7
+ data.tar.gz: 48a352c90ab1d5b4c2cd8e0e8581e58cd8425399d34cde1ba9ceafb333f36d2a6c048f59f739057840ce5bb6c7a0db2c5973dc26113c4d8102daf7300b806605
@@ -29,18 +29,10 @@ jobs:
29
29
  uses: actions/setup-ruby@v1
30
30
  with:
31
31
  ruby-version: ${{ matrix.ruby }}
32
- architecture: 'x64'
33
32
  - name: Update gems
34
33
  run: |
35
34
  sudo gem install bundler --force
36
35
  bundle install --jobs 4 --retry 3
37
- - name: Use Node
38
- uses: actions/setup-node@v1
39
- with:
40
- node-version: '12'
41
- - name: Install Puppeteer
42
- run: |
43
- npm install -g puppeteer@3.0.1
44
36
  - name: Run specs
45
37
  run: |
46
38
  bundle exec rake
@@ -5,6 +5,8 @@ name: ubuntu
5
5
  on:
6
6
  push:
7
7
  branches: [ master ]
8
+ tags:
9
+ - '*'
8
10
  pull_request:
9
11
  paths-ignore:
10
12
  - .github/workflows/macos.yml
@@ -29,32 +31,26 @@ jobs:
29
31
  uses: actions/setup-ruby@v1
30
32
  with:
31
33
  ruby-version: ${{ matrix.ruby }}
32
- architecture: 'x64'
33
34
  - name: Update gems
34
35
  run: |
35
36
  gem install bundler
36
37
  bundle install --jobs 4 --retry 3
37
- - name: Use Node
38
- uses: actions/setup-node@v1
39
- with:
40
- node-version: '12'
41
- - name: Install Puppeteer
42
- run: |
43
- sudo apt-get update
44
- sudo apt-get install libgbm1
45
- npm install -g puppeteer@3.0.1
46
38
  - name: Run specs
47
39
  run: |
48
40
  bundle exec rake
49
- - name: Trigger dependent repositories
50
- if: github.ref == 'refs/heads/master' && matrix.ruby == '2.6'
41
+ - name: Trigger repositories
42
+ if: matrix.ruby == '2.6'
51
43
  env:
52
- GH_USERNAME: ${{ secrets.PAT_USERNAME }}
53
- GH_ACCESS_TOKEN: ${{ secrets.PAT_TOKEN }}
44
+ GH_USERNAME: metanorma-ci
45
+ GH_ACCESS_TOKEN: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
54
46
  run: |
55
47
  curl -LO --retry 3 https://raw.githubusercontent.com/metanorma/metanorma-build-scripts/master/trigger-gh-actions.sh
56
48
  [[ -f ".github/workflows/dependent_repos.env" ]] && source .github/workflows/dependent_repos.env
57
- for repo in $DEPENDENT_REPOS
49
+ CLIENT_PAYLOAD=$(cat <<EOF
50
+ "{ "ref": "${GITHUB_REF}", "repo": "${GITHUB_REPOSITORY}" }"
51
+ EOF
52
+ )
53
+ for repo in $REPOS
58
54
  do
59
- sh trigger-gh-actions.sh $ORGANISATION $repo $GH_USERNAME $GH_ACCESS_TOKEN $GITHUB_REPOSITORY
55
+ sh trigger-gh-actions.sh $ORGANISATION $repo $GH_USERNAME $GH_ACCESS_TOKEN $GITHUB_REPOSITORY "$CLIENT_PAYLOAD"
60
56
  done
@@ -29,20 +29,12 @@ jobs:
29
29
  uses: actions/setup-ruby@v1
30
30
  with:
31
31
  ruby-version: ${{ matrix.ruby }}
32
- architecture: 'x64'
33
32
  - name: Update gems
34
33
  shell: pwsh
35
34
  run: |
36
35
  gem install bundler
37
36
  bundle config --local path vendor/bundle
38
37
  bundle install --jobs 4 --retry 3
39
- - name: Use Node
40
- uses: actions/setup-node@v1
41
- with:
42
- node-version: '12'
43
- - name: Install Puppeteer
44
- run: |
45
- npm install -g puppeteer@3.0.1
46
38
  - name: Run specs
47
39
  run: |
48
40
  bundle exec rake
data/.gitignore CHANGED
@@ -16,3 +16,7 @@
16
16
 
17
17
  # rspec failure tracking
18
18
  .rspec_status
19
+ .rubocop-https---raw-githubusercontent-com-riboseinc-oss-guides-master-ci-rubocop-yml
20
+ Gemfile.lock
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_reader :directives
16
+
17
+ # @return [Hash<String, Metanorma::Document>]
18
+ attr_reader :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,312 @@
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.coverpage if options[:format]&.include?(:html)
53
+ end
54
+
55
+ # Dummy class
56
+ class Dummy
57
+ def attr(_xyz); end
58
+ end
59
+
60
+ # The isodoc class for the metanorma flavour we are using
61
+ def isodoc # rubocop:disable Metrics/MethodLength
62
+ x = Asciidoctor.load nil, backend: @doctype.to_sym
63
+ isodoc = x.converter.html_converter(Dummy.new)
64
+ isodoc.i18n_init(@lang, @script) # read in internationalisation
65
+ # create the @meta class of isodoc, with "navigation" set to the index bar
66
+ # extracted from the manifest
67
+ nav = indexfile(@xml.at(ns("//manifest")))
68
+ i18n = isodoc.i18n
69
+ i18n.set(:navigation, nav)
70
+ isodoc.metadata_init(@lang, @script, i18n)
71
+ # populate the @meta class of isodoc with the various metadata fields
72
+ # native to the flavour; used to populate Liquid
73
+ isodoc.info(@xml, nil)
74
+ isodoc
75
+ end
76
+
77
+ # infer the flavour from the first document identifier; relaton does that
78
+ def doctype
79
+ if (docid = @xml&.at(ns("//bibdata/docidentifier/@type"))&.text)
80
+ dt = docid.downcase
81
+ elsif (docid = @xml&.at(ns("//bibdata/docidentifier"))&.text)
82
+ dt = docid.sub(/\s.*$/, "").lowercase
83
+ else return "standoc"
84
+ end
85
+ @registry = Metanorma::Registry.instance
86
+ @registry.alias(dt.to_sym)&.to_s || dt
87
+ end
88
+
89
+ def ns(xpath)
90
+ IsoDoc::Convert.new({}).ns(xpath)
91
+ end
92
+
93
+ # hash for each document in collection of document identifier to:
94
+ # document reference (fileref or id), type of document reference,
95
+ # and bibdata entry for that file
96
+ # @param path [String] path to collection
97
+ # @return [Hash{String=>Hash}]
98
+ def read_files(path) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
99
+ files = {}
100
+ @xml.xpath(ns("//docref")).each do |d|
101
+ identifier = d.at(ns("./identifier")).text
102
+ files[identifier] = if d["fileref"]
103
+ { type: "fileref",
104
+ ref: File.join(path, d["fileref"]) }
105
+ else { type: "id", ref: d["id"] }
106
+ end
107
+ file, _filename = targetfile(files[identifier], true)
108
+ xml = Nokogiri::XML(file)
109
+ files[identifier][:anchors] = read_anchors(xml)
110
+ files[identifier][:bibdata] = xml.at(ns("//bibdata"))
111
+ end
112
+ files
113
+ end
114
+
115
+ # map locality type and label (e.g. "clause" "1") to id = anchor for
116
+ # a document
117
+ def read_anchors(xml)
118
+ ret = {}
119
+ xrefs = @isodoc.xref_init(@lang, @script, @isodoc, @isodoc.i18n, {})
120
+ xrefs.parse xml
121
+ xrefs.get.each do |k, v|
122
+ v[:label] && v[:type] || next
123
+ ret[v[:type]] ||= {}
124
+ ret[v[:type]][v[:label]] = k
125
+ end
126
+ ret
127
+ end
128
+
129
+ # populate liquid template of ARGV[1] with metadata extracted from
130
+ # collection manifest
131
+ def coverpage
132
+ File.open(File.join(@outdir, "index.html"), "w:UTF-8") do |f|
133
+ f.write @isodoc.populate_template(File.read(@coverpage))
134
+ end
135
+ end
136
+
137
+ # @param elm [Nokogiri::XML::Element]
138
+ # @return [String]
139
+ def indexfile_title(elm)
140
+ lvl = elm&.at(ns("./level"))&.text&.capitalize
141
+ lbl = elm&.at(ns("./title"))&.text
142
+ "#{lvl}#{lvl && lbl ? ': ' : ''}#{lbl}"
143
+ end
144
+
145
+ # uses the identifier to label documents; other attributes (title) can be
146
+ # looked up in @files[id][:bibdata]
147
+ #
148
+ # @param elm [Nokogiri::XML::Element]
149
+ # @param builder [Nokogiri::XML::Builder]
150
+ def indexfile_docref(elm, builder)
151
+ return "" unless elm.at(ns("./docref"))
152
+
153
+ builder.ul { |b| docrefs(elm, b) }
154
+ end
155
+
156
+ # @param elm [Nokogiri::XML::Element]
157
+ # @param builder [Nokogiri::XML::Builder]
158
+ def docrefs(elm, builder)
159
+ elm.xpath(ns("./docref")).each do |d|
160
+ identifier = d.at(ns("./identifier")).text
161
+ link = if d["fileref"] then d["fileref"].sub(/\.xml$/, ".html")
162
+ else d["id"] + ".html"
163
+ end
164
+ builder.li { builder.a identifier, href: link }
165
+ end
166
+ end
167
+
168
+ # single level navigation list, with hierarchical nesting
169
+ # if multiple lists are needed as separate HTML fragments, multiple
170
+ # instances of this function will be needed,
171
+ # and associated to different variables in the call to @isodoc.metadata_init
172
+ # (including possibly an array of HTML fragments)
173
+ #
174
+ # @param elm [Nokogiri::XML::Element]
175
+ # @return [String] XML
176
+ def indexfile(elm)
177
+ Nokogiri::HTML::Builder.new do |b|
178
+ b.ul do
179
+ b.li indexfile_title(elm)
180
+ indexfile_docref(elm, b)
181
+ elm.xpath(ns("./manifest")).each do |d|
182
+ b << indexfile(d)
183
+ end
184
+ end
185
+ end.doc.root.to_html
186
+ end
187
+
188
+ # return file contents + output filename for each file in the collection,
189
+ # given a docref entry
190
+ # @param data [Hash]
191
+ # @param read [Boolean]
192
+ # @return [Array<String, nil>]
193
+ def targetfile(data, read = false)
194
+ if data[:type] == "fileref" then ref_file data[:ref], read
195
+ else xml_file data[:id], read
196
+ end
197
+ end
198
+
199
+ # @param ref [String]
200
+ # @param read [Boolean]
201
+ # @return [Array<String, nil>]
202
+ def ref_file(ref, read)
203
+ file = File.read(ref, encoding: "utf-8") if read
204
+ filename = ref.sub(/\.xml$/, ".html")
205
+ [file, filename]
206
+ end
207
+
208
+ # @param id [String]
209
+ # @param read [Boolean]
210
+ # @return [Array<String, nil>]
211
+ def xml_file(id, read)
212
+ file = @xml.at(ns("//doc-container[@id = '#{id}']")).to_xml if read
213
+ filename = id + ".html"
214
+ [file, filename]
215
+ end
216
+
217
+ # @param bib [Nokogiri::XML::Element]
218
+ # @param identifier [String]
219
+ def update_bibitem(bib, identifier) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
220
+ docid = bib&.at(ns("./docidentifier"))&.text
221
+ unless @files[docid]
222
+ warn "Cannot find crossreference to document #{docid} in document "\
223
+ "#{identifier}!"
224
+ abort
225
+ end
226
+ id = bib["id"]
227
+ newbib = bib.replace(@files[docid][:bibdata])
228
+ newbib.name = "bibitem"
229
+ newbib["id"] = id
230
+ newbib&.at(ns("./ext"))&.remove
231
+ _file, url = targetfile(@files[docid], false)
232
+ uri_node = Nokogiri::XML::Node.new "uri", newbib
233
+ uri_node[:type] = "citation"
234
+ uri_node.content = url
235
+ newbib.at(ns("./docidentifier")).previous = uri_node
236
+ end
237
+
238
+ # TODO: update crossreferences to other files in the selection
239
+ # repo(current-metanorma-collection/ISO 17301-1:2016)
240
+ # replaced by
241
+ # bibdata of "ISO 17301-1:2016" in situ as bibitem
242
+ # Any erefs to that bibitem id are replaced with relative URL
243
+ # Preferably with anchor, and is a job to realise dynamic lookup of
244
+ # localities
245
+ # @param file [String] XML content
246
+ # @param identifier [String] docid
247
+ # @return [String] XML content
248
+ def update_xrefs(file, identifier)
249
+ docxml = Nokogiri::XML(file)
250
+ docxml.xpath(ns("//bibitem[not(ancestor::bibitem)]")).each do |b|
251
+ docid = b&.at(ns("./docidentifier[@type = 'repository']"))&.text
252
+ next unless docid && %r{^current-metanorma-collection/}.match(docid)
253
+
254
+ update_bibitem(b, identifier)
255
+ update_anchors(b, docxml, docid)
256
+ end
257
+ docxml.to_xml
258
+ end
259
+
260
+ # if there is a crossref to another document, with no anchor, retrieve the
261
+ # anchor given the locality, and insert it into the crossref
262
+ def update_anchors(bib, docxml, _id) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
263
+ docid = bib&.at(ns("./docidentifier"))&.text
264
+ docxml.xpath("//xmlns:eref[@citeas = '#{docid}']").each do |e|
265
+ e.at(ns(".//locality[@type = 'anchor']")).nil? || next
266
+ ins = e.at(ns("./localityStack")) || next
267
+ type = ins&.at(ns("./locality/@type"))&.text
268
+ ref = ins&.at(ns("./locality/referenceFrom"))&.text
269
+ (anchor = @files[docid][:anchors][type][ref]) || next
270
+ ref_from = Nokogiri::XML::Node.new "referenceFrom", bib
271
+ ref_from.content = anchor.sub(/^_/, "")
272
+ locality = Nokogiri::XML::Node.new "locality", bib
273
+ locality[:type] = "anchor"
274
+ locality.add_child ref_from
275
+ ins << locality
276
+ end
277
+ end
278
+
279
+ # process each file in the collection
280
+ # files are held in memory, and altered as postprocessing
281
+ def files # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
282
+ @files.each do |identifier, x|
283
+ file, filename = targetfile(x, true)
284
+ file = update_xrefs(file, identifier)
285
+ Tempfile.open(["collection", ".xml"], encoding: "utf-8") do |f|
286
+ f.write(file)
287
+ f.close
288
+ # warn "metanorma compile -x html #{f.path}"
289
+ c = Compile.new
290
+ c.compile f.path, format: :asciidoc, extension_keys: @format
291
+ @format.each do |ext|
292
+ fn = File.basename(filename).sub(/(?<=\.)[^\.]+$/, ext.to_s)
293
+ FileUtils.mv f.path.sub(/\.xml$/, ".#{ext}"), File.join(@outdir, fn)
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ private
300
+
301
+ # @param options [Hash]
302
+ # @raise [ArgumentError]
303
+ def check_options(options)
304
+ unless options[:format].is_a?(Array) && (FORMATS & options[:format]).any?
305
+ raise ArgumentError, "Need to specify formats (xml,html,pdf,doc)"
306
+ end
307
+ return if !options[:format].include?(:html) || options[:coverpage]
308
+
309
+ raise ArgumentError, "Need to specify a coverpage to render HTML"
310
+ end
311
+ end
312
+ end
@@ -4,14 +4,18 @@ require "htmlentities"
4
4
 
5
5
  module Metanorma
6
6
  class Compile
7
+ # @return [Array<String>]
8
+ attr_reader :errors
9
+
7
10
  def initialize
8
11
  @registry = Metanorma::Registry.instance
12
+ @errors = []
9
13
  end
10
14
 
11
15
  def compile(filename, options = {})
12
16
  require_libraries(options)
13
17
  options = options_extract(filename, options)
14
- validate(options) or return nil
18
+ validate_type(options) && validate_format(options) || (return nil)
15
19
  @processor = @registry.find_processor(options[:type].to_sym)
16
20
  extensions = get_extensions(options) or return nil
17
21
  (file, isodoc = process_input(filename, options)) or return nil
@@ -43,6 +47,7 @@ module Metanorma
43
47
  o = Metanorma::Input::Asciidoc.new.extract_metanorma_options(content)
44
48
  o = o.merge(xml_options_extract(content))
45
49
  options[:type] ||= o[:type]&.to_sym
50
+ t = @registry.alias(options[:type]) and options[:type] = t
46
51
  dir = filename.sub(%r(/[^/]+$), "/")
47
52
  options[:relaton] ||= "#{dir}/#{o[:relaton]}" if o[:relaton]
48
53
  options[:sourcecode] ||= "#{dir}/#{o[:sourcecode]}" if o[:sourcecode]
@@ -53,10 +58,6 @@ module Metanorma
53
58
  options
54
59
  end
55
60
 
56
- def validate(options)
57
- validate_type(options) && validate_format(options)
58
- end
59
-
60
61
  def validate_type(options)
61
62
  unless options[:type]
62
63
  Util.log("[metanorma] Error: Please specify a standard type: #{@registry.supported_backends}.", :error)
@@ -89,14 +90,22 @@ module Metanorma
89
90
  end
90
91
 
91
92
  def get_extensions(options)
92
- options[:extension_keys] ||= @processor.output_formats.inject([]) do |memo, (k, _)|
93
- memo << k; memo
93
+ options[:extension_keys] ||= @processor.output_formats.reduce([]) do |memo, (k, _)|
94
+ memo << k
94
95
  end
95
- extensions = options[:extension_keys].inject([]) do |memo, e|
96
- @processor.output_formats[e] and memo << e or
97
- Util.log("[metanorma] Error: #{e} format is not supported for this standard.", :error)
98
- memo
96
+ extensions = options[:extension_keys].reduce([]) do |memo, e|
97
+ if @processor.output_formats[e]
98
+ memo << e
99
+ else
100
+ message = "[metanorma] Error: #{e} format is not supported for this standard."
101
+ @errors << message
102
+ Util.log(message, :error)
103
+ memo
104
+ end
99
105
  end
106
+ if !extensions.include?(:presentation) and extensions.any? { |e| @processor.use_presentation_xml(e) }
107
+ extensions << :presentation
108
+ end
100
109
  extensions
101
110
  end
102
111
 
@@ -110,7 +119,7 @@ module Metanorma
110
119
  dir = File.dirname(filename)
111
120
  dir != '.' and
112
121
  file.gsub!(/^include::/, "include::#{dir}/")
113
- [file, @processor.input_to_isodoc(file, filename)]
122
+ [file, @processor.input_to_isodoc(file, filename, options)]
114
123
  when ".xml"
115
124
  Util.log("[metanorma] Processing: Metanorma XML input.", :info)
116
125
  # TODO NN: this is a hack -- we should provide/bridge the
@@ -195,28 +204,62 @@ module Metanorma
195
204
  end
196
205
  end
197
206
 
207
+ # dependency ordering
208
+ def sort_extensions_execution(ext)
209
+ case ext
210
+ when :xml then 0
211
+ when :rxl then 1
212
+ when :presentation then 2
213
+ else
214
+ 99
215
+ end
216
+ end
217
+
218
+ def wrap_html(options, file_extension, outfilename)
219
+ if options[:wrapper] and /html$/.match file_extension
220
+ outfilename = outfilename.sub(/\.html$/, "")
221
+ FileUtils.mkdir_p outfilename
222
+ FileUtils.mv "#{outfilename}.html", outfilename
223
+ FileUtils.mv "#{outfilename}_images", outfilename, force: true
224
+ end
225
+ end
226
+
227
+ # isodoc is Raw Metanorma XML
198
228
  def process_extensions(extensions, file, isodoc, options)
199
- extensions.each do |ext|
229
+ f = change_output_dir options
230
+ xml_name = f.sub(/\.[^.]+$/, ".xml")
231
+ presentationxml_name = f.sub(/\.[^.]+$/, ".presentation.xml")
232
+ extensions.sort do |a, b|
233
+ sort_extensions_execution(a) <=> sort_extensions_execution(b)
234
+ end.each do |ext|
200
235
  isodoc_options = @processor.extract_options(file)
201
236
  isodoc_options[:datauriimage] = true if options[:datauriimage]
202
237
  file_extension = @processor.output_formats[ext]
203
- outfilename = options[:filename].sub(/\.[^.]+$/, ".#{file_extension}")
238
+ outfilename = f.sub(/\.[^.]+$/, ".#{file_extension}")
204
239
  if ext == :rxl
205
240
  options[:relaton] = outfilename
206
241
  relaton_export(isodoc, options)
207
242
  else
208
243
  begin
209
- @processor.output(isodoc, outfilename, ext, isodoc_options)
210
- rescue StandardError => e
244
+ @processor.use_presentation_xml(ext) ?
245
+ @processor.output(nil, presentationxml_name, outfilename, ext, isodoc_options) :
246
+ @processor.output(isodoc, xml_name, outfilename, ext, isodoc_options)
247
+ rescue StandardError => e
211
248
  puts e.message
212
249
  end
213
250
  end
214
- if options[:wrapper] and /html$/.match file_extension
215
- outfilename = outfilename.sub(/\.html$/, "")
216
- FileUtils.mkdir_p outfilename
217
- FileUtils.mv "#{outfilename}.html", outfilename
218
- FileUtils.mv "#{outfilename}_images", outfilename, force: true
219
- end
251
+ wrap_html(options, file_extension, outfilename)
252
+ end
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]
220
263
  end
221
264
  end
222
265
  end
@@ -1,12 +1,82 @@
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)
8
+ @bibitem = bibitem
9
+ @file = file
9
10
  end
10
11
 
12
+ class << self
13
+ # @param file [String] file path
14
+ # @return [Metanorma::Document]
15
+ def parse_file(file)
16
+ new bibitem(file), file
17
+ end
18
+
19
+ # #param xml [Nokogiri::XML::Document, Nokogiri::XML::Element]
20
+ # @return [Metanorma::Document]
21
+ def parse_xml(xml)
22
+ new from_xml(xml)
23
+ end
24
+
25
+ private
26
+
27
+ # #param xml [Nokogiri::XML::Document, Nokogiri::XML::Element]
28
+ # @return [RelatonBib::BibliographicItem,RelatonIso::IsoBibliographicItem]
29
+ def from_xml(xml)
30
+ Relaton::Cli.parse_xml xml.at("//xmlns:bibitem|//xmlns:bibdata")
31
+ end
32
+
33
+ # @param file [String]
34
+ # @return [Symbol] file type
35
+ def format(file)
36
+ case file
37
+ when /\.xml$/ then :xml
38
+ when /.ya?ml$/ then :yaml
39
+ end
40
+ end
41
+
42
+ # @param file [String]
43
+ # @return [RelatonBib::BibliographicItem,
44
+ # RelatonIso::IsoBibliographicItem]
45
+ def bibitem(file)
46
+ case format(file)
47
+ when :xml
48
+ from_xml Nokogiri::XML(File.read(file, encoding: "UTF-8"))
49
+ when :yaml
50
+ yaml = File.read(file, ecoding: "UTF-8")
51
+ Relaton::Cli::YAMLConvertor.convert_single_file(yaml)
52
+ end
53
+ end
54
+ end
55
+
56
+ # @param builder [Nokogiri::XML::Builder, nil]
57
+ # @return [Nokogiri::XML::Builder, String]
58
+ def to_xml(builder = nil)
59
+ if builder
60
+ render_xml builder
61
+ else
62
+ Nokogiri::XML::Builder.new do |b|
63
+ root = render_xml b
64
+ root["xmlns"] = "http://metanorma.org"
65
+ end.to_xml
66
+ end
67
+ end
68
+
69
+ # @return [String]
70
+ def type
71
+ @type ||= (@bibitem.docidentifier.first&.type ||
72
+ @bibitem.docidentifier.first&.id&.match(/^[^\s]+/)&.to_s)&.downcase ||
73
+ "standoc"
74
+ end
75
+
76
+ private
77
+
78
+ def render_xml(builder)
79
+ builder.send(type + "-standard") { |b| @bibitem.to_xml b, bibdata: true }
80
+ end
11
81
  end
12
82
  end
@@ -5,7 +5,7 @@ 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
10
  ::Asciidoctor.convert(
11
11
  file,
@@ -13,7 +13,10 @@ module Metanorma
13
13
  safe: :safe,
14
14
  backend: type,
15
15
  header_footer: true,
16
- attributes: ["nodoc", "stem", "xrefstyle=short", "docfile=#{filename}"]
16
+ attributes: [
17
+ "nodoc", "stem", "xrefstyle=short", "docfile=#{filename}",
18
+ "output_dir=#{options[:"output-dir"]}"
19
+ ],
17
20
  )
18
21
  end
19
22
 
@@ -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
@@ -15,15 +15,28 @@ module Metanorma
15
15
  def output_formats
16
16
  {
17
17
  xml: "xml",
18
+ presentation: "presentation.xml",
18
19
  rxl: "rxl"
19
20
  }
20
21
  end
21
22
 
22
- def input_to_isodoc(file, filename)
23
- 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
+ end
26
+
27
+ # def input_to_isodoc(file, filename)
28
+ # raise "This is an abstract class!"
29
+ # end
30
+
31
+ def use_presentation_xml(ext)
32
+ case ext
33
+ when :html, :doc, :pdf then true
34
+ else
35
+ false
36
+ end
24
37
  end
25
38
 
26
- def output(isodoc_node, outname, format, options={})
39
+ def output(isodoc_node, inname, outname, format, options={})
27
40
  File.open(outname, "w:UTF-8") { |f| f.write(isodoc_node) }
28
41
  end
29
42
 
@@ -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.0.6"
2
+ VERSION = "1.1.4"
3
3
  end
@@ -27,11 +27,13 @@ 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
+ spec.add_dependency "relaton-cli", "~> 1.3.0"
30
31
 
31
32
  spec.add_development_dependency "rake", "~> 12.0"
32
33
  spec.add_development_dependency "rspec", "~> 3.0"
33
34
  spec.add_development_dependency "byebug", "~> 10.0"
34
35
  spec.add_development_dependency "rspec-command", "~> 1.0"
35
36
  spec.add_development_dependency "equivalent-xml", "~> 0.6"
36
- spec.add_development_dependency "metanorma-iso", "~> 1.3"
37
+ spec.add_development_dependency "metanorma-iso", "~> 1.5"
38
+ spec.add_development_dependency "isodoc", "~> 1.2.1"
37
39
  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.0.6
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-19 00:00:00.000000000 Z
11
+ date: 2020-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: relaton-cli
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.3.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.3.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rake
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -142,14 +156,28 @@ dependencies:
142
156
  requirements:
143
157
  - - "~>"
144
158
  - !ruby/object:Gem::Version
145
- version: '1.3'
159
+ version: '1.5'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.5'
167
+ - !ruby/object:Gem::Dependency
168
+ name: isodoc
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 1.2.1
146
174
  type: :development
147
175
  prerelease: false
148
176
  version_requirements: !ruby/object:Gem::Requirement
149
177
  requirements:
150
178
  - - "~>"
151
179
  - !ruby/object:Gem::Version
152
- version: '1.3'
180
+ version: 1.2.1
153
181
  description: Library to process any Metanorma standard.
154
182
  email:
155
183
  - open.source@ribose.com
@@ -182,6 +210,9 @@ files:
182
210
  - lib/metanorma.rb
183
211
  - lib/metanorma/asciidoctor_extensions.rb
184
212
  - lib/metanorma/asciidoctor_extensions/glob_include_processor.rb
213
+ - lib/metanorma/collection.rb
214
+ - lib/metanorma/collection_manifest.rb
215
+ - lib/metanorma/collection_renderer.rb
185
216
  - lib/metanorma/compile.rb
186
217
  - lib/metanorma/config.rb
187
218
  - lib/metanorma/document.rb
@@ -202,7 +233,7 @@ homepage: https://github.com/metanorma/metanorma
202
233
  licenses:
203
234
  - BSD-2-Clause
204
235
  metadata: {}
205
- post_install_message:
236
+ post_install_message:
206
237
  rdoc_options: []
207
238
  require_paths:
208
239
  - lib
@@ -217,9 +248,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
248
  - !ruby/object:Gem::Version
218
249
  version: '0'
219
250
  requirements: []
220
- rubyforge_project:
221
- rubygems_version: 2.7.6
222
- signing_key:
251
+ rubygems_version: 3.0.3
252
+ signing_key:
223
253
  specification_version: 4
224
254
  summary: Metanorma is the standard of standards; the metanorma gem allows you to create
225
255
  any standard document type supported by Metanorma.