# = epubv3.rb -- EPUB version 3 producer. # # Copyright (c) 2010-2023 Kenshi Muto # # This program is free software. # You can distribute or modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # For details of the GNU LGPL, see the file "COPYING". # require 'review/epubmaker/epubcommon' require 'review/epubmaker/zip_exporter' require 'review/call_hook' module ReVIEW class EPUBMaker # EPUBv3 is EPUB version 3 producer. class EPUBv3 < EPUBCommon include ReVIEW::CallHook DC_ITEMS = %w[title language date type format source description relation coverage subject rights] CREATOR_ATTRIBUTES = %w[a-adp a-ann a-arr a-art a-asn a-aqt a-aft a-aui a-ant a-bkp a-clb a-cmm a-csl a-dsr a-edt a-ill a-lyr a-mdc a-mus a-nrt a-oth a-pht a-prt a-red a-rev a-spn a-ths a-trc a-trl aut] CONTRIBUTER_ATTRIBUTES = %w[adp ann arr art asn aqt aft aui ant bkp clb cmm csl dsr edt ill lyr mdc mus nrt oth pbd pbl pht prt red rev spn ths trc trl] # Construct object with parameter hash +config+ and message resource hash +res+. def initialize(producer) super @opf_prefix = {} if config['opf_prefix'].present? config['opf_prefix'].each { |k, v| @opf_prefix[k] = v } end end # Return opf file content. def opf @opf_metainfo = opf_metainfo @opf_coverimage = opf_coverimage @opf_manifest = opf_manifest @opf_toc = opf_tocx @package_attrs = '' if @opf_prefix && @opf_prefix.size > 0 prefixes_str = @opf_prefix.map { |k, v| %Q(#{k}: #{v}) }.join(' ') @package_attrs << %Q( prefix="#{prefixes_str}") end ReVIEW::Template.generate(path: './opf/epubv3.opf.erb', binding: binding) end def opf_metainfo @dc_items = opf_dc_items # creator (should be array) @creators = opf_creators # contributor (should be array) @contributers = opf_contributers ReVIEW::Template.generate(path: './opf/opf_metainfo_epubv3.opf.erb', binding: binding) end def opf_dc_items DC_ITEMS.map do |item| next unless config[item] case config[item] when Array config[item].map.with_index do |v, i| if v.is_a?(Hash) { tag: "dc:#{item}", id: "#{item}-#{i}", val: v['name'], refines: v.map { |name, val| { name: name, val: val } }.delete_if { |h| h[:name] == 'name' } } else { tag: "dc:#{item}", id: "#{item}-#{i}", val: v.to_s, refines: [] } end end when Hash { tag: "dc:#{item}", id: item.to_s, val: config[item]['name'], refines: config[item].map { |name, val| { name: name, val: val } }.delete_if { |h| h[:name] == 'name' } } else { tag: "dc:#{item}", id: item.to_s, val: config[item].to_s, refines: [] } end end.flatten.compact end def opf_creators CREATOR_ATTRIBUTES.map do |role| next unless config[role] config[role].map.with_index do |v, i| case v when Hash refines = v.map { |name, val| { id: "#{role.sub('a-', '')}-#{i}", property: name.to_s, scheme: nil, val: val } }.delete_if { |h| h[:property] == 'name' } { id: "#{role}-#{i}", val: v['name'], refines: [ { id: "#{role}-#{i}", property: 'role', scheme: 'marc:relators', val: role.sub('a-', '') } ].concat(refines) } else { id: "#{role}-#{i}", val: v, refines: [ { id: "#{role}-#{i}", property: 'role', scheme: 'marc:relators', val: role.sub('a-', '') } ] } end end end.flatten.compact end def opf_contributers CONTRIBUTER_ATTRIBUTES.map do |role| next unless config[role] config[role].map.with_index do |v, i| case v when Hash refines = v.map { |name, val| { id: "#{role}-#{i}", property: name, scheme: nil, val: val } }.delete_if { |h| h[:property] == 'name' } contributer = { id: "#{role}-#{i}", val: v['name'], refines: [ { id: "#{role}-#{i}", property: 'role', scheme: 'marc:relators', val: role } ].concat(refines) } else contributer = { id: "#{role}-#{i}", val: v, refines: [ { id: "#{role}-#{i}", property: 'role', scheme: 'marc:relators', val: role } ] } end if %w[prt pbl].include?(role) contributer[:pub_id] = "pub-#{role}-#{i}" case v when Hash contributer[:pub_val] = v['name'] pub_refines = v.map { |name, val| { id: "pub-#{role}-#{i}", property: name, scheme: nil, val: val } }.delete_if { |h| h[:property] == 'name' } contributer[:pub_refines] = [ { id: "pub-#{role}-#{i}", property: 'role', scheme: 'marc:relators', val: role } ].concat(pub_refines) else contributer[:pub_val] = v contributer[:pub_refines] = [ { id: "pub-#{role}-#{i}", property: 'role', scheme: 'marc:relators', val: 'prt' } ] end end contributer end end.flatten.compact end def opf_manifest if config['coverimage'] @coverimage = contents.find { |content| content.coverimage?(config['coverimage']) } # @coverimage can be nil end @items = if @coverimage contents.find_all { |content| content.file !~ /#/ && content.id != @coverimage.id } # skip subgroup, or @coverimage else contents.find_all { |content| content.file !~ /#/ } end ReVIEW::Template.generate(path: './opf/opf_manifest_epubv3.opf.erb', binding: binding) end def opf_tocx @cover_linear = if config['epubmaker']['cover_linear'] && config['epubmaker']['cover_linear'] != 'no' 'yes' else 'no' end @tocx_contents = [] toc = nil contents.each do |item| next unless /xhtml\+xml/.match?(item.media) # skip non XHTML @tocx_contents << item end ReVIEW::Template.generate(path: './opf/opf_tocx_epubv3.opf.erb', binding: binding) end def ncx(indentarray) ncx_main = if config['epubmaker']['flattoc'].nil? hierarchy_ncx('ol') else flat_ncx('ol', config['epubmaker']['flattocindent']) end @body = <<-EOT EOT @title = h(ReVIEW::I18n.t('toctitle')) @language = config['language'] @stylesheets = config['stylesheet'] ReVIEW::Template.generate(path: template_name, binding: binding) end def coveritem if config['cover'] [Content.new(file: config['cover'], title: ReVIEW::I18n.t('covertitle'), level: 1, chaptype: 'cover')] else [] end end # Produce EPUB file +epubfile+. # +work_dir+ points the directory has contents. # +tmpdir+ defines temporary directory. def produce(epubfile, work_dir, tmpdir, base_dir:) @workdir = base_dir produce_write_common(work_dir, tmpdir) toc_file = "#{tmpdir}/OEBPS/#{config['bookname']}-toc.#{config['htmlext']}" File.write(toc_file, ncx(config['epubmaker']['ncxindent'])) call_hook('hook_prepack', tmpdir, base_dir: @workdir) expoter = ReVIEW::EPUBMaker::ZipExporter.new(tmpdir, config) expoter.export_zip(epubfile) end end end end