# = epubcommon.rb -- super class for EPUBv2 and EPUBv3 # # Copyright (c) 2010-2017 Kenshi Muto and Masayoshi Takahashi # # 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/i18n' require 'review/template' require 'cgi' module EPUBMaker # EPUBCommon is the common class for EPUB producer. class EPUBCommon # Construct object with parameter hash +config+ and message resource hash +res+. def initialize(producer) @body_ext = '' @producer = producer @body_ext = nil end # Return mimetype content. def mimetype 'application/epub+zip' end def opf_path "OEBPS/#{@producer.config['bookname']}.opf" end def opf_coverimage s = '' if @producer.config['coverimage'] file = nil @producer.contents.each do |item| next if !item.media.start_with?('image') || item.file !~ /#{@producer.config["coverimage"]}\Z/ s << %Q( \n) file = item.file break end raise "coverimage #{@producer.config['coverimage']} not found. Abort." if file.nil? end s end def ncx_isbn uid = @producer.config['isbn'] || @producer.config['urnid'] %Q( \n) end def ncx_doctitle < #{CGI.escapeHTML(@producer.config['title'])} #{@producer.config['aut'].nil? ? '' : CGI.escapeHTML(join_with_separator(@producer.config['aut'], ReVIEW::I18n.t('names_splitter')))} EOT end def ncx_navmap(indentarray) s = < #{CGI.escapeHTML(@producer.config['title'])} EOT nav_count = 2 unless @producer.config['mytoc'].nil? s << < #{CGI.escapeHTML(@producer.res.v('toctitle'))} EOT nav_count += 1 end @producer.contents.each do |item| next if item.title.nil? indent = indentarray.nil? ? [''] : indentarray level = item.level.nil? ? 0 : (item.level - 1) level = indent.size - 1 if level >= indent.size s << < #{indent[level]}#{CGI.escapeHTML(item.title)} EOT nav_count += 1 end s << < EOT s end # Return container content. def container @opf_path = opf_path tmplfile = File.expand_path('./xml/container.xml.erb', ReVIEW::Template::TEMPLATE_DIR) tmpl = ReVIEW::Template.load(tmplfile) tmpl.result(binding) end # Return cover content. def cover(type = nil) @body_ext = type.nil? ? '' : %Q( epub:type="#{type}") if @producer.config['coverimage'] file = @producer.coverimage raise "coverimage #{@producer.config['coverimage']} not found. Abort." unless file @body = <<-EOT
#{CGI.escapeHTML(@producer.config.name_of('title'))}
EOT else @body = <<-EOT

#{CGI.escapeHTML(@producer.config.name_of('title'))}

EOT if @producer.config['subtitle'] @body << <<-EOT

#{CGI.escapeHTML(@producer.config.name_of('subtitle'))}

EOT end end @title = CGI.escapeHTML(@producer.config.name_of('title')) @language = @producer.config['language'] @stylesheets = @producer.config['stylesheet'] tmplfile = if @producer.config['htmlversion'].to_i == 5 File.expand_path('./html/layout-html5.html.erb', ReVIEW::Template::TEMPLATE_DIR) else File.expand_path('./html/layout-xhtml1.html.erb', ReVIEW::Template::TEMPLATE_DIR) end tmpl = ReVIEW::Template.load(tmplfile) tmpl.result(binding) end # Return title (copying) content. # NOTE: this method is not used yet. # see lib/review/epubmaker.rb#build_titlepage def titlepage @title = CGI.escapeHTML(@producer.config.name_of('title')) @body = <#{@title} EOT if @producer.config['subtitle'] @body << <#{CGI.escapeHTML(@producer.config.name_of('subtitle'))} EOT end if @producer.config['aut'] @body << <

#{CGI.escapeHTML(join_with_separator(@producer.config.names_of('aut'), ReVIEW::I18n.t('names_splitter')))}

EOT end publisher = @producer.config.names_of('pbl') if publisher @body << <



#{CGI.escapeHTML(join_with_separator(publisher, ReVIEW::I18n.t('names_splitter')))}

EOT end @language = @producer.config['language'] @stylesheets = @producer.config['stylesheet'] tmplfile = if @producer.config['htmlversion'].to_i == 5 File.expand_path('./html/layout-html5.html.erb', ReVIEW::Template::TEMPLATE_DIR) else File.expand_path('./html/layout-xhtml1.html.erb', ReVIEW::Template::TEMPLATE_DIR) end tmpl = ReVIEW::Template.load(tmplfile) tmpl.result(binding) end # Return colophon content. def colophon @title = CGI.escapeHTML(@producer.res.v('colophontitle')) @body = < EOT if @producer.config['subtitle'].nil? @body << <#{CGI.escapeHTML(@producer.config.name_of('title'))}

EOT else @body << <#{CGI.escapeHTML(@producer.config.name_of('title'))}
#{CGI.escapeHTML(@producer.config.name_of('subtitle'))}

EOT end @body << colophon_history if @producer.config['date'] || @producer.config['history'] @body << %Q( \n) @body << @producer.config['colophon_order'].map do |role| if @producer.config[role] %Q( \n) else '' end end.join @body << %Q( \n) if @producer.isbn_hyphen @body << %Q(
#{CGI.escapeHTML(@producer.res.v(role))}#{CGI.escapeHTML(join_with_separator(@producer.config.names_of(role), ReVIEW::I18n.t('names_splitter')))}
ISBN#{@producer.isbn_hyphen}
\n) @body << %Q( \n) if @producer.config['rights'] && !@producer.config['rights'].empty? @body << %Q( \n) @language = @producer.config['language'] @stylesheets = @producer.config['stylesheet'] tmplfile = if @producer.config['htmlversion'].to_i == 5 File.expand_path('./html/layout-html5.html.erb', ReVIEW::Template::TEMPLATE_DIR) else File.expand_path('./html/layout-xhtml1.html.erb', ReVIEW::Template::TEMPLATE_DIR) end tmpl = ReVIEW::Template.load(tmplfile) tmpl.result(binding) end def colophon_history buf = '' buf << %Q(
\n) if @producer.config['history'] @producer.config['history'].each_with_index do |items, edit| items.each_with_index do |item, rev| editstr = edit == 0 ? ReVIEW::I18n.t('first_edition') : ReVIEW::I18n.t('nth_edition', (edit + 1).to_s) revstr = ReVIEW::I18n.t('nth_impression', (rev + 1).to_s) if item =~ /\A\d+\-\d+\-\d+\Z/ buf << %Q(

#{ReVIEW::I18n.t('published_by1', [date_to_s(item), editstr + revstr])}

\n) elsif item =~ /\A(\d+\-\d+\-\d+)[\s ](.+)/ # custom date with string item.match(/\A(\d+\-\d+\-\d+)[\s ](.+)/) do |m| buf << %Q(

#{ReVIEW::I18n.t('published_by3', [date_to_s(m[1]), m[2]])}

\n) end else # free format buf << %Q(

#{item}

\n) end end end else buf << %Q(

#{ReVIEW::I18n.t('published_by2', date_to_s(@producer.config['date']))}

\n) end buf << %Q(
\n) buf end def date_to_s(date) require 'date' d = Date.parse(date) d.strftime(ReVIEW::I18n.t('date_format')) end # Return own toc content. def mytoc @title = CGI.escapeHTML(@producer.res.v('toctitle')) @body = %Q(

#{CGI.escapeHTML(@producer.res.v('toctitle'))}

\n) if @producer.config['epubmaker']['flattoc'].nil? @body << hierarchy_ncx('ul') else @body << flat_ncx('ul', @producer.config['epubmaker']['flattocindent']) end @language = @producer.config['language'] @stylesheets = @producer.config['stylesheet'] tmplfile = if @producer.config['htmlversion'].to_i == 5 File.expand_path('./html/layout-html5.html.erb', ReVIEW::Template::TEMPLATE_DIR) else File.expand_path('./html/layout-xhtml1.html.erb', ReVIEW::Template::TEMPLATE_DIR) end tmpl = ReVIEW::Template.load(tmplfile) tmpl.result(binding) end def hierarchy_ncx(type) require 'rexml/document' level = 1 find_jump = nil has_part = nil toclevel = @producer.config['toclevel'].to_i # check part existance @producer.contents.each do |item| next if item.notoc || item.chaptype != 'part' has_part = true break end if has_part @producer.contents.each do |item| item.level += 1 if item.chaptype == 'part' || item.chaptype == 'body' item.notoc = true if (item.chaptype == 'pre' || item.chaptype == 'post') && !item.level.nil? && (item.level + 1 == toclevel) # FIXME: part processing end toclevel += 1 end doc = REXML::Document.new(%Q(<#{type} class="toc-h#{level}">
  • )) doc.context[:attribute_quote] = :quote e = doc.root.elements[1] # first
  • @producer.contents.each do |item| next if !item.notoc.nil? || item.level.nil? || item.file.nil? || item.title.nil? || item.level > toclevel if item.level == level e2 = e.parent.add_element('li') e = e2 elsif item.level > level find_jump = true if (item.level - level) > 1 # deeper (level + 1).upto(item.level) do |n| if e.size == 0 # empty span for epubcheck e.attributes['style'] = 'list-style-type: none;' es = e.add_element('span', 'style' => 'display:none;') es.add_text(REXML::Text.new(' ', false, nil, true)) end e2 = e.add_element(type, 'class' => "toc-h#{n}") e3 = e2.add_element('li') e = e3 end level = item.level elsif item.level < level # shallower (level - 1).downto(item.level) { e = e.parent.parent } e2 = e.parent.add_element('li') e = e2 level = item.level end e2 = e.add_element('a', 'href' => item.file) e2.add_text(REXML::Text.new(item.title, true)) end warn %Q(found level jumping in table of contents. consider to use 'epubmaker:flattoc: true' for strict ePUB validator.) unless find_jump.nil? doc.to_s.gsub('
  • ', '').gsub('
  • ', "\n").gsub("<#{type} ", "\n" + '\&') # ugly end def flat_ncx(type, indent = nil) s = %Q(<#{type} class="toc-h1">\n) @producer.contents.each do |item| next if !item.notoc.nil? || item.level.nil? || item.file.nil? || item.title.nil? || item.level > @producer.config['toclevel'].to_i is = indent == true ? ' ' * item.level : '' s << %Q(
  • #{is}#{CGI.escapeHTML(item.title)}
  • \n) end s << %Q(\n) s end def produce_write_common(basedir, tmpdir) File.open("#{tmpdir}/mimetype", 'w') { |f| @producer.mimetype(f) } FileUtils.mkdir_p("#{tmpdir}/META-INF") File.open("#{tmpdir}/META-INF/container.xml", 'w') { |f| @producer.container(f) } FileUtils.mkdir_p("#{tmpdir}/OEBPS") File.open(File.join(tmpdir, opf_path), 'w') { |f| @producer.opf(f) } if File.exist?("#{basedir}/#{@producer.config['cover']}") FileUtils.cp("#{basedir}/#{@producer.config['cover']}", "#{tmpdir}/OEBPS") else File.open("#{tmpdir}/OEBPS/#{@producer.config['cover']}", 'w') { |f| @producer.cover(f) } end @producer.contents.each do |item| next if item.file =~ /#/ # skip subgroup fname = "#{basedir}/#{item.file}" raise "#{fname} doesn't exist. Abort." unless File.exist?(fname) FileUtils.mkdir_p(File.dirname("#{tmpdir}/OEBPS/#{item.file}")) FileUtils.cp(fname, "#{tmpdir}/OEBPS/#{item.file}") end end def legacy_cover_and_title_file(loadfile, writefile) @title = @producer.config['booktitle'] s = '' File.open(loadfile) do |f| f.each_line do |l| s << l end end File.open(writefile, 'w') do |f| f.puts s end end def join_with_separator(value, sep) if value.is_a?(Array) value.join(sep) else value end end end end