metanorma 1.0.6 → 1.1.4

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