# frozen_string_literal: true require "relaton" require "relaton/cli" require "metanorma/collection_manifest" require "metanorma-utils" require_relative "util" module Metanorma # Metanorma collection of documents class Collection attr_reader :file # @return [Array] documents-inline to inject the XML into # the collection manifest; documents-external to keeps them outside attr_accessor :directives, :documents, :bibdatas, :coverpage, :dirname attr_accessor :disambig, :manifest # @param file [String] path to source file # @param dirname [String] directory of source file # @param directives [Array] documents-inline to inject the XML into # the collection manifest; documents-external to keeps them outside # @param bibdata [RelatonBib::BibliographicItem] # @param manifest [Metanorma::CollectionManifest] # @param documents [Hash] # @param prefatory [String] # @param coverpage [String] # @param final [String] # rubocop:disable Metrics/AbcSize,Metrics/MethodLength def initialize(**args) @file = args[:file] @dirname = File.dirname(@file) @directives = args[:directives] || [] @bibdata = args[:bibdata] @manifest = args[:manifest] @manifest.collection = self @coverpage = Util::hash_key_detect(@directives, "coverpage", @coverpage) @coverpage_style = Util::hash_key_detect(@directives, "coverpage-style", @coverpage_style) @documents = args[:documents] || {} @bibdatas = args[:documents] || {} if (@documents.any? || @manifest) && (%w(documents-inline documents-external) & @directives).empty? @directives << "documents-inline" end @documents.merge! @manifest.documents(@dirname) @bibdatas.merge! @manifest.documents(@dirname) @documents.transform_keys { |k| Util::key(k) } @bibdatas.transform_keys { |k| Util::key(k) } @prefatory = args[:prefatory] @final = args[:final] @compile = Metanorma::Compile.new @log = Metanorma::Utils::Log.new @disambig = Util::DisambigFiles.new end # rubocop:enable Metrics/AbcSize,Metrics/MethodLength def clean_exit @log.write(File.join(@dirname, "#{File.basename(@file, '.*')}.err.html")) end # @return [String] XML def to_xml b = Nokogiri::XML::Builder.new do |xml| xml.send(:"metanorma-collection", "xmlns" => "http://metanorma.org") do |mc| collection_body(mc) end end b.to_xml end def collection_body(coll) coll << @bibdata.to_xml(bibdata: true, date_format: :full) @directives.each do |d| coll << "#{obj_to_xml(d)}" end @manifest.to_xml coll content_to_xml "prefatory", coll doccontainer coll content_to_xml "final", coll end def obj_to_xml(elem) case elem when ::Array elem.each_with_object([]) do |v, m| m << "#{obj_to_xml(v)}" end.join when ::Hash elem.each_with_object([]) do |(k, v), m| m << "<#{k}>#{obj_to_xml(v)}" end.join else elem end end def render(opts) CollectionRenderer.render self, opts.merge(log: @log) clean_exit end class << self # @param file [String] # @return [RelatonBib::BibliographicItem, # RelatonIso::IsoBibliographicItem] def parse(file) case file when /\.xml$/ then parse_xml(file) when /.ya?ml$/ then parse_yaml(file) end end private def parse_xml(file) xml = Nokogiri::XML(File.read(file, encoding: "UTF-8"), &:huge) (b = xml.at("/xmlns:metanorma-collection/xmlns:bibdata")) and bd = Relaton::Cli.parse_xml(b) mnf_xml = xml.at("/xmlns:metanorma-collection/xmlns:manifest") mnf = CollectionManifest.from_xml mnf_xml pref = pref_final_content xml.at("//xmlns:prefatory-content") fnl = pref_final_content xml.at("//xmlns:final-content") cov = pref_final_content xml.at("//xmlns:coverpage") new(file: file, bibdata: bd, manifest: mnf, directives: directives_from_xml(xml.xpath("//xmlns:directives")), documents: docs_from_xml(xml, mnf), bibdatas: docs_from_xml(xml, mnf), prefatory: pref, final: fnl, coverpage: cov) end # TODO refine def directives_from_xml(dir) dir.each_with_object([]) do |d, m| m << if d.at("./xmlns:value") x.xpath("./xmlns:value").map(&:text) elsif d.at("./*") d.elements.each_with_object({}) do |e, ret| ret[e.name] = e.children.to_xml end else d.children.to_xml end end end def parse_yaml(file) yaml = YAML.load_file file if yaml["bibdata"] bd = Relaton::Cli::YAMLConvertor.convert_single_file yaml["bibdata"] end mnf = CollectionManifest.from_yaml yaml["manifest"] dirs = yaml["directives"] pref = yaml["prefatory-content"] fnl = yaml["final-content"] new(file: file, directives: dirs, bibdata: bd, manifest: mnf, prefatory: pref, final: fnl) end # @param xml [Nokogiri::XML::Document] # @parma mnf [Metanorma::CollectionManifest] # @return [Hash{String=>Metanorma::Document}] def docs_from_xml(xml, mnf) xml.xpath("//xmlns:doc-container//xmlns:bibdata") .each_with_object({}) do |b, m| bd = Relaton::Cli.parse_xml b docref = mnf.docref_by_id bd.docidentifier.first.id m[docref["identifier"]] = Document.new bd, docref["fileref"] m end end # @param xml [Nokogiri::XML::Element, nil] # @return [String, nil] def pref_final_content(xml) xml or return <<~CONT == #{xml.at('title')&.text} #{xml.at('p')&.text} CONT end end private # @return [String, nil] attr_reader :prefatory, :final # @return [String] def dummy_header <<~DUMMY = X A DUMMY end # @param elm [String] 'prefatory' or 'final' # @param builder [Nokogiri::XML::Builder] def content_to_xml(elm, builder) return unless (cnt = send(elm)) @compile.load_flavor(doctype) out = sections(dummy_header + cnt.strip) builder.send("#{elm}-content") { |b| b << out } end # @param cnt [String] prefatory/final content # @return [String] XML def sections(cnt) c = Asciidoctor.convert(cnt, backend: doctype.to_sym, header_footer: true) Nokogiri::XML(c, &:huge).at("//xmlns:sections").children.to_xml end # @param builder [Nokogiri::XML::Builder] def doccontainer(builder) Array(@directives).include? "documents-inline" or return documents.each_with_index do |(_, d), i| doccontainer1(builder, d, i) end end def doccontainer1(builder, doc, idx) id = format("doc%09d", index: idx) builder.send(:"doc-container", id: id) do |b| if doc.attachment doc.bibitem and b << doc.bibitem.root.to_xml b.attachment Vectory::Utils::datauri(doc.file) else doc.to_xml b end end end def doctype @doctype ||= fetch_doctype || "standoc" end def fetch_doctype docid = @bibdata.docidentifier.first docid or return docid.type&.downcase || docid.id&.sub(/\s.*$/, "")&.downcase end end end