metanorma 1.1.0 → 1.1.5

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: 315077d28cfe3e052808e9890d2f3c3f571b7350788e6d6423be1746e5559ef4
4
- data.tar.gz: f8a9c7d3055af8a687fe8df52b725b1fadbceb8920bef8ea449af5cde0aafb6a
3
+ metadata.gz: 42020b6888a6b5d4d11678cc5d3d217e93ced978e477d05fee31a6593722f78b
4
+ data.tar.gz: 4379ba9bcf18655c119598b44ead30f223d843edb33ce53205d4ad6c16826904
5
5
  SHA512:
6
- metadata.gz: ab023b366ca20c7d4fe3785e00698d2ccd868c0d119b0df74a5ba52e11f4bd69b884b18fea2b820838708c3bae5516be911c9e82de8302b6ade8cf52c99c36b4
7
- data.tar.gz: 3f3520b67f58296be755b0e8b2b9cfb0e98611e214f73e131e1eccde7b74dfd611db1f476cee84d5071f6ef0c1b7b801ed9874e7261697b52fb7bf288282c57d
6
+ metadata.gz: 67525ff08b409c36a90cc8725dc150a518b2c3888f0b3518cc83113b51dcc739ba89120681fe0c2e36c4c6b8771178afe1abe25c64887aee93b29fb055c82a4e
7
+ data.tar.gz: c0f35a9f5f0e3b0397fb0f81eaf84b90be85f09af4cb99a25e9ac146e0b9658c0b0c26883015c0a8979d656f12d57f5dc0cb24f1af92fa1cb9c58ba7f5e13a90
@@ -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
@@ -19,3 +19,4 @@
19
19
  .rubocop-https---raw-githubusercontent-com-riboseinc-oss-guides-master-ci-rubocop-yml
20
20
  Gemfile.lock
21
21
  relaton/
22
+ .vscode/
@@ -4,7 +4,3 @@
4
4
 
5
5
  inherit_from:
6
6
  - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
7
- AllCops:
8
- TargetRubyVersion: 2.3
9
- Rails:
10
- Enabled: true
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "metanorma/version"
2
4
  require "asciidoctor"
3
5
  require "metanorma/util"
@@ -8,7 +10,10 @@ require "metanorma/registry"
8
10
  require "metanorma/processor"
9
11
  require "metanorma/asciidoctor_extensions"
10
12
  require "metanorma/compile"
13
+ require "metanorma/collection"
14
+ require "metanorma/collection_renderer"
15
+ require "metanorma/document"
11
16
 
17
+ # Metanorma module
12
18
  module Metanorma
13
-
14
19
  end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "relaton"
4
+ require "relaton/cli"
5
+ require "metanorma/collection_manifest"
6
+
7
+ module Metanorma
8
+ # Metanorma collection of documents
9
+ class Collection
10
+ # @return [String]
11
+ attr_reader :file
12
+
13
+ # @return [Array<String>] documents-inline to inject the XML into
14
+ # the collection manifest; documents-external to keeps them outside
15
+ attr_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
@@ -15,7 +15,7 @@ module Metanorma
15
15
  def compile(filename, options = {})
16
16
  require_libraries(options)
17
17
  options = options_extract(filename, options)
18
- validate(options) or return nil
18
+ validate_type(options) && validate_format(options) || (return nil)
19
19
  @processor = @registry.find_processor(options[:type].to_sym)
20
20
  extensions = get_extensions(options) or return nil
21
21
  (file, isodoc = process_input(filename, options)) or return nil
@@ -47,6 +47,7 @@ module Metanorma
47
47
  o = Metanorma::Input::Asciidoc.new.extract_metanorma_options(content)
48
48
  o = o.merge(xml_options_extract(content))
49
49
  options[:type] ||= o[:type]&.to_sym
50
+ t = @registry.alias(options[:type]) and options[:type] = t
50
51
  dir = filename.sub(%r(/[^/]+$), "/")
51
52
  options[:relaton] ||= "#{dir}/#{o[:relaton]}" if o[:relaton]
52
53
  options[:sourcecode] ||= "#{dir}/#{o[:sourcecode]}" if o[:sourcecode]
@@ -57,10 +58,6 @@ module Metanorma
57
58
  options
58
59
  end
59
60
 
60
- def validate(options)
61
- validate_type(options) && validate_format(options)
62
- end
63
-
64
61
  def validate_type(options)
65
62
  unless options[:type]
66
63
  Util.log("[metanorma] Error: Please specify a standard type: #{@registry.supported_backends}.", :error)
@@ -122,7 +119,7 @@ module Metanorma
122
119
  dir = File.dirname(filename)
123
120
  dir != '.' and
124
121
  file.gsub!(/^include::/, "include::#{dir}/")
125
- [file, @processor.input_to_isodoc(file, filename)]
122
+ [file, @processor.input_to_isodoc(file, filename, options)]
126
123
  when ".xml"
127
124
  Util.log("[metanorma] Processing: Metanorma XML input.", :info)
128
125
  # TODO NN: this is a hack -- we should provide/bridge the
@@ -229,30 +226,41 @@ module Metanorma
229
226
 
230
227
  # isodoc is Raw Metanorma XML
231
228
  def process_extensions(extensions, file, isodoc, options)
232
- xml_name = options[:filename].sub(/\.[^.]+$/, ".xml")
233
- presentationxml_name = options[:filename].sub(/\.[^.]+$/, ".presentation.xml")
234
- isodoc_options = @processor.extract_options(file)
235
- isodoc_options[:datauriimage] = true if options[:datauriimage]
229
+ f = change_output_dir options
230
+ xml_name = f.sub(/\.[^.]+$/, ".xml")
231
+ presentationxml_name = f.sub(/\.[^.]+$/, ".presentation.xml")
236
232
  extensions.sort do |a, b|
237
233
  sort_extensions_execution(a) <=> sort_extensions_execution(b)
238
234
  end.each do |ext|
235
+ isodoc_options = @processor.extract_options(file)
236
+ isodoc_options[:datauriimage] = true if options[:datauriimage]
239
237
  file_extension = @processor.output_formats[ext]
240
- outfilename = options[:filename].sub(/\.[^.]+$/, ".#{file_extension}")
238
+ outfilename = f.sub(/\.[^.]+$/, ".#{file_extension}")
241
239
  if ext == :rxl
242
240
  options[:relaton] = outfilename
243
241
  relaton_export(isodoc, options)
244
242
  else
245
243
  begin
246
- #require "byebug"; byebug
247
244
  @processor.use_presentation_xml(ext) ?
248
245
  @processor.output(nil, presentationxml_name, outfilename, ext, isodoc_options) :
249
246
  @processor.output(isodoc, xml_name, outfilename, ext, isodoc_options)
250
- rescue StandardError => e
247
+ rescue StandardError => e
251
248
  puts e.message
252
249
  end
253
250
  end
254
251
  wrap_html(options, file_extension, outfilename)
255
252
  end
256
253
  end
254
+
255
+ private
256
+
257
+ # @param options [Hash]
258
+ # @return [String]
259
+ def change_output_dir(options)
260
+ if options[:"output-dir"]
261
+ File.join options[:"output-dir"], File.basename(options[:filename])
262
+ else options[:filename]
263
+ end
264
+ end
257
265
  end
258
266
  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,16 +5,38 @@ module Metanorma
5
5
 
6
6
  class Asciidoc < Base
7
7
 
8
- def process(file, filename, type)
8
+ def process(file, filename, type, options = {})
9
9
  require "asciidoctor"
10
- ::Asciidoctor.convert(
11
- file,
10
+ out_opts = {
12
11
  to_file: false,
13
12
  safe: :safe,
14
13
  backend: type,
15
14
  header_footer: true,
16
- attributes: ["nodoc", "stem", "xrefstyle=short", "docfile=#{filename}"]
17
- )
15
+ attributes: [
16
+ "nodoc", "stem", "xrefstyle=short", "docfile=#{filename}",
17
+ "output_dir=#{options[:"output-dir"]}"
18
+ ]
19
+ }
20
+ unless asciidoctor_validate(file, filename, out_opts)
21
+ warn "Cannot continue compiling Asciidoctor document"
22
+ abort
23
+ end
24
+ ::Asciidoctor.convert(file, out_opts)
25
+ end
26
+
27
+ def asciidoctor_validate(file, filename, options)
28
+ err = nil
29
+ begin
30
+ previous_stderr, $stderr = $stderr, StringIO.new
31
+ ::Asciidoctor.load(file, options)
32
+ %r{(\n|^)asciidoctor: ERROR: ['"]?#{Regexp.escape(filename ||
33
+ "<empty>")}['"]?: line \d+: include file not found: }.match($stderr.string) and
34
+ err = $stderr.string
35
+ ensure
36
+ $stderr = previous_stderr
37
+ end
38
+ warn err unless err.nil?
39
+ err.nil?
18
40
  end
19
41
 
20
42
  def extract_metanorma_options(file)
@@ -7,7 +7,13 @@ module Metanorma
7
7
  def convert(url_path, output_path, xsl_stylesheet)
8
8
  return if url_path.nil? || output_path.nil? || xsl_stylesheet.nil?
9
9
 
10
- Mn2pdf.convert(url_path, output_path, xsl_stylesheet)
10
+ Mn2pdf.convert(quote(url_path), quote(output_path), quote(xsl_stylesheet))
11
+ end
12
+
13
+ def quote(x)
14
+ return x if /^'.*'$/.match(x)
15
+ return x if /^".*"$/.match(x)
16
+ %("#{x}")
11
17
  end
12
18
  end
13
19
  end
@@ -20,10 +20,14 @@ module Metanorma
20
20
  }
21
21
  end
22
22
 
23
- def input_to_isodoc(file, filename)
24
- raise "This is an abstract class!"
23
+ def input_to_isodoc(file, filename, options = {})
24
+ Metanorma::Input::Asciidoc.new.process(file, filename, @asciidoctor_backend, options)
25
25
  end
26
26
 
27
+ # def input_to_isodoc(file, filename)
28
+ # raise "This is an abstract class!"
29
+ # end
30
+
27
31
  def use_presentation_xml(ext)
28
32
  case ext
29
33
  when :html, :doc, :pdf then true
@@ -14,14 +14,23 @@ module Metanorma
14
14
 
15
15
  def initialize
16
16
  @processors = {}
17
+ @aliases = {csd: :cc, m3d: :m3aawg, mpfd: :mpfa, csand: :csa}
18
+ end
19
+
20
+ def alias(x)
21
+ @aliases[x]
17
22
  end
18
23
 
19
24
  def register processor
20
25
  raise Error unless processor < ::Metanorma::Processor
21
26
  p = processor.new
22
- Array(p.short).each do |s|
23
- @processors[s] = p
27
+ # p.short[-1] is the canonical name
28
+ short = Array(p.short)
29
+ @processors[short[-1]] = p
30
+ short.each do |s|
31
+ @aliases[s] = short[-1]
24
32
  end
33
+ Array(p.short)
25
34
  Util.log("[metanorma] processor \"#{Array(p.short)[0]}\" registered", :info)
26
35
  end
27
36
 
@@ -1,3 +1,3 @@
1
1
  module Metanorma
2
- VERSION = "1.1.0"
2
+ VERSION = "1.1.5"
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.4"
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.1.0
4
+ version: 1.1.5
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-06-26 00:00:00.000000000 Z
11
+ date: 2020-09-11 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.4'
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.4'
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