metanorma 2.1.4 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +23 -2
  3. data/Gemfile +14 -0
  4. data/README.adoc +1 -1
  5. data/lib/metanorma/collection/artifact_store.rb +142 -0
  6. data/lib/metanorma/collection/collection.rb +140 -131
  7. data/lib/metanorma/collection/config/bibdata.rb +1 -1
  8. data/lib/metanorma/collection/config/config.rb +50 -20
  9. data/lib/metanorma/collection/config/converters.rb +89 -31
  10. data/lib/metanorma/collection/config/doc_container.rb +28 -0
  11. data/lib/metanorma/collection/config/manifest.rb +30 -6
  12. data/lib/metanorma/collection/config/namespaces.rb +12 -0
  13. data/lib/metanorma/collection/document/document.rb +54 -15
  14. data/lib/metanorma/collection/filelookup/filelookup.rb +170 -74
  15. data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +62 -27
  16. data/lib/metanorma/collection/filelookup/utils.rb +52 -0
  17. data/lib/metanorma/collection/helpers.rb +82 -0
  18. data/lib/metanorma/collection/log.rb +24 -0
  19. data/lib/metanorma/collection/manifest/manifest.rb +27 -14
  20. data/lib/metanorma/collection/multilingual/multilingual.rb +1 -1
  21. data/lib/metanorma/collection/renderer/filelocation.rb +166 -0
  22. data/lib/metanorma/collection/renderer/fileparse.rb +60 -55
  23. data/lib/metanorma/collection/renderer/fileprocess.rb +184 -40
  24. data/lib/metanorma/collection/renderer/navigation.rb +20 -6
  25. data/lib/metanorma/collection/renderer/render_word.rb +8 -4
  26. data/lib/metanorma/collection/renderer/renderer.rb +202 -19
  27. data/lib/metanorma/collection/renderer/svg.rb +60 -5
  28. data/lib/metanorma/collection/renderer/utils.rb +76 -42
  29. data/lib/metanorma/collection/sectionsplit/collection.rb +14 -4
  30. data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +141 -61
  31. data/lib/metanorma/collection/util/disambig_files.rb +4 -5
  32. data/lib/metanorma/collection/util/util.rb +147 -23
  33. data/lib/metanorma/collection/xrefprocess/xrefprocess.rb +7 -7
  34. data/lib/metanorma/compile/assets/icc-boilerplate.adoc +41 -0
  35. data/lib/metanorma/compile/compile.rb +180 -165
  36. data/lib/metanorma/compile/compile_options.rb +155 -83
  37. data/lib/metanorma/compile/extract.rb +76 -56
  38. data/lib/metanorma/compile/flavor.rb +19 -0
  39. data/lib/metanorma/compile/output_filename.rb +75 -0
  40. data/lib/metanorma/compile/output_filename_config.rb +27 -0
  41. data/lib/metanorma/compile/relaton_drop.rb +56 -0
  42. data/lib/metanorma/compile/render.rb +196 -0
  43. data/lib/metanorma/compile/validator.rb +26 -0
  44. data/lib/metanorma/compile/writeable.rb +12 -0
  45. data/lib/metanorma/util/fontist_helper.rb +35 -19
  46. data/lib/metanorma/version.rb +1 -1
  47. data/lib/metanorma.rb +1 -7
  48. data/metanorma.gemspec +11 -20
  49. data/plans/collection-rendering-architecture-review.md +516 -0
  50. metadata +42 -122
  51. data/.hound.yml +0 -5
  52. data/lib/metanorma/asciidoctor_extensions/glob_include_processor.rb +0 -22
  53. data/lib/metanorma/asciidoctor_extensions.rb +0 -1
  54. data/lib/metanorma/compile/compile_validate.rb +0 -68
  55. data/lib/metanorma/config/config.rb +0 -23
  56. data/lib/metanorma/input/asciidoc.rb +0 -88
  57. data/lib/metanorma/input/base.rb +0 -9
  58. data/lib/metanorma/input.rb +0 -7
  59. data/lib/metanorma/processor/processor.rb +0 -54
  60. data/lib/metanorma/processor.rb +0 -6
  61. data/lib/metanorma/registry/registry.rb +0 -63
  62. data/lib/metanorma/util/util.rb +0 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59bdb97eff1bbdd231593855917d6ecc44e723dfc6bdec7bdd84a739a54d6162
4
- data.tar.gz: ed4c015ceb9003f8aa9d8151cae07d59ef8d645f1812bac237706994ad707402
3
+ metadata.gz: 4f36a841296240362316b293a3353c54b85b83485b81d21e762d639fe26ce100
4
+ data.tar.gz: 7302ecaa3bbf5893fa6a0c52199ce7080475efb00a9973fe8e21ace651a996b8
5
5
  SHA512:
6
- metadata.gz: 1bd7eb1ec3a9765a3135f35b5f9011463716546c6d86d1d487e99cbef2f97147ef29f685694e03945a7fac26c25c62ec580f5657b76ecda50d1054282dfb5896
7
- data.tar.gz: abb72caf7d58a9f24eafa7c5bf6fa1fd8ee2cfe2333d30d18d83fc4b1c13245dc7bf79bc60f6c7cd77654e0ec787ca1a44f0d2844eb08e75af5c4489b47096df
6
+ metadata.gz: b9d143219d4f9a9130321c675e3b1bc8f5966dcb09961f13527381d493e7d6a6e7efffce28bd807bf5a07b2ae040048a321f498a8d15ecab002a756d8c493f8a
7
+ data.tar.gz: fd1c20bd7b7d1062130781c1276b95b921a9408d4da2a17d622f5b21053b8a20ab168daa710bfbdefbc652901bea7d5e7c75e891e7a1fb91d2dd74075edbe39b
data/.rubocop.yml CHANGED
@@ -1,10 +1,31 @@
1
1
  # Auto-generated by Cimas: Do not edit it manually!
2
2
  # See https://github.com/metanorma/cimas
3
3
  inherit_from:
4
- - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
4
+ - https://raw.githubusercontent.com/riboseinc/oss-guides/main/ci/rubocop.yml
5
+ # .rubocop_todo.yml MUST be the last entry. inherit_from is last-wins:
6
+ # if listed before oss-guides, the shared config's stricter Metrics/
7
+ # (MethodLength, BlockLength, etc.) rules override the todo's per-file
8
+ # grandfathering, and the todo becomes inert on those cops. Empirically
9
+ # verified on suma 2026-07-06: with todo listed first, 34 Metrics/* offenses
10
+ # remained; moved last, cleared. Every gem in the metanorma-org fleet
11
+ # already ships a `.rubocop_todo.yml` on its live tree (audited 2026-07-06,
12
+ # 54/54 have it), so this reference is safe to emit unconditionally.
13
+ - .rubocop_todo.yml
14
+
15
+ # Rubocop plugins enabled centrally so every metanorma-org gem picks them up
16
+ # on cimas sync — best practice belongs at the shared-template layer, not
17
+ # per-repo. Per ronaldtse feedback on metanorma/ci#332.
18
+ plugins:
19
+ - rubocop-rspec
20
+ - rubocop-performance
21
+ - rubocop-rake
5
22
 
6
23
  # local repo-specific modifications
7
24
  # ...
8
25
 
9
26
  AllCops:
10
- TargetRubyVersion: 2.5
27
+ # 3.3 matches the org-wide minimum being pushed via #274 (Raise minimum
28
+ # Ruby version to 3.3 due to EOL of 3.2 on 2026-03-31). Was 3.4 before
29
+ # this commit — 3.4 was above the org's stated minimum and would have
30
+ # applied Rubocop rules that fail-close on gems still targeting 3.3.
31
+ TargetRubyVersion: 3.3
data/Gemfile CHANGED
@@ -11,3 +11,17 @@ begin
11
11
  rescue StandardError
12
12
  nil
13
13
  end
14
+
15
+ gem "debug"
16
+ gem "equivalent-xml"
17
+ #gem "metanorma-iho"
18
+ #gem "metanorma-iso", ">= 3.2.0"
19
+ gem "mnconvert"
20
+ gem "pry"
21
+ gem "rake", "~> 13.0"
22
+ gem "rspec", "~> 3.0"
23
+ gem "rspec-command", "~> 1.0"
24
+ gem "rubocop", "~> 1"
25
+ gem "rubocop-performance"
26
+ gem "sassc-embedded", "~> 1"
27
+ gem "simplecov", "~> 0.15"
data/README.adoc CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  image:https://img.shields.io/gem/v/metanorma.svg["Gem Version", link="https://rubygems.org/gems/metanorma"]
4
4
  image:https://github.com/metanorma/metanorma/workflows/rake/badge.svg["Build Status", link="https://github.com/metanorma/metanorma/actions?workflow=rake"]
5
- image:https://codeclimate.com/github/metanorma/metanorma/badges/gpa.svg["Code Climate", link="https://codeclimate.com/github/metanorma/metanorma"]
5
+ // image:https://codeclimate.com/github/metanorma/metanorma/badges/gpa.svg["Code Climate", link="https://codeclimate.com/github/metanorma/metanorma"]
6
6
  image:https://img.shields.io/github/issues-pr-raw/metanorma/metanorma.svg["Pull Requests", link="https://github.com/metanorma/metanorma/pulls"]
7
7
  image:https://img.shields.io/github/commits-since/metanorma/metanorma/latest.svg["Commits since latest",link="https://github.com/metanorma/metanorma/releases"]
8
8
 
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "digest"
5
+
6
+ module Metanorma
7
+ class Collection
8
+ # Durable, content-addressed store for staged collection-build artefacts.
9
+ #
10
+ # Enables incremental (batched) builds and resumption across runs: an
11
+ # artefact is named +<docid-slug>.<content-hash>.<stage>.<format>+, so a
12
+ # document already built with identical inputs is detected by filename alone
13
+ # and reused, and an interrupted run resumes by recomputing which artefacts
14
+ # are already present.
15
+ #
16
+ # Reuse is keyed on the caller-supplied content hash of the document's
17
+ # *input closure* (source + resolved includes + templates + referenced
18
+ # schemas + tool versions + options) -- never on file presence alone. See
19
+ # metanorma/iso-10303#455 for why presence-keyed reuse of compiled XML is
20
+ # unsound; the store deliberately requires a content hash it does not infer.
21
+ #
22
+ # Disabled by default: the collection renderer uses NullArtifactStore unless
23
+ # an incremental build is explicitly requested (opt-in, never default).
24
+ class ArtifactStore
25
+ DEFAULT_DIRNAME = ".metanorma-collection-cache"
26
+
27
+ # The artefact-stage vocabulary, and the one non-redundant property each
28
+ # stage carries: the format it is serialised as (also its file extension).
29
+ # THIS IS ITS HOME: every producer and consumer names its stage from this
30
+ # table by symbol, never a bare string literal elsewhere; an unknown stage
31
+ # raises (KeyError via +fetch+), so the vocabulary cannot drift outside
32
+ # this file. The stage's *name* is the filename token, so it is not stored
33
+ # here -- that would just restate the key. The stages trace the pipeline
34
+ # (isolated compile -> index -> reinflate); each role is documented inline:
35
+ STAGE_FORMATS = {
36
+ # Compiled Semantic XML, unresolved cross-document repo:() references
37
+ # PRESERVED as deterministic stubs (neither stripped nor resolved). The
38
+ # unit of an isolated per-document compile; a pure function of the
39
+ # document's input closure, hence content-addressable and reusable.
40
+ semantic: "xml",
41
+ # This document's contribution to the global anchor index: its anchors
42
+ # and ids (type => {label/UUID => anchor-id}). Aggregated across all
43
+ # documents to build the anchor -> owning-document index used at
44
+ # reinflation.
45
+ anchors: "json",
46
+ # Reinflated Presentation XML: the preserved stubs resolved against the
47
+ # global index into real cross-document links. Shared source for the PDF
48
+ # and Word collection outputs, and per-document for HTML.
49
+ presentation: "xml",
50
+ }.freeze
51
+
52
+ attr_reader :dir
53
+
54
+ def initialize(dir)
55
+ @dir = dir
56
+ FileUtils.mkdir_p(@dir)
57
+ end
58
+
59
+ # <docid-slug>.<content-hash>.<stage>.<format>
60
+ # +stage+ must be a key of STAGE_FORMATS; an unknown stage raises KeyError.
61
+ def path(docid, content_hash, stage)
62
+ format = STAGE_FORMATS.fetch(stage)
63
+ File.join(@dir, "#{slug(docid)}.#{content_hash}.#{stage}.#{format}")
64
+ end
65
+
66
+ # Is this exact (document, input-closure, stage) artefact already present?
67
+ # This is the resumption / skip predicate: a hit is byte-identical to a
68
+ # fresh build because the hash covers every input that determines output.
69
+ def key?(docid, content_hash, stage)
70
+ File.exist?(path(docid, content_hash, stage))
71
+ end
72
+
73
+ def read(docid, content_hash, stage)
74
+ f = path(docid, content_hash, stage)
75
+ File.exist?(f) ? File.read(f, encoding: "UTF-8") : nil
76
+ end
77
+
78
+ # Idempotent write: the same (docid, content_hash, stage) always maps to
79
+ # the same path and the content is a pure function of the hash, so a re-run
80
+ # overwrites with identical bytes -- no side effects on repeat.
81
+ def write(docid, content_hash, stage, content)
82
+ f = path(docid, content_hash, stage)
83
+ File.write(f, content, encoding: "UTF-8")
84
+ f
85
+ end
86
+
87
+ # Remove every stored version of this document whose content hash is not
88
+ # +keep_hash+ -- the superseded artefacts left behind when the document's
89
+ # content changed. Keeps exactly one (current) version per document per
90
+ # stage, bounding disk to current state without losing the cache the next
91
+ # build resumes from.
92
+ def prune_superseded(docid, keep_hash)
93
+ STAGE_FORMATS.each do |stage, ext|
94
+ keep = path(docid, keep_hash, stage)
95
+ Dir[File.join(@dir, "#{slug(docid)}.*.#{stage}.#{ext}")].each do |f|
96
+ f == keep or File.delete(f)
97
+ end
98
+ end
99
+ end
100
+
101
+ # Wipe the entire store. The escape hatch when inputs the content hash
102
+ # cannot see -- imported EXPRESS schemas, templates, included files -- have
103
+ # changed, so the cache can no longer be trusted; the next build recompiles
104
+ # everything. Tracking such shared-input changes is the collection
105
+ # maintainer's responsibility, not the build's.
106
+ def clear
107
+ FileUtils.rm_rf(@dir)
108
+ FileUtils.mkdir_p(@dir)
109
+ end
110
+
111
+ # SHA256 over the input closure, truncated to 16 hex (64 bits -- ample for
112
+ # a per-collection store, and short enough to stay well inside filename
113
+ # length limits). Each input is hashed to a fixed-width digest and the
114
+ # digests concatenated before the final hash, so no two distinct closures
115
+ # collide on the pre-truncation digest. The caller assembles the closure;
116
+ # the store does not infer it.
117
+ def self.content_hash(*inputs)
118
+ digests = inputs.map { |i| Digest::SHA256.hexdigest(i.to_s) }
119
+ Digest::SHA256.hexdigest(digests.join)[0, 16]
120
+ end
121
+
122
+ private
123
+
124
+ def slug(docid)
125
+ docid.to_s.gsub(/[^A-Za-z0-9._-]+/, "-")
126
+ .gsub(/-{2,}/, "-").gsub(/\A-|-\z/, "")
127
+ end
128
+ end
129
+
130
+ # Null object: the store disabled (the default). Never reports a hit, never
131
+ # writes. Lets the renderer treat "no incremental build" as the no-store
132
+ # path without a conditional at every call site (open/closed).
133
+ class NullArtifactStore
134
+ def path(*_args) = nil
135
+ def key?(*_args) = false
136
+ def read(*_args) = nil
137
+ def write(*_args) = nil
138
+ def prune_superseded(*_args) = nil
139
+ def clear(*_args) = nil
140
+ end
141
+ end
142
+ end
@@ -1,24 +1,28 @@
1
- require "relaton"
2
- require "relaton/cli"
1
+ require "relaton-cli"
3
2
  require "metanorma-utils"
4
3
  require_relative "util/util"
5
4
  require_relative "util/disambig_files"
6
5
  require_relative "config/config"
7
6
  require_relative "config/manifest"
7
+ require_relative "helpers"
8
8
 
9
9
  module Metanorma
10
10
  class FileNotFoundException < StandardError; end
11
11
 
12
12
  class AdocFileNotFoundException < StandardError; end
13
13
 
14
+ # One or more collection members failed to render (#586)
15
+ class RenderFailureException < StandardError; end
16
+
14
17
  # Metanorma collection of documents
15
18
  class Collection
16
- attr_reader :file
19
+ attr_reader :file, :prefatory, :final
17
20
 
18
21
  # @return [Array<String>] documents-inline to inject the XML into
19
22
  # the collection manifest; documents-external to keeps them outside
20
- attr_accessor :directives, :documents, :bibdatas, :coverpage, :dirname
21
- attr_accessor :disambig, :manifest, :bibdata, :compile, :config
23
+ attr_accessor :directives, :documents, :bibdatas, :coverpage,
24
+ :coverpage_pdf_portfolio, :dirname,
25
+ :disambig, :manifest, :bibdata, :compile, :config
22
26
 
23
27
  # @param file [String] path to source file
24
28
  # @param config [Metanorma::Collection::Config]
@@ -45,12 +49,14 @@ module Metanorma
45
49
  def initialize_vars
46
50
  @compile = Metanorma::Compile.new # feeds @manifest
47
51
  @log = Metanorma::Utils::Log.new
52
+ @log.suppress_log = { severity: 4, category: [], error_ids: [],
53
+ locations: [] }
48
54
  @disambig = Util::DisambigFiles.new
49
55
  end
50
56
 
51
57
  def initialize_config(config)
52
58
  @config = config
53
- @directives = config.directive || [] # feeds initialize_directives
59
+ @directives = (config.directive || []).dup # feeds initialize_directives
54
60
  @bibdata = config.bibdata
55
61
  @prefatory = config.prefatory_content
56
62
  @final = config.final_content
@@ -63,6 +69,7 @@ module Metanorma
63
69
  def initialize_directives
64
70
  d = @directives.each_with_object({}) { |x, m| m[x.key] = x.value }
65
71
  @coverpage = d["coverpage"]
72
+ @coverpage_pdf_portfolio = d["coverpage-pdf-portfolio"]
66
73
  @coverpage_style = d["coverpage-style"]
67
74
  @flavor = d["flavor"]
68
75
  if (@documents.any? || @manifest) && !d.key?("documents-inline") &&
@@ -85,75 +92,155 @@ module Metanorma
85
92
  def to_xml
86
93
  c = ::Metanorma::Collection::Config::Config
87
94
  .new(directive: @directives, bibdata: @bibdata,
88
- manifest: @manifest.config, documents: @documents,
95
+ manifest: @manifest.config, documents: doc_containers,
89
96
  prefatory_content: @prefatory, final_content: @final)
90
97
  c.collection = self
91
- c.to_xml # .sub("<metanorma-collection", "<metanorma-collection xmlns='http://metanorma.org'")
98
+ c.to_xml
99
+ end
100
+
101
+ # Populate the Config#documents collection with DocContainer wrappers.
102
+ # Only emit them when the `documents-inline` directive is set; otherwise
103
+ # the manifest's <entry fileref=…> already references them externally.
104
+ def doc_containers
105
+ @directives.detect { |d| d.key == "documents-inline" } or return []
106
+ @documents.each_with_index.map do |(_, d), idx|
107
+ ::Metanorma::Collection::Config::DocContainer.new(
108
+ id: format("doc%<index>09d", index: idx),
109
+ content: doccontainer1_inner(d),
110
+ )
111
+ end
112
+ end
113
+
114
+ # Inner XML of a single <doc-container> as a string. The body is either
115
+ # the document's <metanorma> presentation root (with its own xmlns) or
116
+ # an attachment payload.
117
+ def doccontainer1_inner(doc)
118
+ b = Nokogiri::XML::Builder.new do |xml|
119
+ xml.send(:wrapper) do |w|
120
+ if doc.attachment
121
+ doc.bibitem and w << doc.bibitem.root.to_xml
122
+ w.attachment Vectory::Utils::datauri(doc.file)
123
+ else
124
+ doc.to_xml w
125
+ w.parent.children.first["flavor"] = Util::taste2flavor(flavor)
126
+ end
127
+ end
128
+ end
129
+ b.parent.elements.first.children.map(&:to_xml).join
92
130
  end
93
131
 
94
132
  def render(opts)
95
- if opts[:format].nil? || opts[:format].empty?
133
+ opts[:format].nil? || opts[:format].empty? and
96
134
  opts[:format] = @format || [:html]
97
- end
98
135
  opts[:log] = @log
99
136
  opts[:flavor] = @flavor
100
- ::Metanorma::Collection::Renderer.render self, opts
137
+ output_folder(opts)
138
+ cr = ::Metanorma::Collection::Renderer.render self, opts
101
139
  clean_exit
140
+ render_failures_abort(cr)
141
+ cr
102
142
  end
103
143
 
104
- # @return [String, nil]
105
- attr_reader :prefatory, :final
106
-
107
- # @return [String]
108
- def dummy_header
109
- <<~DUMMY
110
- = X
111
- A
144
+ # A member whose rendering fails no longer lets the collection
145
+ # report success with the document missing from the output (#586).
146
+ # Every member is rendered before aborting, so one run reports all
147
+ # failures; the raise makes callers (CLI, suma) exit non-zero. Runs
148
+ # after clean_exit so the failures are in the written log.
149
+ def render_failures_abort(renderer)
150
+ f = renderer.fatal_errors
151
+ f.empty? and return
152
+ raise RenderFailureException.new(
153
+ "Collection render failed for #{f.size} document(s):\n" +
154
+ f.join("\n"),
155
+ )
156
+ end
112
157
 
113
- DUMMY
158
+ def output_folder(opts)
159
+ opts[:output_folder] ||= config.output_folder
160
+ opts[:output_folder] && !Pathname.new(opts[:output_folder]).absolute? and
161
+ opts[:output_folder] = File.join(@dirname, opts[:output_folder])
162
+ warn opts[:output_folder]
114
163
  end
115
164
 
116
165
  # @param elm [String] 'prefatory' or 'final'
117
166
  # @param builder [Nokogiri::XML::Builder]
118
167
  def content_to_xml(elm, builder)
119
168
  (cnt = send(elm)) or return
120
- @compile.load_flavor(flavor)
121
- out = sections(dummy_header + cnt.strip)
169
+ @compile.load_flavor(Util::taste2flavor(flavor))
170
+ out = prefatory_parse(
171
+ Util::asciidoc_dummy_header(
172
+ docidentifier: dummy_header_docidentifier,
173
+ ) + cnt.strip,
174
+ )
122
175
  builder.send("#{elm}-content") { |b| b << out }
123
176
  end
124
177
 
178
+ # Pick a real docidentifier value for the prefatory dummy header so
179
+ # the flavor's metadata_id has something pubid-parseable to put into
180
+ # the bibdata, instead of falling back to its (Liquid-templated)
181
+ # docid_template (issue #558). Prefer the first manifest document's
182
+ # docidentifier (concrete, e.g. "IHO S-97"); fall back to the
183
+ # collection's own (which may carry suffixes like "(all parts)" that
184
+ # pubid won't parse). Both candidates are wrapped because the
185
+ # bibitem field may be a parsed Relaton BibliographicItem in some
186
+ # phases and a Nokogiri::XML::Document in others.
187
+ def dummy_header_docidentifier
188
+ first_doc_docid || @bibdata&.docidentifier&.first&.content
189
+ end
190
+
191
+ def first_doc_docid
192
+ bib = documents.values.first&.bibitem or return nil
193
+ if bib.respond_to?(:docidentifier)
194
+ bib.docidentifier&.first&.content
195
+ elsif bib.respond_to?(:at) # Nokogiri (rendering-phase shape)
196
+ # Use local-name() so the match works regardless of whether the
197
+ # rendering-phase document carries a default namespace, without
198
+ # tripping Nokogiri's "undefined namespace prefix" error when
199
+ # it doesn't.
200
+ bib.at("//*[local-name()='docidentifier']")&.text
201
+ end
202
+ end
203
+
125
204
  # @param cnt [String] prefatory/final content
126
205
  # @return [String] XML
127
- def sections(cnt)
128
- c = Asciidoctor.convert(cnt, backend: flavor.to_sym, header_footer: true)
129
- x = Nokogiri::XML(c)
130
- x.xpath("//xmlns:clause").each { |n| n["unnumbered"] = true }
131
- file = Tempfile.new(%w(foo presentation.xml))
132
- file.write(x.to_xml(indent: 0))
133
- file.close
134
- c1 = Util::isodoc_create(@flavor, @manifest.lang, @manifest.script, x, presxml: true)
135
- .convert(file.path, nil, true)
136
- Nokogiri::XML(c1).at("//xmlns:sections").children.to_xml
206
+ def prefatory_parse(cnt)
207
+ x = prefatory_parse_semantic(cnt)
208
+ _, filepath = Util::nokogiri_to_temp(x, "foo", ".presentation.xml")
209
+ c1 = Util::isodoc_create(Util::taste2flavor(@flavor), @manifest.lang,
210
+ @manifest.script, x,
211
+ presxml: true).convert(filepath, nil, true)
212
+ presxml = Nokogiri::XML(c1)
213
+ prefatory_extract_xml(presxml)
137
214
  end
138
215
 
139
- # @param builder [Nokogiri::XML::Builder]
140
- def doccontainer(builder)
141
- @directives.detect { |d| d.key == "documents-inline" } or return
142
- documents.each_with_index do |(_, d), i|
143
- doccontainer1(builder, d, i)
144
- end
216
+ def prefatory_extract_xml(presxml)
217
+ body = presxml.at("//xmlns:sections")
218
+ body.at("//xmlns:p[@class = 'zzSTDTitle1']")&.remove
219
+ body.text.to_s.strip.empty? and body = presxml.at("//xmlns:preface")
220
+ body.at("//xmlns:clause[@type = 'toc']")&.remove
221
+ body.children.to_xml
145
222
  end
146
223
 
147
- def doccontainer1(builder, doc, idx)
148
- id = format("doc%<index>09d", index: idx)
149
- builder.send(:"doc-container", id: id) do |b|
150
- if doc.attachment
151
- doc.bibitem and b << doc.bibitem.root.to_xml
152
- b.attachment Vectory::Utils::datauri(doc.file)
153
- else
154
- doc.to_xml b
155
- end
224
+ def prefatory_parse_semantic(cnt)
225
+ c = Asciidoctor.convert(cnt, backend: Util::taste2flavor(flavor).to_sym,
226
+ header_footer: true)
227
+ x = Nokogiri::XML(c)
228
+ x.xpath("//xmlns:clause").each { |n| n["unnumbered"] = true }
229
+ b = x.at("//xmlns:bibdata")
230
+ prefatory_parse_fix_bibdata(b)
231
+ b.children = ::Metanorma::Standoc::Cleanup::MergeBibitems
232
+ .new(b.to_xml, @bibdata.to_xml(bibdata: true)).merge.to_noko.children
233
+ x
234
+ end
235
+
236
+ # Stop standoc ownerless copyright from breaking Relaton parse of bibdata
237
+ def prefatory_parse_fix_bibdata(bibdata)
238
+ if cop = bibdata.at("//xmlns:copyright")
239
+ cop.at("./xmlns:owner") or
240
+ cop.children.first.previous =
241
+ "<owner><organization><name>SDO</name></organization></owner>"
156
242
  end
243
+ bibdata
157
244
  end
158
245
 
159
246
  def flavor
@@ -163,92 +250,14 @@ module Metanorma
163
250
  # TODO: retrieve flavor based on @bibdata publisher when lookup implemented
164
251
  # Will still infer based on docid, but will validate it before proceeding
165
252
  def fetch_flavor
166
- docid = @bibdata.docidentifier.first or return
167
- f = docid.type.downcase || docid.id.sub(/\s.*$/, "").downcase or return
168
- require ::Metanorma::Compile.new.stdtype2flavor(f)
253
+ docid = @bibdata&.docidentifier&.first or return
254
+ f = docid.type&.downcase || docid.content&.sub(/\s.*$/,
255
+ "")&.downcase or return
256
+ f = Util::taste2flavor(f)
257
+ require ::Metanorma::Compile.new.stdtype2flavor_gem(f)
169
258
  f
170
- rescue LoadError
259
+ rescue LoadError, NameError
171
260
  nil
172
261
  end
173
-
174
- class << self
175
- # @param Block [Proc]
176
- # @note allow user-specific function to run in pre-parse model stage
177
- def set_pre_parse_model(&block)
178
- @pre_parse_model_proc = block
179
- end
180
-
181
- # @param Block [Proc]
182
- # @note allow user-specific function to resolve identifier
183
- def set_identifier_resolver(&block)
184
- @identifier_resolver = block
185
- end
186
-
187
- # @param Block [Proc]
188
- # @note allow user-specific function to resolve fileref
189
- # NOTE: MUST ALWAYS RETURN PATH relative to working directory
190
- # (initial YAML file location). @fileref_resolver.call(ref_folder, fileref)
191
- # fileref is not what is in the YAML, but the resolved path
192
- # relative to the working directory
193
- def set_fileref_resolver(&block)
194
- @fileref_resolver = block
195
- end
196
-
197
- def unset_fileref_resolver
198
- @fileref_resolver = nil
199
- end
200
-
201
- # @param collection_model [Hash{String=>String}]
202
- def pre_parse_model(collection_model)
203
- @pre_parse_model_proc or return
204
- @pre_parse_model_proc.call(collection_model)
205
- end
206
-
207
- # @param identifier [String]
208
- # @return [String]
209
- def resolve_identifier(identifier)
210
- @identifier_resolver or return identifier
211
- @identifier_resolver.call(identifier)
212
- end
213
-
214
- # @param fileref [String]
215
- # @return [String]
216
- def resolve_fileref(ref_folder, fileref)
217
- warn ref_folder
218
- warn fileref
219
- #require "debug"; binding.b
220
- unless @fileref_resolver
221
- (Pathname.new fileref).absolute? or
222
- fileref = File.join(ref_folder, fileref)
223
- return fileref
224
- end
225
-
226
- @fileref_resolver.call(ref_folder, fileref)
227
- end
228
-
229
- # @param filepath
230
- # @raise [FileNotFoundException]
231
- def check_file_existence(filepath)
232
- unless File.exist?(filepath)
233
- error_message = "#{filepath} not found!"
234
- ::Metanorma::Util.log("[metanorma] Error: #{error_message}", :error)
235
- raise FileNotFoundException.new error_message.to_s
236
- end
237
- end
238
-
239
- def parse(file)
240
- # need @dirname initialised before collection object initialisation
241
- @dirname = File.expand_path(File.dirname(file))
242
- config = case file
243
- when /\.xml$/
244
- ::Metanorma::Collection::Config::Config.from_xml(File.read(file))
245
- when /.ya?ml$/
246
- y = YAML.safe_load(File.read(file))
247
- pre_parse_model(y)
248
- ::Metanorma::Collection::Config::Config.from_yaml(y.to_yaml)
249
- end
250
- new(file: file, config: config)
251
- end
252
- end
253
262
  end
254
263
  end
@@ -5,7 +5,7 @@ module Metanorma
5
5
  class Collection
6
6
  module Config
7
7
  class Bibdata < ::Lutaml::Model::Serializable
8
- model ::RelatonBib::BibliographicItem
8
+ model ::Relaton::Bib::ItemData
9
9
  end
10
10
  end
11
11
  end