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.
- checksums.yaml +4 -4
- data/.rubocop.yml +23 -2
- data/Gemfile +14 -0
- data/README.adoc +1 -1
- data/lib/metanorma/collection/artifact_store.rb +142 -0
- data/lib/metanorma/collection/collection.rb +140 -131
- data/lib/metanorma/collection/config/bibdata.rb +1 -1
- data/lib/metanorma/collection/config/config.rb +50 -20
- data/lib/metanorma/collection/config/converters.rb +89 -31
- data/lib/metanorma/collection/config/doc_container.rb +28 -0
- data/lib/metanorma/collection/config/manifest.rb +30 -6
- data/lib/metanorma/collection/config/namespaces.rb +12 -0
- data/lib/metanorma/collection/document/document.rb +54 -15
- data/lib/metanorma/collection/filelookup/filelookup.rb +170 -74
- data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +62 -27
- data/lib/metanorma/collection/filelookup/utils.rb +52 -0
- data/lib/metanorma/collection/helpers.rb +82 -0
- data/lib/metanorma/collection/log.rb +24 -0
- data/lib/metanorma/collection/manifest/manifest.rb +27 -14
- data/lib/metanorma/collection/multilingual/multilingual.rb +1 -1
- data/lib/metanorma/collection/renderer/filelocation.rb +166 -0
- data/lib/metanorma/collection/renderer/fileparse.rb +60 -55
- data/lib/metanorma/collection/renderer/fileprocess.rb +184 -40
- data/lib/metanorma/collection/renderer/navigation.rb +20 -6
- data/lib/metanorma/collection/renderer/render_word.rb +8 -4
- data/lib/metanorma/collection/renderer/renderer.rb +202 -19
- data/lib/metanorma/collection/renderer/svg.rb +60 -5
- data/lib/metanorma/collection/renderer/utils.rb +76 -42
- data/lib/metanorma/collection/sectionsplit/collection.rb +14 -4
- data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +141 -61
- data/lib/metanorma/collection/util/disambig_files.rb +4 -5
- data/lib/metanorma/collection/util/util.rb +147 -23
- data/lib/metanorma/collection/xrefprocess/xrefprocess.rb +7 -7
- data/lib/metanorma/compile/assets/icc-boilerplate.adoc +41 -0
- data/lib/metanorma/compile/compile.rb +180 -165
- data/lib/metanorma/compile/compile_options.rb +155 -83
- data/lib/metanorma/compile/extract.rb +76 -56
- data/lib/metanorma/compile/flavor.rb +19 -0
- data/lib/metanorma/compile/output_filename.rb +75 -0
- data/lib/metanorma/compile/output_filename_config.rb +27 -0
- data/lib/metanorma/compile/relaton_drop.rb +56 -0
- data/lib/metanorma/compile/render.rb +196 -0
- data/lib/metanorma/compile/validator.rb +26 -0
- data/lib/metanorma/compile/writeable.rb +12 -0
- data/lib/metanorma/util/fontist_helper.rb +35 -19
- data/lib/metanorma/version.rb +1 -1
- data/lib/metanorma.rb +1 -7
- data/metanorma.gemspec +11 -20
- data/plans/collection-rendering-architecture-review.md +516 -0
- metadata +42 -122
- data/.hound.yml +0 -5
- data/lib/metanorma/asciidoctor_extensions/glob_include_processor.rb +0 -22
- data/lib/metanorma/asciidoctor_extensions.rb +0 -1
- data/lib/metanorma/compile/compile_validate.rb +0 -68
- data/lib/metanorma/config/config.rb +0 -23
- data/lib/metanorma/input/asciidoc.rb +0 -88
- data/lib/metanorma/input/base.rb +0 -9
- data/lib/metanorma/input.rb +0 -7
- data/lib/metanorma/processor/processor.rb +0 -54
- data/lib/metanorma/processor.rb +0 -6
- data/lib/metanorma/registry/registry.rb +0 -63
- data/lib/metanorma/util/util.rb +0 -45
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f36a841296240362316b293a3353c54b85b83485b81d21e762d639fe26ce100
|
|
4
|
+
data.tar.gz: 7302ecaa3bbf5893fa6a0c52199ce7080475efb00a9973fe8e21ace651a996b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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/
|
|
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
|
-
|
|
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,
|
|
21
|
-
|
|
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:
|
|
95
|
+
manifest: @manifest.config, documents: doc_containers,
|
|
89
96
|
prefatory_content: @prefatory, final_content: @final)
|
|
90
97
|
c.collection = self
|
|
91
|
-
c.to_xml
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
@
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
167
|
-
f = docid.type
|
|
168
|
-
|
|
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
|