metanorma 2.4.3 → 2.5.1

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.
@@ -17,10 +17,36 @@ module Metanorma
17
17
  class Renderer
18
18
  FORMATS = %i[html xml doc pdf pdf-portfolio].freeze
19
19
 
20
- attr_accessor :isodoc, :isodoc_presxml, :nested
20
+ attr_accessor :isodoc, :isodoc_presxml
21
21
  attr_reader :xml, :compile, :compile_options, :documents, :outdir,
22
22
  :manifest
23
23
 
24
+ # Run the block with the renderer in nested mode, restoring the previous
25
+ # mode afterwards. Nested mode (used by sectionsplit, which re-enters this
26
+ # renderer's update_xrefs on the pre-split document) preserves unresolved
27
+ # erefs and skips the finalising reference passes.
28
+ def with_nested
29
+ saved = @nested
30
+ @nested = true
31
+ yield
32
+ ensure
33
+ @nested = saved
34
+ end
35
+
36
+ # Run the block with the renderer preserving unresolved cross-document
37
+ # references as stubs instead of stripping them -- for an isolated build of
38
+ # a collection member (compiled without the rest of its collection
39
+ # present). Unlike +with_nested+, the ordinary intra-document passes still
40
+ # run (xref_process, svgmap); only the cross-document resolve/strip is
41
+ # turned into preserve, so a later reinflation pass can relink the stubs.
42
+ def with_preserve_unresolved
43
+ saved = @preserve_unresolved
44
+ @preserve_unresolved = true
45
+ yield
46
+ ensure
47
+ @preserve_unresolved = saved
48
+ end
49
+
24
50
  # This is only going to render the HTML collection
25
51
  # @param xml [Metanorma::Collection] input XML collection
26
52
  # @param folder [String] input folder
@@ -71,6 +97,31 @@ module Metanorma
71
97
  # if false, this is the root instance of Renderer
72
98
  # if true, then this is not the last time Renderer will be run
73
99
  # (e.g. this is sectionsplit)
100
+ # Isolated/incremental build: preserve unresolved cross-document
101
+ # references as stubs instead of resolving or stripping them, so each
102
+ # member compiles independently and a later reinflation pass relinks.
103
+ @preserve_unresolved = options[:preserve_unresolved]
104
+ # Durable content-addressed store for staged artefacts. Opt-in: a plain
105
+ # build sets neither :preserve_unresolved nor :artifact_store_dir, so it
106
+ # gets the no-op Null store and is unaffected. Staging (:preserve_unresolved)
107
+ # defaults the store directory to ArtifactStore::DEFAULT_DIRNAME when no
108
+ # :artifact_store_dir is given, so callers need not name it. (Reinflation
109
+ # reads stored stubs via the manifest, not this object, so it does not
110
+ # trigger the default -- that would only create an empty directory.)
111
+ @artifact_store =
112
+ if (dir = options[:artifact_store_dir]) || options[:preserve_unresolved]
113
+ ArtifactStore.new(dir || ArtifactStore::DEFAULT_DIRNAME)
114
+ else
115
+ NullArtifactStore.new
116
+ end
117
+ # Reinflation pass: the input is a stored stub-bearing Semantic XML that
118
+ # already has intra-doc xrefs and the document suffix applied, so run
119
+ # only the cross-document resolution, not the passes already done.
120
+ @reinflate = options[:reinflate]
121
+ # Cache retention: by default keep only each document's latest artefacts,
122
+ # pruning superseded content-hash versions on write; keep_cache retains
123
+ # every version (history / multiple states side by side).
124
+ @keep_cache = options[:keep_cache]
74
125
 
75
126
  @coverpage = options[:coverpage] || collection.coverpage
76
127
  @coverpage_pdf_portflio = options[:coverpage_pdf_portfolio] ||
@@ -90,21 +141,6 @@ module Metanorma
90
141
  directives_normalise_keystore_pdf_portfolio(directives)
91
142
  end
92
143
 
93
- # KILL
94
- def directives_normalise_coverpage_pdf_portfolio(directives)
95
- directives.reject! { |d| d.key == "coverpage-pdf-portfolio" }
96
- absolute_coverpage_pdf_portflio = @coverpage_pdf_portflio &&
97
- Pathname.new(@coverpage_pdf_portflio).absolute?
98
- @coverpage_pdf_portflio =
99
- Util::rel_path_resolve(@dirname, @coverpage_pdf_portflio)
100
- absolute_coverpage_pdf_portflio or
101
- @coverpage_pdf_portflio = Pathname.new(@coverpage_pdf_portflio)
102
- .relative_path_from(Pathname.new(@outdir)).to_s
103
- directives << ::Metanorma::Collection::Config::Directive
104
- .new(key: "coverpage-pdf-portfolio", value: @coverpage_pdf_portflio)
105
- directives
106
- end
107
-
108
144
  def directives_normalise_coverpage_pdf_portfolio(directives)
109
145
  directives_resolve_filepath(directives, "coverpage-pdf-portfolio",
110
146
  @coverpage_pdf_portflio)
@@ -258,10 +294,22 @@ module Metanorma
258
294
  .new(key: "documents-inline")
259
295
  out.bibdatas.each_key do |ident|
260
296
  id = @isodoc.docid_prefix(nil, ident.dup)
261
- @files.get(id, :attachment) || @files.get(id, :outputs).nil? and next
297
+ @files.get(id, :attachment) and next
298
+ # A non-attachment document with no compiled output for THIS format
299
+ # cannot be inlined: leaving it in place serialises as a bibdata-only,
300
+ # namespace-less <metanorma> that mn2pdf silently drops. Skip loudly
301
+ # rather than crash on a nil path or corrupt the collection PDF.
302
+ # (Sectionsplit parents get a :presentation output upstream, so they
303
+ # inline whole for the PDF/presentation path.)
304
+ outputs = @files.get(id, :outputs)
305
+ if outputs.nil? || outputs[ext].nil?
306
+ warn "[metanorma] collection document '#{id}' has no compiled " \
307
+ "#{ext} output to inline; skipping (its doc-container would " \
308
+ "otherwise be bibdata-only)."
309
+ next
310
+ end
262
311
  out.documents[Util::key id] =
263
- Metanorma::Collection::Document
264
- .raw_file(@files.get(id, :outputs)[ext])
312
+ Metanorma::Collection::Document.raw_file(outputs[ext])
265
313
  end
266
314
  out
267
315
  end
@@ -7,8 +7,24 @@ require "concurrent-ruby"
7
7
 
8
8
  module Metanorma
9
9
  class Collection
10
+ # Sectionsplit is a collection render nested inside a collection render:
11
+ # a document is split into one output file per section, and those files are
12
+ # then rendered as their own (sub-)collection.
13
+ #
14
+ # Two output-location invariants matter (both load-bearing -- see the
15
+ # collection architecture review plan and metanorma/iso-10303#208):
16
+ #
17
+ # * XML section files are always written FLAT to the split directory
18
+ # (basename only). HTML output may instead carry a directory taken from
19
+ # sectionsplit_filename, reattached only at HTML compile time
20
+ # (preserve_directory_structure?).
21
+ # * A sectionsplit document's content is emitted at the sectionsplit output
22
+ # location (the collection root, unless sectionsplit_filename sets a
23
+ # directory), NOT at the document's own :out_path. Anything relativising a
24
+ # URL for such a document (e.g. attachment/citation links) must base it on
25
+ # the split location, or the ../../ overshoots.
10
26
  class Sectionsplit
11
- attr_accessor :filecache, :key
27
+ attr_accessor :filecache, :key, :whole_presentation_file
12
28
 
13
29
  def initialize(opts)
14
30
  @input_filename = opts[:input]
@@ -40,6 +56,13 @@ module Metanorma
40
56
  # Input XML is Semantic XML
41
57
  def sectionsplit
42
58
  xml = sectionsplit_prep(File.read(@input_filename), @base, @dir)
59
+ # Retain the whole (un-split) Presentation XML. sectionsplit is an
60
+ # HTML-only mechanism, but the collection PDF/presentation path must
61
+ # inline each document intact; without this the sectionsplit parent
62
+ # reaches concatenation with no presentation output and degrades to a
63
+ # bibdata-only, namespace-less <metanorma> stub that mn2pdf drops.
64
+ # https://github.com/metanorma/iso-10303/issues/208
65
+ @whole_presentation_file = write_whole_presentation(xml)
43
66
  @key = Metanorma::Collection::XrefProcess::xref_preprocess(xml, @isodoc)
44
67
  empty = empty_doc(xml)
45
68
  empty1 = empty_attachments(empty)
@@ -66,11 +89,11 @@ module Metanorma
66
89
 
67
90
  def sectionsplit2(xml, empty, chunks, parentnode, opt)
68
91
  @pool.post do
69
- output_filename = @sectionsplit_filename
70
- &.gsub(/\{document-num\}/, @parent_idx.to_s)
71
- &.gsub(/\{basename_legacy\}/, @base)
72
- &.gsub(/\{basename\}/, File.basename(@base, ".*"))
73
- &.gsub(/\{sectionsplit-num\}/, opt[:idx].to_s)
92
+ output_filename = Util.substitute_filename_pattern(
93
+ @sectionsplit_filename,
94
+ document_num: @parent_idx, basename: File.basename(@base, ".*"),
95
+ basename_legacy: @base, sectionsplit_num: opt[:idx]
96
+ )
74
97
  warn "Sectionsplit: #{output_filename}"
75
98
  a = sectionfile(xml, empty, output_filename, chunks,
76
99
  parentnode)
@@ -108,6 +131,16 @@ module Metanorma
108
131
  r
109
132
  end
110
133
 
134
+ # Persist the whole, svgmap-restored Presentation XML (the in-memory doc,
135
+ # not the on-disk tmp which still carries the internal svgmap1 marker) so
136
+ # the collection PDF path can inline the document whole. Written flat to
137
+ # the split output directory, where the per-section files also live.
138
+ def write_whole_presentation(xml)
139
+ fname = File.join(@splitdir, "#{@base}.whole.presentation.xml")
140
+ File.write(fname, xml.to_xml, encoding: "UTF-8")
141
+ fname
142
+ end
143
+
111
144
  def sectionsplit_preprocess_semxml(file, filename)
112
145
  xml = Nokogiri::XML(file, &:huge)
113
146
  type = xml.root["flavor"]
@@ -120,10 +153,8 @@ module Metanorma
120
153
 
121
154
  def sectionsplit_update_xrefs(xml)
122
155
  if c = @fileslookup&.parent
123
- n = c.nested
124
- c.nested = true # so unresolved erefs are not deleted
125
- c.update_xrefs(xml, @ident, {})
126
- c.nested = n
156
+ # nested mode so unresolved erefs are not deleted
157
+ c.with_nested { c.update_xrefs(xml, @ident, {}) }
127
158
  xml.xpath("//xmlns:svgmap").each { |x| x.name = "svgmap1" }
128
159
  # do not process svgmap until after files are split
129
160
  end
@@ -81,6 +81,22 @@ module Metanorma
81
81
  @c.decode(ident).gsub(/(\p{Zs})+/, " ")
82
82
  end
83
83
 
84
+ # Substitute special strings in filename patterns. Single source for
85
+ # both FileLookup#substitute_filename_pattern and sectionsplit.
86
+ # @param pattern [String] filename pattern with placeholders
87
+ # @param options [Hash] substitution values:
88
+ # :document_num, :basename, :basename_legacy, :sectionsplit_num
89
+ def substitute_filename_pattern(pattern, options = {})
90
+ pattern or return pattern
91
+ subs = { "{document-num}" => options[:document_num],
92
+ "{basename}" => options[:basename],
93
+ "{basename_legacy}" => options[:basename_legacy],
94
+ "{sectionsplit-num}" => options[:sectionsplit_num] }
95
+ subs.each_with_object(pattern.dup) do |(token, val), result|
96
+ val and result.gsub!(token, val.to_s)
97
+ end
98
+ end
99
+
84
100
  def taste2flavor(taste)
85
101
  tastes = Metanorma::TasteRegister.instance.aliases
86
102
  tastes[taste.to_sym] and taste = tastes[taste.to_sym]
@@ -1,3 +1,3 @@
1
1
  module Metanorma
2
- VERSION = "2.4.3".freeze
2
+ VERSION = "2.5.1".freeze
3
3
  end
data/metanorma.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.bindir = "bin"
23
23
  # spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
- spec.required_ruby_version = ">= 3.1.0"
25
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.3.0")
26
26
 
27
27
  spec.add_runtime_dependency "asciidoctor"
28
28
  spec.add_runtime_dependency "concurrent-ruby"