metanorma 2.1.4 → 2.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: 59bdb97eff1bbdd231593855917d6ecc44e723dfc6bdec7bdd84a739a54d6162
4
- data.tar.gz: ed4c015ceb9003f8aa9d8151cae07d59ef8d645f1812bac237706994ad707402
3
+ metadata.gz: f6fcf07578e0bbbcd55d7ea1cbdafbe588333b5060ad10b1bb4e0851b33e4036
4
+ data.tar.gz: 7a91ca6650c240fe06eb68fc5986617662cdd6958160ecea1dafeeb09dd644e6
5
5
  SHA512:
6
- metadata.gz: 1bd7eb1ec3a9765a3135f35b5f9011463716546c6d86d1d487e99cbef2f97147ef29f685694e03945a7fac26c25c62ec580f5657b76ecda50d1054282dfb5896
7
- data.tar.gz: abb72caf7d58a9f24eafa7c5bf6fa1fd8ee2cfe2333d30d18d83fc4b1c13245dc7bf79bc60f6c7cd77654e0ec787ca1a44f0d2844eb08e75af5c4489b47096df
6
+ metadata.gz: 34989eb8241876f89038ecc1dd1353c09f9c54961b74b50048eddd330eb0a24eca628ad0e8a893108042ad4b89b57b8de8e9ff32166b7eb2e3098f0ef4340bb2
7
+ data.tar.gz: 2f9d6d4f182481d8b02b46082bafd6d75fb8d6b1a72c42987a161c60fb3a00fb4d8c5cc53674a9d001996f64bc8cea9b9e854b9d544af5126db1070c30413ac5
data/.rubocop.yml CHANGED
@@ -7,4 +7,7 @@ inherit_from:
7
7
  # ...
8
8
 
9
9
  AllCops:
10
- TargetRubyVersion: 2.5
10
+ TargetRubyVersion: 3.1
11
+
12
+ Lint/MissingSuper:
13
+ AllowedParentClasses: [Liquid::Drop]
@@ -136,7 +136,7 @@ module Metanorma
136
136
  #require "debug"; binding.b
137
137
  entry.file && !(Pathname.new entry.file).absolute? and
138
138
  entry.file = File.join(prefix, entry.file)
139
- entry.entry.each do |f|
139
+ entry&.entry&.each do |f|
140
140
  update_filepaths(f, prefix)
141
141
  end
142
142
  end
@@ -55,7 +55,7 @@ module Metanorma
55
55
 
56
56
  def index?(mnf)
57
57
  mnf.index and return true
58
- mnf.entry.detect { |e| index?(e) }
58
+ mnf.entry&.detect { |e| index?(e) }
59
59
  end
60
60
 
61
61
  def indexfile1(mnf)
@@ -1,21 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "fileutils"
2
4
  require "nokogiri"
3
5
  require "htmlentities"
4
6
  require "yaml"
5
7
  require "fontist"
6
8
  require "fontist/manifest/install"
7
- require_relative "compile_validate"
9
+ require_relative "writeable"
10
+ require_relative "validator"
8
11
  require_relative "compile_options"
9
12
  require_relative "../util/fontist_helper"
10
13
  require_relative "../util/util"
11
14
  require_relative "extract"
12
15
  require_relative "../collection/sectionsplit/sectionsplit"
13
16
  require_relative "../util/worker_pool"
17
+ require_relative "output_filename"
18
+ require_relative "output_filename_config"
19
+ require_relative "flavor"
20
+ require_relative "relaton_drop"
14
21
 
15
22
  module Metanorma
16
23
  class Compile
24
+ include Validator
25
+ include CompileOptions
26
+ include Flavor
27
+ include Writeable
28
+
29
+ DEFAULT_NUM_WORKERS = 3
30
+
17
31
  # @return [Array<String>]
18
- attr_reader :errors, :processor
32
+ attr_reader :errors
33
+ attr_reader :processor
19
34
 
20
35
  def initialize
21
36
  @registry = Metanorma::Registry.instance
@@ -25,22 +40,48 @@ module Metanorma
25
40
  @log = Metanorma::Utils::Log.new
26
41
  end
27
42
 
43
+ # Main compile method that orchestrates the document conversion process
44
+ # @param filename [String] path to the input file
45
+ # @param options [Hash] compilation options
28
46
  def compile(filename, options = {})
29
- options_process(filename, options)
47
+ process_options!(filename, options)
30
48
  @processor = @registry.find_processor(options[:type].to_sym)
31
- (file, isodoc = process_input(filename, options)) or return nil
32
- extensions = get_extensions(options) or return nil
33
- relaton_export(isodoc, options)
34
- extract(isodoc, options[:extract], options[:extract_type])
35
- process_exts(filename, extensions, file, isodoc, options)
49
+
50
+ # Step 1: Generate Semantic XML
51
+ semantic_result = generate_semantic_xml(filename, options)
52
+ return nil unless semantic_result
53
+
54
+ source_file, semantic_xml = semantic_result
55
+
56
+ # Step 2: Prepare output paths
57
+ xml = Nokogiri::XML(semantic_xml, &:huge)
58
+ bibdata = extract_relaton_metadata(xml)
59
+ output_paths = prepare_output_paths(filename, bibdata, options)
60
+
61
+ # Step 3: Determine which output formats to generate
62
+ extensions = get_extensions(options)
63
+ return nil unless extensions
64
+
65
+ # Step 4: Extract information from Semantic XML if requested
66
+ extract_information(semantic_xml, bibdata, options)
67
+
68
+ # Step 5: Generate output formats from Semantic XML
69
+ generate_outputs(
70
+ source_file,
71
+ semantic_xml,
72
+ bibdata,
73
+ extensions,
74
+ output_paths,
75
+ options,
76
+ )
36
77
  ensure
37
78
  clean_exit(options)
38
79
  end
39
80
 
40
- def options_process(filename, options)
81
+ def process_options!(filename, options)
41
82
  require_libraries(options)
42
- options = options_extract(filename, options)
43
- validate_options(options)
83
+ options = extract_options(filename, options)
84
+ validate_options!(options)
44
85
  @log.save_to(filename, options[:output_dir])
45
86
  options[:log] = @log
46
87
  end
@@ -50,7 +91,11 @@ module Metanorma
50
91
  @log.write
51
92
  end
52
93
 
53
- def process_input(filename, options)
94
+ # Step 1: Generate Semantic XML from input file
95
+ # @param filename [String] input file path
96
+ # @param options [Hash] compilation options
97
+ # @return [Array, nil] tuple of [source_file, semantic_xml] or nil on failure
98
+ def generate_semantic_xml(filename, options)
54
99
  case extname = File.extname(filename)
55
100
  when ".adoc" then process_input_adoc(filename, options)
56
101
  when ".xml" then process_input_xml(filename, options)
@@ -61,6 +106,113 @@ module Metanorma
61
106
  end
62
107
  end
63
108
 
109
+ # Step 2: Prepare output paths for generated files
110
+ # Use default filename template if empty string is provided.
111
+ #
112
+ # @param filename [String] input file path
113
+ # @param bibdata [Nokogiri::XML::Element] the bibliographic data element
114
+ # @param options [Hash] compilation options
115
+ # @return [Hash] paths for different output formats
116
+ def prepare_output_paths(filename, bibdata, options)
117
+ basename = if !options[:filename_template].nil?
118
+ drop = RelatonDrop.new(bibdata)
119
+ config = OutputFilenameConfig.new(options[:filename_template])
120
+ config.generate_filename(drop)
121
+ else
122
+ filename.sub(/\.[^.]+$/, "")
123
+ end
124
+
125
+ @output_filename = OutputFilename.new(
126
+ basename,
127
+ options[:output_dir],
128
+ @processor,
129
+ )
130
+
131
+ {
132
+ xml: @output_filename.semantic_xml,
133
+ orig_filename: filename,
134
+ presentationxml: @output_filename.presentation_xml,
135
+ }
136
+ end
137
+
138
+ # Step 4: Extract information from Semantic XML
139
+ # @param semantic_xml [String] semantic XML content
140
+ # @param options [Hash] compilation options
141
+ def extract_information(semantic_xml, bibdata, options)
142
+ # Extract Relaton bibliographic data
143
+ export_relaton_from_bibdata(bibdata, options) if options[:relaton]
144
+
145
+ # Extract other components (sourcecode, images, requirements)
146
+ if options[:extract]
147
+ Extract.extract(
148
+ semantic_xml,
149
+ options[:extract],
150
+ options[:extract_type],
151
+ )
152
+ end
153
+ end
154
+
155
+ # Step 5: Generate output formats from Semantic XML
156
+ # @param source_file [String] source file content
157
+ # @param semantic_xml [String] semantic XML content
158
+ # @param bibdata [Nokogiri::XML::Element] the bibliographic data element
159
+ # @param extensions [Array<Symbol>] output formats to generate
160
+ # @param output_paths [Hash] paths for output files
161
+ # @param options [Hash] compilation options
162
+ def generate_outputs(
163
+ source_file, semantic_xml, bibdata, extensions, output_paths, options
164
+ )
165
+ if extensions == %i(presentation)
166
+ # Just generate presentation XML
167
+ generate_presentation_xml(
168
+ source_file, semantic_xml, bibdata, output_paths, options
169
+ )
170
+ else
171
+ # Generate multiple output formats with parallel processing
172
+ generate_outputs_parallel(
173
+ source_file, semantic_xml, bibdata, extensions, output_paths, options
174
+ )
175
+ end
176
+ end
177
+
178
+ # Generate presentation XML from semantic XML
179
+ def generate_presentation_xml(
180
+ source_file, semantic_xml, bibdata, output_paths, options
181
+ )
182
+ process_ext(
183
+ :presentation, source_file, semantic_xml, bibdata, output_paths, options
184
+ )
185
+ end
186
+
187
+ # Generate multiple output formats with parallel processing
188
+ def generate_outputs_parallel(
189
+ source_file, semantic_xml, bibdata, extensions, output_paths, options
190
+ )
191
+ @queue = ::Metanorma::Util::WorkersPool.new(
192
+ ENV["METANORMA_PARALLEL"]&.to_i || DEFAULT_NUM_WORKERS,
193
+ )
194
+
195
+ # Install required fonts for all extensions
196
+ gather_and_install_fonts(source_file, options.dup, extensions)
197
+
198
+ # Process each extension in order
199
+ process_extensions_in_order(
200
+ source_file, semantic_xml, bibdata, extensions, output_paths, options
201
+ )
202
+
203
+ @queue.shutdown
204
+ end
205
+
206
+ def process_extensions_in_order(
207
+ source_file, semantic_xml, bibdata, extensions, output_paths, options
208
+ )
209
+ Util.sort_extensions_execution(extensions).each do |ext|
210
+ process_ext(
211
+ ext, source_file, semantic_xml, bibdata, output_paths, options
212
+ ) or break
213
+ end
214
+ end
215
+
64
216
  def process_input_adoc(filename, options)
65
217
  Util.log("[metanorma] Processing: AsciiDoc input.", :info)
66
218
  file = read_file(filename)
@@ -84,19 +236,21 @@ module Metanorma
84
236
  File.read(filename, encoding: "utf-8").gsub("\r\n", "\n")
85
237
  end
86
238
 
87
- def relaton_export(isodoc, options)
239
+ # Export given bibliographic data to Relaton XML on disk
240
+ # @param bibdata [Nokogiri::XML::Element] the bibliographic data element
241
+ # @param options [Hash] compilation options
242
+ def export_relaton_from_bibdata(bibdata, options)
88
243
  return unless options[:relaton]
89
244
 
90
- xml = Nokogiri::XML(isodoc, &:huge)
91
- bibdata = xml.at("//bibdata") || xml.at("//xmlns:bibdata")
92
245
  # docid = bibdata&.at("./xmlns:docidentifier")&.text || options[:filename]
93
246
  # outname = docid.sub(/^\s+/, "").sub(/\s+$/, "").gsub(/\s+/, "-") + ".xml"
94
- File.open(options[:relaton], "w:UTF-8") { |f| f.write bibdata.to_xml }
247
+ export_output(options[:relaton], bibdata.to_xml)
95
248
  end
96
249
 
97
- def export_output(fname, content, **options)
98
- mode = options[:binary] ? "wb" : "w:UTF-8"
99
- File.open(fname, mode) { |f| f.write content }
250
+ # @param xml [Nokogiri::XML::Document] the XML document
251
+ # @return [Nokogiri::XML::Element] the bibliographic data element
252
+ def extract_relaton_metadata(xml)
253
+ xml.at("//bibdata") || xml.at("//xmlns:bibdata")
100
254
  end
101
255
 
102
256
  def wrap_html(options, file_extension, outfilename)
@@ -109,90 +263,101 @@ module Metanorma
109
263
  end
110
264
 
111
265
  # isodoc is Raw Metanorma XML
112
- def process_exts(filename, extensions, file, isodoc, options)
113
- f = File.expand_path(change_output_dir(options))
114
- fnames = { xml: f.sub(/\.[^.]+$/, ".xml"), f: f,
115
- orig_filename: File.expand_path(filename),
116
- presentationxml: f.sub(/\.[^.]+$/, ".presentation.xml") }
117
- if extensions == %i(presentation)
118
- process_ext(:presentation, file, isodoc, fnames, options)
119
- else
120
- process_exts_queue(fnames, extensions, file, isodoc, options)
121
- end
122
- end
123
266
 
124
- def process_exts_queue(fnames, extensions, file, isodoc, options)
125
- @queue = ::Metanorma::Util::WorkersPool
126
- .new(ENV["METANORMA_PARALLEL"]&.to_i || 3)
127
- gather_and_install_fonts(file, options.dup, extensions)
128
- process_exts_run(fnames, file, isodoc, extensions, options)
129
- @queue.shutdown
130
- end
131
-
132
- def process_exts_run(fnames, file, isodoc, extensions, options)
267
+ def gather_and_install_fonts(source_file, options, extensions)
133
268
  Util.sort_extensions_execution(extensions).each do |ext|
134
- process_ext(ext, file, isodoc, fnames, options) or break
135
- end
136
- end
137
-
138
- def gather_and_install_fonts(file, options, extensions)
139
- Util.sort_extensions_execution(extensions).each do |ext|
140
- isodoc_options = get_isodoc_options(file, options, ext)
269
+ isodoc_options = get_isodoc_options(source_file, options, ext)
141
270
  font_install(isodoc_options.merge(options))
142
271
  end
143
272
  end
144
273
 
145
- def process_ext(ext, file, isodoc, fnames, options)
146
- fnames[:ext] = @processor.output_formats[ext]
147
- fnames[:out] = fnames[:f].sub(/\.[^.]+$/, ".#{fnames[:ext]}")
148
- isodoc_options = get_isodoc_options(file, options, ext)
149
- thread = true
150
- unless process_ext_simple(ext, isodoc, fnames, options,
151
- isodoc_options)
152
- thread = process_exts1(ext, fnames, isodoc, options, isodoc_options)
274
+ # Process a single extension (output format)
275
+ def process_ext(ext, source_file, semantic_xml, bibdata, output_paths,
276
+ options)
277
+ output_paths[:ext] = @processor.output_formats[ext]
278
+ output_paths[:out] = @output_filename.for_format(ext) ||
279
+ output_paths[:xml].sub(/\.[^.]+$/, ".#{output_paths[:ext]}")
280
+ isodoc_options = get_isodoc_options(source_file, options, ext)
281
+
282
+ # Handle special cases first
283
+ return true if process_ext_special(
284
+ ext, semantic_xml, bibdata, output_paths, options, isodoc_options
285
+ )
286
+
287
+ # Otherwise, determine if it uses presentation XML
288
+ if @processor.use_presentation_xml(ext)
289
+ # Format requires presentation XML first, then convert to final format
290
+ process_via_presentation_xml(ext, output_paths, options, isodoc_options)
291
+ else
292
+ # Format can be generated directly from semantic XML
293
+ process_from_semantic_xml(
294
+ ext, output_paths, semantic_xml, isodoc_options
295
+ )
153
296
  end
154
- thread
155
297
  end
156
298
 
157
- def process_ext_simple(ext, isodoc, fnames, options, isodoc_options)
299
+ # Process special extensions with custom handling
300
+ def process_ext_special(
301
+ ext, semantic_xml, bibdata, output_paths, options, isodoc_options
302
+ )
158
303
  if ext == :rxl
159
- relaton_export(isodoc, options.merge(relaton: fnames[:out]))
160
- elsif options[:passthrough_presentation_xml] && ext == :presentation
161
- #f = File.exist?(fnames[:f]) ? fnames[:f] : fnames[:orig_filename]
162
- f = File.exist?(fnames[:orig_filename]) ? fnames[:orig_filename] : fnames[:f]
163
- FileUtils.cp f, fnames[:presentationxml]
304
+
305
+ # Special case: Relaton export
306
+ export_relaton_from_bibdata(
307
+ bibdata,
308
+ options.merge(relaton: output_paths[:out]),
309
+ )
310
+ true
311
+
312
+ elsif ext == :presentation && options[:passthrough_presentation_xml]
313
+
314
+ # Special case: Pass through presentation XML
315
+ f = if File.exist?(output_paths[:orig_filename])
316
+ output_paths[:orig_filename]
317
+ else
318
+ output_paths[:xml]
319
+ end
320
+
321
+ FileUtils.cp f, output_paths[:presentationxml]
322
+ true
323
+
164
324
  elsif ext == :html && options[:sectionsplit]
165
- sectionsplit_convert(fnames[:xml], isodoc, fnames[:out],
166
- isodoc_options)
167
- else return false
325
+
326
+ # Special case: Split HTML into sections
327
+ sectionsplit_convert(
328
+ output_paths[:xml], semantic_xml, output_paths[:out], isodoc_options
329
+ )
330
+ true
331
+ else
332
+ false
168
333
  end
169
- true
170
334
  end
171
335
 
172
- def process_exts1(ext, fnames, isodoc, options, isodoc_options)
173
- if @processor.use_presentation_xml(ext)
174
- @queue.schedule(ext, fnames.dup, options.dup,
175
- isodoc_options.dup) do |a, b, c, d|
176
- process_output_threaded(a, b, c, d)
177
- end
178
- else
179
- process_output_unthreaded(ext, fnames, isodoc, isodoc_options)
336
+ # Process format that requires presentation XML
337
+ def process_via_presentation_xml(ext, output_paths, options, isodoc_options)
338
+ @queue.schedule(ext, output_paths.dup, options.dup,
339
+ isodoc_options.dup) do |a, b, c, d|
340
+ process_output_from_presentation_xml(a, b, c, d)
180
341
  end
181
342
  end
182
343
 
183
- def process_output_threaded(ext, fnames1, options1, isodoc_options1)
184
- @processor.output(nil, fnames1[:presentationxml], fnames1[:out], ext,
185
- isodoc_options1)
186
- wrap_html(options1, fnames1[:ext], fnames1[:out])
344
+ # Generate output format from presentation XML
345
+ def process_output_from_presentation_xml(ext, output_paths, options,
346
+ isodoc_options)
347
+ @processor.output(nil, output_paths[:presentationxml],
348
+ output_paths[:out], ext, isodoc_options)
349
+ wrap_html(options, output_paths[:ext], output_paths[:out])
187
350
  rescue StandardError => e
188
- strict = ext == :presentation || isodoc_options1[:strict] == true
351
+ strict = ext == :presentation || isodoc_options[:strict] == true
189
352
  isodoc_error_process(e, strict, false)
190
353
  end
191
354
 
192
- def process_output_unthreaded(ext, fnames, isodoc, isodoc_options)
193
- @processor.output(isodoc, fnames[:xml], fnames[:out], ext,
194
- isodoc_options)
195
- true # return as Thread
355
+ # Process format directly from semantic XML
356
+ def process_from_semantic_xml(ext, output_paths, semantic_xml,
357
+ isodoc_options)
358
+ @processor.output(semantic_xml, output_paths[:xml], output_paths[:out],
359
+ ext, isodoc_options)
360
+ true # Return as Thread equivalent
196
361
  rescue StandardError => e
197
362
  strict = ext == :presentation || isodoc_options[:strict] == "true"
198
363
  isodoc_error_process(e, strict, true)
@@ -205,7 +370,7 @@ module Metanorma
205
370
  @isodoc ||= IsoDoc::PresentationXMLConvert.new({})
206
371
  input_filename += ".xml" unless input_filename.match?(/\.xml$/)
207
372
  File.exist?(input_filename) or
208
- File.open(input_filename, "w:UTF-8") { |f| f.write(file) }
373
+ export_output(input_filename, file)
209
374
  presxml = File.read(input_filename, encoding: "utf-8")
210
375
  _xml, filename, dir = @isodoc.convert_init(presxml, input_filename, false)
211
376
 
@@ -231,14 +396,5 @@ module Metanorma
231
396
  puts err.backtrace.join("\n")
232
397
  must_abort and 1
233
398
  end
234
-
235
- # @param options [Hash]
236
- # @return [String]
237
- def change_output_dir(options)
238
- if options[:output_dir]
239
- File.join options[:output_dir], File.basename(options[:filename])
240
- else options[:filename]
241
- end
242
- end
243
399
  end
244
400
  end
@@ -2,104 +2,109 @@ require "csv"
2
2
 
3
3
  module Metanorma
4
4
  class Compile
5
- def require_libraries(options)
6
- options&.dig(:require)&.each { |r| require r }
7
- end
5
+ module CompileOptions
6
+ def require_libraries(options)
7
+ options&.dig(:require)&.each { |r| require r }
8
+ end
8
9
 
9
- def xml_options_extract(file)
10
- xml = Nokogiri::XML(file, &:huge)
11
- if xml.root
12
- @registry.root_tags.each do |k, v|
13
- return { type: k } if v == xml.root.name
10
+ def extract_xml_options(file)
11
+ xml = Nokogiri::XML(file, &:huge)
12
+ if xml.root
13
+ @registry.root_tags.each do |k, v|
14
+ return { type: k } if v == xml.root.name
15
+ end
14
16
  end
17
+ {}
15
18
  end
16
- {}
17
- end
18
19
 
19
- def options_extract(filename, options)
20
- content = read_file(filename)
21
- o = Metanorma::Input::Asciidoc.new.extract_metanorma_options(content)
22
- .merge(xml_options_extract(content))
23
- options[:type] ||= o[:type]&.to_sym
24
- t = @registry.alias(options[:type]) and options[:type] = t
25
- dir = filename.sub(%r(/[^/]+$), "/")
26
- options[:relaton] ||= File.join(dir, o[:relaton]) if o[:relaton]
27
- options[:sourcecode] ||= File.join(dir, o[:sourcecode]) if o[:sourcecode]
28
- options[:extension_keys] ||= o[:extensions]&.split(/, */)&.map(&:to_sym)
29
- options[:extension_keys] = nil if options[:extension_keys] == [:all]
30
- options[:format] ||= :asciidoc
31
- options[:filename] = filename
32
- options[:fontlicenseagreement] ||= "no-install-fonts"
33
- options[:novalid] = o[:novalid] if o[:novalid]
34
- options
35
- end
20
+ def extract_options(filename, options)
21
+ content = read_file(filename)
22
+ o = Metanorma::Input::Asciidoc.new.extract_metanorma_options(content)
23
+ .merge(extract_xml_options(content))
24
+ options[:type] ||= o[:type]&.to_sym
25
+ t = @registry.alias(options[:type]) and options[:type] = t
26
+ dir = filename.sub(%r(/[^/]+$), "/")
27
+ options[:relaton] ||= File.join(dir, o[:relaton]) if o[:relaton]
28
+ if o[:sourcecode]
29
+ options[:sourcecode] ||= File.join(dir,
30
+ o[:sourcecode])
31
+ end
32
+ options[:extension_keys] ||= o[:extensions]&.split(/, */)&.map(&:to_sym)
33
+ options[:extension_keys] = nil if options[:extension_keys] == [:all]
34
+ options[:format] ||= :asciidoc
35
+ options[:filename] = filename
36
+ options[:fontlicenseagreement] ||= "no-install-fonts"
37
+ options[:novalid] = o[:novalid] if o[:novalid]
38
+ options
39
+ end
36
40
 
37
- def get_extensions(options)
38
- ext = extract_extensions(options)
39
- !ext.include?(:presentation) && ext.any? do |e|
40
- @processor.use_presentation_xml(e)
41
- end and ext << :presentation
42
- !ext.include?(:rxl) && options[:site_generate] and
43
- ext << :rxl
44
- ext
45
- end
41
+ def get_extensions(options)
42
+ ext = extract_extensions(options)
43
+ !ext.include?(:presentation) && ext.any? do |e|
44
+ @processor.use_presentation_xml(e)
45
+ end and ext << :presentation
46
+ !ext.include?(:rxl) && options[:site_generate] and
47
+ ext << :rxl
48
+ ext
49
+ end
46
50
 
47
- def extract_extensions(options)
48
- options[:extension_keys] ||=
49
- @processor.output_formats.reduce([]) { |memo, (k, _)| memo << k }
50
- options[:extension_keys].reduce([]) do |memo, e|
51
- if @processor.output_formats[e] then memo << e
52
- else
53
- unsupported_format_error(e)
54
- memo
51
+ def extract_extensions(options)
52
+ options[:extension_keys] ||=
53
+ @processor.output_formats.reduce([]) { |memo, (k, _)| memo << k }
54
+ options[:extension_keys].reduce([]) do |memo, e|
55
+ if @processor.output_formats[e] then memo << e
56
+ else
57
+ unsupported_format_error(e)
58
+ memo
59
+ end
55
60
  end
56
61
  end
57
- end
58
62
 
59
- def font_install(opt)
60
- @fontist_installed or
61
- Util::FontistHelper.install_fonts(@processor, opt)
62
- @fontist_installed = true
63
- end
63
+ def font_install(opt)
64
+ @fontist_installed or
65
+ Util::FontistHelper.install_fonts(@processor, opt)
66
+ @fontist_installed = true
67
+ end
64
68
 
65
- private
69
+ private
66
70
 
67
- def unsupported_format_error(ext)
68
- message = "[metanorma] Error: #{ext} format is not supported " \
69
- "for this standard."
70
- @errors << message
71
- Util.log(message, :error)
72
- end
71
+ def unsupported_format_error(ext)
72
+ message = "[metanorma] Error: #{ext} format is not supported " \
73
+ "for this standard."
74
+ @errors << message
75
+ Util.log(message, :error)
76
+ end
73
77
 
74
- def get_isodoc_options(file, options, ext)
75
- ret = @processor.extract_options(file)
76
- dir = options[:filename].sub(%r(/[^/]+$), "/")
77
- ret[:i18nyaml] &&= File.join(dir, ret[:i18nyaml])
78
- copy_isodoc_options_attrs(options, ret)
79
- font_manifest_mn2pdf(options, ret, ext)
80
- ret[:output_formats]&.select! do |k, _|
81
- options[:extension_keys].include?(k)
78
+ def get_isodoc_options(file, options, ext)
79
+ ret = @processor.extract_options(file)
80
+ dir = options[:filename].sub(%r(/[^/]+$), "/")
81
+ ret[:i18nyaml] &&= File.join(dir, ret[:i18nyaml])
82
+ copy_isodoc_options_attrs(options, ret)
83
+ font_manifest_mn2pdf(options, ret, ext)
84
+ ret[:output_formats]&.select! do |k, _|
85
+ options[:extension_keys].include?(k)
86
+ end
87
+ ret[:log] = @log
88
+ ret
82
89
  end
83
- ret[:log] = @log
84
- ret
85
- end
86
90
 
87
- def copy_isodoc_options_attrs(options, ret)
88
- ret[:datauriimage] = true if options[:datauriimage]
89
- ret[:sourcefilename] = options[:filename]
90
- %i(bare sectionsplit install_fonts baseassetpath aligncrosselements
91
- tocfigures toctables tocrecommendations strict)
92
- .each { |x| ret[x] ||= options[x] }
93
- end
91
+ def copy_isodoc_options_attrs(options, ret)
92
+ ret[:datauriimage] = true if options[:datauriimage]
93
+ ret[:sourcefilename] = options[:filename]
94
+ %i(bare sectionsplit install_fonts baseassetpath aligncrosselements
95
+ tocfigures toctables tocrecommendations strict)
96
+ .each { |x| ret[x] ||= options[x] }
97
+ end
94
98
 
95
- def font_manifest_mn2pdf(options, ret, ext)
96
- custom_fonts = Util::FontistHelper
97
- .has_custom_fonts?(@processor, options, ret)
99
+ def font_manifest_mn2pdf(options, ret, ext)
100
+ custom_fonts = Util::FontistHelper
101
+ .has_custom_fonts?(@processor, options, ret)
98
102
 
99
- ext == :pdf && custom_fonts and
100
- ret[:mn2pdf] = {
101
- font_manifest: Util::FontistHelper.location_manifest(@processor, ret),
102
- }
103
+ ext == :pdf && custom_fonts and
104
+ ret[:mn2pdf] = {
105
+ font_manifest: Util::FontistHelper.location_manifest(@processor, ret),
106
+ }
107
+ end
103
108
  end
104
109
  end
105
110
  end
@@ -1,68 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "writeable"
4
+
1
5
  module Metanorma
2
6
  class Compile
3
- def relaton_export(isodoc, options)
4
- options[:relaton] or return
5
- xml = Nokogiri::XML(isodoc, &:huge)
6
- bibdata = xml.at("//bibdata") || xml.at("//xmlns:bibdata")
7
- File.open(options[:relaton], "w:UTF-8") { |f| f.write bibdata.to_xml }
8
- end
7
+ module Extract
8
+ # @param isodoc [String] the XML document
9
+ # @param dirname [String, nil] the directory to extract to
10
+ # @param extract_types [Array<Symbol>, nil] the types to extract
11
+ # @return [void]
12
+ def self.extract(isodoc, dirname, extract_types)
13
+ dirname or return
14
+ extract_types.nil? || extract_types.empty? and
15
+ extract_types = %i[sourcecode image requirement]
16
+ FileUtils.rm_rf dirname
17
+ FileUtils.mkdir_p dirname
18
+ xml = Nokogiri::XML(isodoc, &:huge)
19
+ extract_types.each do |type|
20
+ case type
21
+ when :sourcecode
22
+ export_sourcecode(xml, dirname)
23
+ when :image
24
+ export_image(xml, dirname)
25
+ when :requirement
26
+ export_requirement(xml, dirname)
27
+ end
28
+ end
29
+ end
9
30
 
10
- def clean_sourcecode(xml)
11
- xml.xpath(".//callout | .//annotation | .//xmlns:callout | "\
12
- ".//xmlns:annotation").each(&:remove)
13
- xml.xpath(".//br | .//xmlns:br").each { |x| x.replace("\n") }
14
- a = xml.at("./body | ./xmlns:body") and xml = a
15
- HTMLEntities.new.decode(xml.children.to_xml)
16
- end
31
+ class << self
32
+ include Writeable
17
33
 
18
- def extract(isodoc, dirname, extract_types)
19
- dirname or return
20
- extract_types.nil? || extract_types.empty? and
21
- extract_types = %i[sourcecode image requirement]
22
- FileUtils.rm_rf dirname
23
- FileUtils.mkdir_p dirname
24
- xml = Nokogiri::XML(isodoc, &:huge)
25
- sourcecode_export(xml, dirname) if extract_types.include? :sourcecode
26
- image_export(xml, dirname) if extract_types.include? :image
27
- extract_types.include?(:requirement) and
28
- requirement_export(xml, dirname)
29
- end
34
+ private
30
35
 
31
- def sourcecode_export(xml, dirname)
32
- xml.at("//sourcecode | //xmlns:sourcecode") or return
33
- FileUtils.mkdir_p "#{dirname}/sourcecode"
34
- xml.xpath("//sourcecode | //xmlns:sourcecode").each_with_index do |s, i|
35
- filename = s["filename"] || sprintf("sourcecode-%04d.txt", i)
36
- export_output("#{dirname}/sourcecode/#{filename}",
37
- clean_sourcecode(s.dup))
38
- end
39
- end
36
+ # @param xml [Nokogiri::XML::Document] the XML document
37
+ # @return [String] the cleaned sourcecode
38
+ def clean_sourcecode(xml)
39
+ xml.xpath(".//callout | .//annotation | .//xmlns:callout | "\
40
+ ".//xmlns:annotation").each(&:remove)
41
+ xml.xpath(".//br | .//xmlns:br").each { |x| x.replace("\n") }
42
+ a = xml.at("./body | ./xmlns:body") and xml = a
43
+ HTMLEntities.new.decode(xml.children.to_xml)
44
+ end
40
45
 
41
- def image_export(xml, dirname)
42
- xml.at("//image | //xmlns:image") or return
43
- FileUtils.mkdir_p "#{dirname}/image"
44
- xml.xpath("//image | //xmlns:image").each_with_index do |s, i|
45
- next unless /^data:image/.match? s["src"]
46
+ def export_sourcecode(xml, dirname)
47
+ xml.at("//sourcecode | //xmlns:sourcecode") or return
48
+ FileUtils.mkdir_p "#{dirname}/sourcecode"
49
+ xml.xpath("//sourcecode | //xmlns:sourcecode").each_with_index do |s, i|
50
+ filename = s["filename"] || sprintf("sourcecode-%04d.txt", i)
51
+ export_output("#{dirname}/sourcecode/#{filename}",
52
+ clean_sourcecode(s.dup))
53
+ end
54
+ end
46
55
 
47
- %r{^data:image/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ s["src"]
48
- fn = s["filename"] || sprintf("image-%<num>04d.%<name>s",
49
- num: i, name: imgtype)
50
- export_output("#{dirname}/image/#{fn}", Base64.strict_decode64(imgdata),
51
- binary: true)
52
- end
53
- end
56
+ def export_image(xml, dirname)
57
+ xml.at("//image | //xmlns:image") or return
58
+ FileUtils.mkdir_p "#{dirname}/image"
59
+ xml.xpath("//image | //xmlns:image").each_with_index do |s, i|
60
+ next unless /^data:image/.match? s["src"]
61
+
62
+ %r{^data:image/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ s["src"]
63
+ fn = s["filename"] || sprintf("image-%<num>04d.%<name>s",
64
+ num: i, name: imgtype)
65
+ export_output(
66
+ "#{dirname}/image/#{fn}",
67
+ Base64.strict_decode64(imgdata),
68
+ binary: true,
69
+ )
70
+ end
71
+ end
54
72
 
55
- REQUIREMENT_XPATH =
56
- "//requirement | //xmlns:requirement | //recommendation | "\
57
- "//xmlns:recommendation | //permission | //xmlns:permission".freeze
73
+ REQUIREMENT_XPATH =
74
+ "//requirement | //xmlns:requirement | //recommendation | "\
75
+ "//xmlns:recommendation | //permission | //xmlns:permission"
58
76
 
59
- def requirement_export(xml, dirname)
60
- xml.at(REQUIREMENT_XPATH) or return
61
- FileUtils.mkdir_p "#{dirname}/requirement"
62
- xml.xpath(REQUIREMENT_XPATH).each_with_index do |s, i|
63
- fn = s["filename"] ||
64
- sprintf("%<name>s-%<num>04d.xml", name: s.name, num: i)
65
- export_output("#{dirname}/requirement/#{fn}", s)
77
+ def export_requirement(xml, dirname)
78
+ xml.at(REQUIREMENT_XPATH) or return
79
+ FileUtils.mkdir_p "#{dirname}/requirement"
80
+ xml.xpath(REQUIREMENT_XPATH).each_with_index do |s, i|
81
+ fn = s["filename"] ||
82
+ sprintf("%<name>s-%<num>04d.xml", name: s.name, num: i)
83
+ export_output("#{dirname}/requirement/#{fn}", s)
84
+ end
85
+ end
66
86
  end
67
87
  end
68
88
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ class Compile
5
+ module Flavor
6
+ # Load the flavor gem for the given standard type
7
+ # @param stdtype [Symbol] the standard type
8
+ # @return [void]
9
+ def load_flavor(stdtype)
10
+ stdtype = stdtype.to_sym
11
+ flavor = stdtype2flavor(stdtype)
12
+ @registry.supported_backends.include? stdtype or
13
+ Util.log("[metanorma] Info: Loading `#{flavor}` gem "\
14
+ "for standard type `#{stdtype}`.", :info)
15
+ require_flavor(flavor)
16
+ @registry.supported_backends.include? stdtype or
17
+ Util.log("[metanorma] Error: The `#{flavor}` gem does not "\
18
+ "support the standard type #{stdtype}. Exiting.", :fatal)
19
+ end
20
+
21
+ # Convert the standard type to the flavor gem name
22
+ # @param stdtype [Symbol] the standard type
23
+ # @return [String] the flavor gem name
24
+ def stdtype2flavor(stdtype)
25
+ flavor = STDTYPE2FLAVOR[stdtype] || stdtype
26
+ "metanorma-#{flavor}"
27
+ end
28
+
29
+ private
30
+
31
+ STDTYPE2FLAVOR = {}.freeze
32
+
33
+ def require_flavor(flavor)
34
+ require flavor
35
+ Util.log("[metanorma] Info: gem `#{flavor}` loaded.", :info)
36
+ rescue LoadError => e
37
+ error_log = "#{Date.today}-error.log"
38
+ File.write(error_log, e)
39
+
40
+ msg = <<~MSG
41
+ Error: #{e.message}
42
+ Metanorma has encountered an exception.
43
+
44
+ If this problem persists, please report this issue at the following link:
45
+
46
+ * https://github.com/metanorma/metanorma/issues/new
47
+
48
+ Please attach the #{error_log} file.
49
+ Your valuable feedback is very much appreciated!
50
+
51
+ - The Metanorma team
52
+ MSG
53
+ Util.log(msg, :fatal)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ class Compile
5
+ class OutputFilename
6
+ # Returns an instance of OutputFilename from the source filename
7
+ # @param source_filename [String] the source filename
8
+ # @param output_dir [String, nil] the output directory
9
+ # @param processor [Metanorma::Processor, nil] the processor
10
+ # @return [OutputFilename] the instance of OutputFilename
11
+ def self.from_filename(source_filename, output_dir = nil, processor = nil)
12
+ new(strip_ext(source_filename), output_dir, processor)
13
+ end
14
+
15
+ class << self
16
+ private
17
+
18
+ def strip_ext(filename)
19
+ filename.sub(/\.[^.]*$/, "")
20
+ end
21
+ end
22
+
23
+ # @param noext_filename [String] the path (absolute/relative) of the source file, without extension (e.g., "/a/b/c/test")
24
+ # @param output_dir [String, nil] the output directory
25
+ # @param processor [Metanorma::Processor, nil] the processor
26
+ # @return [OutputFilename] the instance of OutputFilename
27
+ def initialize(noext_filename, output_dir = nil, processor = nil)
28
+ @noext_filename = noext_filename
29
+ @output_dir = output_dir
30
+ @processor = processor
31
+ end
32
+
33
+ # Returns the full file path name with the semantic XML extension
34
+ # @return [String] the full file path name with the semantic XML extension
35
+ def semantic_xml
36
+ with_extension("xml")
37
+ end
38
+
39
+ # Returns the full file path name with the presentation XML extension
40
+ # @return [String] the full file path name with the presentation XML extension
41
+ def presentation_xml
42
+ with_extension("presentation.xml")
43
+ end
44
+
45
+ # Returns the full file path name with the given format extension
46
+ # @param format [Symbol] the format
47
+ # @return [String, nil] the full file path name with the format extension
48
+ def for_format(format)
49
+ ext = @processor&.output_formats&.[](format)
50
+ ext ? with_extension(ext) : nil
51
+ end
52
+
53
+ # Returns the full file path name with the given extension
54
+ # @param ext [String] the extension
55
+ # @return [String] the full file path name with the extension
56
+ def with_extension(ext)
57
+ file = change_output_dir
58
+ "#{file}.#{ext}"
59
+ end
60
+
61
+ private
62
+
63
+ def change_output_dir
64
+ File.expand_path(if !@output_dir.nil?
65
+ File.join(
66
+ @output_dir,
67
+ File.basename(@noext_filename),
68
+ )
69
+ else
70
+ @noext_filename
71
+ end)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ class Compile
5
+ class OutputFilenameConfig
6
+ DEFAULT_TEMPLATE =
7
+ "{{ document.docidentifier | downcase" \
8
+ " | replace: '/' , '-'" \
9
+ " | replace: ' ' , '-' }}"
10
+
11
+ attr_reader :template
12
+
13
+ def initialize(template)
14
+ @template = if template.nil? || template.empty?
15
+ DEFAULT_TEMPLATE
16
+ else
17
+ template
18
+ end
19
+ end
20
+
21
+ def generate_filename(relaton_data)
22
+ template = Liquid::Template.parse(@template)
23
+ template.render("document" => relaton_data)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "liquid"
4
+
5
+ module Metanorma
6
+ class Compile
7
+ class RelatonDrop < Liquid::Drop
8
+ def initialize(relaton_data)
9
+ @relaton = relaton_data
10
+ end
11
+
12
+ def docidentifier
13
+ at("./docidentifier")
14
+ end
15
+
16
+ def title
17
+ at("./title")
18
+ end
19
+
20
+ def date
21
+ at("./date/on")
22
+ end
23
+
24
+ def publisher
25
+ at("./contributor[role/@type = 'publisher']/organization/name")
26
+ end
27
+
28
+ def language
29
+ at("./language")
30
+ end
31
+
32
+ def script
33
+ at("./script")
34
+ end
35
+
36
+ def version
37
+ at("./version")
38
+ end
39
+
40
+ def slugify
41
+ docidentifier&.downcase
42
+ &.gsub(/[^a-z0-9]+/, "-")
43
+ &.gsub(/-+/, "-")
44
+ &.gsub(/^-|-$/, "")
45
+ end
46
+
47
+ private
48
+
49
+ def at(xpath)
50
+ @relaton.at(xpath)&.text
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ class Compile
5
+ module Validator
6
+ def validate_options!(options)
7
+ validate_type!(options)
8
+ validate_format!(options)
9
+ end
10
+
11
+ def validate_type!(options)
12
+ unless options[:type]
13
+ Util.log("[metanorma] Error: Please specify a standard type: "\
14
+ "#{@registry.supported_backends}.", :fatal)
15
+ end
16
+ stdtype = options[:type].to_sym
17
+ load_flavor(stdtype)
18
+ end
19
+
20
+ def validate_format!(options)
21
+ unless options[:format] == :asciidoc
22
+ Util.log("[metanorma] Error: Only source file format currently "\
23
+ "supported is 'asciidoc'.", :fatal)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ class Compile
5
+ module Writeable
6
+ def export_output(fname, content, **options)
7
+ mode = options[:binary] ? "wb" : "w:UTF-8"
8
+ File.open(fname, mode) { |f| f.write content }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module Metanorma
2
- VERSION = "2.1.4".freeze
2
+ VERSION = "2.1.5".freeze
3
3
  end
data/metanorma.gemspec CHANGED
@@ -45,7 +45,7 @@ Gem::Specification.new do |spec|
45
45
  spec.add_development_dependency "rspec", "~> 3.0"
46
46
  spec.add_development_dependency "rspec-command", "~> 1.0"
47
47
  spec.add_development_dependency "rubocop", "~> 1"
48
- spec.add_development_dependency "rubocop-performance"
48
+ spec.add_development_dependency "rubocop-performance"
49
49
  spec.add_development_dependency "sassc-embedded", "~> 1"
50
50
  spec.add_development_dependency "simplecov", "~> 0.15"
51
51
  spec.add_development_dependency "xml-c14n"
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: 2.1.4
4
+ version: 2.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: 2025-03-04 00:00:00.000000000 Z
11
+ date: 2025-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -340,8 +340,13 @@ files:
340
340
  - lib/metanorma/collection/xrefprocess/xrefprocess.rb
341
341
  - lib/metanorma/compile/compile.rb
342
342
  - lib/metanorma/compile/compile_options.rb
343
- - lib/metanorma/compile/compile_validate.rb
344
343
  - lib/metanorma/compile/extract.rb
344
+ - lib/metanorma/compile/flavor.rb
345
+ - lib/metanorma/compile/output_filename.rb
346
+ - lib/metanorma/compile/output_filename_config.rb
347
+ - lib/metanorma/compile/relaton_drop.rb
348
+ - lib/metanorma/compile/validator.rb
349
+ - lib/metanorma/compile/writeable.rb
345
350
  - lib/metanorma/config/config.rb
346
351
  - lib/metanorma/input.rb
347
352
  - lib/metanorma/input/asciidoc.rb
@@ -1,68 +0,0 @@
1
- module Metanorma
2
- class Compile
3
- def validate_options(options)
4
- validate_type(options)
5
- validate_format(options)
6
- end
7
-
8
- def validate_type(options)
9
- unless options[:type]
10
- Util.log("[metanorma] Error: Please specify a standard type: "\
11
- "#{@registry.supported_backends}.", :fatal)
12
- end
13
- stdtype = options[:type].to_sym
14
- load_flavor(stdtype)
15
- end
16
-
17
- def validate_format(options)
18
- unless options[:format] == :asciidoc
19
- Util.log("[metanorma] Error: Only source file format currently "\
20
- "supported is 'asciidoc'.", :fatal)
21
- end
22
- end
23
-
24
- def load_flavor(stdtype)
25
- stdtype = stdtype.to_sym
26
- flavor = stdtype2flavor(stdtype)
27
- @registry.supported_backends.include? stdtype or
28
- Util.log("[metanorma] Info: Loading `#{flavor}` gem "\
29
- "for standard type `#{stdtype}`.", :info)
30
- require_flavor(flavor)
31
- @registry.supported_backends.include? stdtype or
32
- Util.log("[metanorma] Error: The `#{flavor}` gem does not "\
33
- "support the standard type #{stdtype}. Exiting.", :fatal)
34
- end
35
-
36
- def stdtype2flavor(stdtype)
37
- flavor = STDTYPE2FLAVOR[stdtype] || stdtype
38
- "metanorma-#{flavor}"
39
- end
40
-
41
- private
42
-
43
- STDTYPE2FLAVOR = {}.freeze
44
-
45
- def require_flavor(flavor)
46
- require flavor
47
- Util.log("[metanorma] Info: gem `#{flavor}` loaded.", :info)
48
- rescue LoadError => e
49
- error_log = "#{Date.today}-error.log"
50
- File.write(error_log, e)
51
-
52
- msg = <<~MSG
53
- Error: #{e.message}
54
- Metanorma has encountered an exception.
55
-
56
- If this problem persists, please report this issue at the following link:
57
-
58
- * https://github.com/metanorma/metanorma/issues/new
59
-
60
- Please attach the #{error_log} file.
61
- Your valuable feedback is very much appreciated!
62
-
63
- - The Metanorma team
64
- MSG
65
- Util.log(msg, :fatal)
66
- end
67
- end
68
- end