metanorma 1.1.0 → 1.1.5

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