lib/epubmaker/epubcommon.rb in review-2.0.0.beta1 vs lib/epubmaker/epubcommon.rb in review-2.0.0
- old
+ new
@@ -1,41 +1,52 @@
# encoding: utf-8
# = epubcommon.rb -- super class for EPUBv2 and EPUBv3
#
-# Copyright (c) 2010-2014 Kenshi Muto and Masayoshi Takahashi
+# Copyright (c) 2010-2016 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 'epubmaker/producer'
require 'review/i18n'
+require 'review/template'
require 'cgi'
require 'shellwords'
+begin
+ require 'zip'
+rescue LoadError
+ ## I cannot find rubyzip library, so I use external zip command.
+end
module EPUBMaker
# EPUBCommon is the common class for EPUB producer.
class EPUBCommon
# Construct object with parameter hash +params+ 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.params["bookname"]}.opf"
+ end
+
def opf_coverimage
s = ""
if @producer.params["coverimage"]
file = nil
@producer.contents.each do |item|
- if item.media =~ /\Aimage/ && item.file =~ /#{@producer.params["coverimage"]}\Z/
+ if item.media.start_with?('image') && item.file =~ /#{@producer.params["coverimage"]}\Z/
s << %Q[ <meta name="cover" content="#{item.id}"/>\n]
file = item.file
break
end
end
@@ -43,24 +54,21 @@
end
s
end
def ncx_isbn
- if @producer.params["isbn"].nil?
- %Q[ <meta name="dtb:uid" content="#{@producer.params["urnid"]}"/>\n]
- else
- %Q[ <meta name="dtb:uid" content="#{@producer.params["isbn"]}"/>\n]
- end
+ uid = @producer.params["isbn"] || @producer.params["urnid"]
+ %Q[ <meta name="dtb:uid" content="#{uid}"/>\n]
end
def ncx_doctitle
<<EOT
<docTitle>
<text>#{CGI.escapeHTML(@producer.params["title"])}</text>
</docTitle>
<docAuthor>
- <text>#{@producer.params["aut"].nil? ? "" : CGI.escapeHTML(@producer.params["aut"].join(", "))}</text>
+ <text>#{@producer.params["aut"].nil? ? "" : CGI.escapeHTML(join_with_separator(@producer.params["aut"], ReVIEW::I18n.t("names_splitter")))}</text>
</docAuthor>
EOT
end
def ncx_navmap(indentarray)
@@ -78,11 +86,11 @@
unless @producer.params["mytoc"].nil?
s << <<EOT
<navPoint id="toc" playOrder="#{nav_count}">
<navLabel>
- <text>#{@producer.res.v("toctitle")}</text>
+ <text>#{CGI.escapeHTML(@producer.res.v("toctitle"))}</text>
</navLabel>
<content src="#{@producer.params["bookname"]}-toc.#{@producer.params["htmlext"]}"/>
</navPoint>
EOT
nav_count += 1
@@ -110,204 +118,190 @@
s
end
# Return container content.
def container
- s = <<EOT
-<?xml version="1.0" encoding="UTF-8"?>
-<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
- <rootfiles>
- <rootfile full-path="OEBPS/#{@producer.params["bookname"]}.opf" media-type="application/oebps-package+xml" />
- </rootfiles>
-</container>
-EOT
- s
+ @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)
- bodyext = type.nil? ? "" : " epub:type=\"#{type}\""
+ @body_ext = type.nil? ? "" : " epub:type=\"#{type}\""
- s = common_header
- s << <<EOT
- <title>#{CGI.escapeHTML(@producer.params["title"])}</title>
-</head>
-<body#{bodyext}>
-EOT
- if @producer.params["coverimage"].nil?
- s << <<EOT
-<h1 class="cover-title">#{CGI.escapeHTML(@producer.params["title"])}</h1>
-EOT
- else
- file = nil
- @producer.contents.each do |item|
- if item.media =~ /\Aimage/ && item.file =~ /#{@producer.params["coverimage"]}\Z/ # /
- file = item.file
- break
- end
+ if @producer.params["coverimage"]
+ file = @producer.coverimage
+ if !file
+ raise "coverimage #{@producer.params["coverimage"]} not found. Abort."
end
- raise "coverimage #{@producer.params["coverimage"]} not found. Abort." if file.nil?
- s << <<EOT
+ @body = <<-EOT
<div id="cover-image" class="cover-image">
- <img src="#{file}" alt="#{CGI.escapeHTML(@producer.params["title"])}" class="max"/>
+ <img src="#{file}" alt="#{CGI.escapeHTML(@producer.params.name_of("title"))}" class="max"/>
</div>
-EOT
+ EOT
+ else
+ @body = <<-EOT
+<h1 class="cover-title">#{CGI.escapeHTML(@producer.params.name_of("title"))}</h1>
+ EOT
end
- s << <<EOT
-</body>
-</html>
-EOT
- s
+ @title = CGI.escapeHTML(@producer.params.name_of("title"))
+ @language = @producer.params['language']
+ @stylesheets = @producer.params["stylesheet"]
+ if @producer.params["htmlversion"].to_i == 5
+ tmplfile = File.expand_path('./html/layout-html5.html.erb', ReVIEW::Template::TEMPLATE_DIR)
+ else
+ tmplfile = 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.
def titlepage
- s = common_header
- s << <<EOT
- <title>#{CGI.escapeHTML(@producer.params["title"])}</title>
-</head>
-<body>
- <h1 class="tp-title">#{CGI.escapeHTML(@producer.params["title"])}</h1>
-EOT
+ @title = CGI.escapeHTML(@producer.params.name_of("title"))
+ @body = <<EOT
+ <h1 class="tp-title">#{@title}</h1>
+EOT
if @producer.params["aut"]
- s << <<EOT
+ @body << <<EOT
<p>
<br />
<br />
</p>
- <h2 class="tp-author">#{CGI.escapeHTML(@producer.params["aut"].join(", "))}</h2>
+ <h2 class="tp-author">#{CGI.escapeHTML(join_with_separator(@producer.params.names_of("aut"), ReVIEW::I18n.t("names_splitter")))}</h2>
EOT
end
- publisher = @producer.params["pbl"] || @producer.params["prt"] # XXX Backward Compatiblity
+ publisher = @producer.params.names_of("pbl")
if publisher
- s << <<EOT
+ @body << <<EOT
<p>
<br />
<br />
<br />
<br />
</p>
- <h3 class="tp-publisher">#{CGI.escapeHTML(publisher.join(", "))}</h3>
+ <h3 class="tp-publisher">#{CGI.escapeHTML(join_with_separator(publisher, ReVIEW::I18n.t("names_splitter")))}</h3>
EOT
end
- s << <<EOT
-</body>
-</html>
-EOT
-
- s
+ @language = @producer.params['language']
+ @stylesheets = @producer.params["stylesheet"]
+ if @producer.params["htmlversion"].to_i == 5
+ tmplfile = File.expand_path('./html/layout-html5.html.erb', ReVIEW::Template::TEMPLATE_DIR)
+ else
+ tmplfile = 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
- s = common_header
- s << <<EOT
- <title>#{@producer.res.v("colophontitle")}</title>
-</head>
-<body>
+ @title = CGI.escapeHTML(@producer.res.v("colophontitle"))
+ @body = <<EOT
<div class="colophon">
EOT
if @producer.params["subtitle"].nil?
- s << <<EOT
- <p class="title">#{CGI.escapeHTML(@producer.params["title"])}</p>
+ @body << <<EOT
+ <p class="title">#{CGI.escapeHTML(@producer.params.name_of("title"))}</p>
EOT
else
- s << <<EOT
- <p class="title">#{CGI.escapeHTML(@producer.params["title"])}<br /><span class="subtitle">#{CGI.escapeHTML(@producer.params["subtitle"])}</span></p>
+ @body << <<EOT
+ <p class="title">#{CGI.escapeHTML(@producer.params.name_of("title"))}<br /><span class="subtitle">#{CGI.escapeHTML(@producer.params.name_of("subtitle"))}</span></p>
EOT
end
if @producer.params["date"] || @producer.params["history"]
- s << %Q[ <div class="pubhistory">\n]
- if @producer.params["history"]
- @producer.params["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}")
- revstr = ReVIEW::I18n.t("nth_impression", "#{rev+1}")
- if item =~ /\A\d+\-\d+\-\d+\Z/
- s << %Q[ <p>#{ReVIEW::I18n.t("published_by1", [date_to_s(item), editstr+revstr])}</p>\n]
- else
- # custom date with string
- item.match(/\A(\d+\-\d+\-\d+)[\s ](.+)/) do |m|
- s << %Q[ <p>#{ReVIEW::I18n.t("published_by3", [date_to_s(m[1]), m[2]])}</p>\n]
- end
- end
- end
- end
- else
- s << %Q[ <p>#{ReVIEW::I18n.t("published_by2", date_to_s(@producer.params["date"]))}</p>\n]
- end
- s << %Q[ </div>\n]
+ @body << colophon_history
end
- s << %Q[ <table class="colophon">\n]
- s << %Q[ <tr><th>#{@producer.res.v("aut")}</th><td>#{CGI.escapeHTML(@producer.params["aut"].join(", "))}</td></tr>\n] unless @producer.params["aut"].nil?
- s << %Q[ <tr><th>#{@producer.res.v("csl")}</th><td>#{CGI.escapeHTML(@producer.params["csl"].join(", "))}</td></tr>\n] unless @producer.params["csl"].nil?
- s << %Q[ <tr><th>#{@producer.res.v("trl")}</th><td>#{CGI.escapeHTML(@producer.params["trl"].join(", "))}</td></tr>\n] unless @producer.params["trl"].nil?
- s << %Q[ <tr><th>#{@producer.res.v("dsr")}</th><td>#{CGI.escapeHTML(@producer.params["dsr"].join(", "))}</td></tr>\n] unless @producer.params["dsr"].nil?
- s << %Q[ <tr><th>#{@producer.res.v("ill")}</th><td>#{CGI.escapeHTML(@producer.params["ill"].join(", "))}</td></tr>\n] unless @producer.params["ill"].nil?
- s << %Q[ <tr><th>#{@producer.res.v("edt")}</th><td>#{CGI.escapeHTML(@producer.params["edt"].join(", "))}</td></tr>\n] unless @producer.params["edt"].nil?
- s << %Q[ <tr><th>#{@producer.res.v("pbl")}</th><td>#{CGI.escapeHTML(@producer.params["pbl"].join(", "))}</td></tr>\n] unless @producer.params["pbl"].nil?
- s << %Q[ <tr><th>#{@producer.res.v("prt")}</th><td>#{CGI.escapeHTML(@producer.params["prt"].join(", "))}</td></tr>\n] unless @producer.params["prt"].nil?
- s << %Q[ <tr><th>#{@producer.res.v("pht")}</th><td>#{CGI.escapeHTML(@producer.params["pht"].join(", "))}</td></tr>\n] unless @producer.params["pht"].nil?
- if @producer.params["isbn"].to_s =~ /\A\d{10}\Z/ || @producer.params["isbn"].to_s =~ /\A\d{13}\Z/
- isbn = nil
- str = @producer.params["isbn"].to_s
- if str.size == 10
- isbn = "#{str[0..0]}-#{str[1..5]}-#{str[6..8]}-#{str[9..9]}"
+ @body << %Q[ <table class="colophon">\n]
+ @body << @producer.params["colophon_order"].map{ |role|
+ if @producer.params[role]
+ %Q[ <tr><th>#{CGI.escapeHTML(@producer.res.v(role))}</th><td>#{CGI.escapeHTML(join_with_separator(@producer.params.names_of(role), ReVIEW::I18n.t("names_splitter")))}</td></tr>\n]
else
- isbn = "#{str[0..2]}-#{str[3..3]}-#{str[4..8]}-#{str[9..11]}-#{str[12..12]}"
+ ""
end
- s << %Q[ <tr><th>ISBN</th><td>#{isbn}</td></tr>\n]
+ }.join("")
+
+ if @producer.isbn_hyphen
+ @body << %Q[ <tr><th>ISBN</th><td>#{@producer.isbn_hyphen}</td></tr>\n]
end
- s << <<EOT
- </table>
-EOT
+ @body << %Q[ </table>\n]
if !@producer.params["rights"].nil? && @producer.params["rights"].size > 0
- s << %Q[ <p class="copyright">#{@producer.params["rights"].join("<br />")}</p>]
+ @body << %Q[ <p class="copyright">#{join_with_separator(@producer.params.names_of("rights").map {|m| CGI.escapeHTML(m)}, "<br />")}</p>\n]
end
+ @body << %Q[ </div>\n]
- s << <<EOT
- </div>
-</body>
-</html>
-EOT
- s
+ @language = @producer.params['language']
+ @stylesheets = @producer.params["stylesheet"]
+ if @producer.params["htmlversion"].to_i == 5
+ tmplfile = File.expand_path('./html/layout-html5.html.erb', ReVIEW::Template::TEMPLATE_DIR)
+ else
+ tmplfile = 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[ <div class="pubhistory">\n]
+ if @producer.params["history"]
+ @producer.params["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}")
+ revstr = ReVIEW::I18n.t("nth_impression", "#{rev+1}")
+ if item =~ /\A\d+\-\d+\-\d+\Z/
+ buf << %Q[ <p>#{ReVIEW::I18n.t("published_by1", [date_to_s(item), editstr+revstr])}</p>\n]
+ else
+ # custom date with string
+ item.match(/\A(\d+\-\d+\-\d+)[\s ](.+)/) do |m|
+ buf << %Q[ <p>#{ReVIEW::I18n.t("published_by3", [date_to_s(m[1]), m[2]])}</p>\n]
+ end
+ end
+ end
+ end
+ else
+ buf << %Q[ <p>#{ReVIEW::I18n.t("published_by2", date_to_s(@producer.params["date"]))}</p>\n]
+ end
+ buf << %Q[ </div>\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
- s = common_header
- s << <<EOT
- <title>#{@producer.res.v("toctitle")}</title>
-</head>
-<body>
- <h1 class="toc-title">#{@producer.res.v("toctitle")}</h1>
-EOT
+ @title = CGI.escapeHTML(@producer.res.v("toctitle"))
+ @body = %Q[ <h1 class="toc-title">#{CGI.escapeHTML(@producer.res.v("toctitle"))}</h1>\n]
if @producer.params["epubmaker"]["flattoc"].nil?
- s << hierarchy_ncx("ul")
+ @body << hierarchy_ncx("ul")
else
- s << flat_ncx("ul", @producer.params["epubmaker"]["flattocindent"])
+ @body << flat_ncx("ul", @producer.params["epubmaker"]["flattocindent"])
end
- s << <<EOT
-</body>
-</html>
-EOT
- s
+ @language = @producer.params['language']
+ @stylesheets = @producer.params["stylesheet"]
+ if @producer.params["htmlversion"].to_i == 5
+ tmplfile = File.expand_path('./html/layout-html5.html.erb', ReVIEW::Template::TEMPLATE_DIR)
+ else
+ tmplfile = 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
@@ -392,11 +386,11 @@
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("#{tmpdir}/OEBPS/#{@producer.params["bookname"]}.opf", "w") {|f| @producer.opf(f) }
+ File.open(File.join(tmpdir, opf_path), "w") {|f| @producer.opf(f) }
if File.exist?("#{basedir}/#{@producer.params["cover"]}")
FileUtils.cp("#{basedir}/#{@producer.params["cover"]}", "#{tmpdir}/OEBPS")
else
File.open("#{tmpdir}/OEBPS/#{@producer.params["cover"]}", "w") {|f| @producer.cover(f) }
@@ -410,32 +404,66 @@
FileUtils.cp(fname, "#{tmpdir}/OEBPS/#{item.file}")
end
end
def export_zip(tmpdir, epubfile)
+ if defined?(Zip)
+ export_zip_rubyzip(tmpdir, epubfile)
+ else
+ export_zip_extcmd(tmpdir, epubfile)
+ end
+ end
+
+ def export_zip_extcmd(tmpdir, epubfile)
Dir.chdir(tmpdir) {|d| `#{@producer.params["epubmaker"]["zip_stage1"]} #{epubfile.shellescape} mimetype` }
Dir.chdir(tmpdir) {|d| `#{@producer.params["epubmaker"]["zip_stage2"]} #{epubfile.shellescape} META-INF OEBPS #{@producer.params["epubmaker"]["zip_addpath"]}` }
end
+ def export_zip_rubyzip(tmpdir, epubfile)
+ Dir.chdir(tmpdir) do |d|
+ Zip::OutputStream.open(epubfile) do |epub|
+ root_pathname = Pathname.new(tmpdir)
+ # relpath = Pathname.new(File.join(tmpdir,'mimetype')).relative_path_from(root_pathname)
+ epub.put_next_entry('mimetype', nil, nil, Zip::Entry::STORED)
+ epub << "application/epub+zip"
+
+ export_zip_rubyzip_addpath(epub, File.join(tmpdir,'META-INF'), root_pathname)
+ export_zip_rubyzip_addpath(epub, File.join(tmpdir,'OEBPS'), root_pathname)
+ if @producer.params["zip_addpath"].present?
+ export_zip_rubyzip_addpath(epub, File.join(tmpdir,@producer.params["zip_addpath"]), root_pathname)
+ end
+ end
+ end
+ end
+
+ def export_zip_rubyzip_addpath(epub, dirname, rootdir)
+ Dir[File.join(dirname,'**','**')].each do |path|
+ next if File.directory?(path)
+ relpath = Pathname.new(path).relative_path_from(rootdir)
+ epub.put_next_entry(relpath)
+ epub << File.binread(path)
+ end
+ end
+
def legacy_cover_and_title_file(loadfile, writefile)
- s = common_header
- s << <<EOT
- <title>#{@producer.params["booktitle"]}</title>
-</head>
-<body>
-EOT
+ @title = @producer.params["booktitle"]
+ s = ""
File.open(loadfile) do |f|
f.each_line do |l|
s << l
end
end
- s << <<EOT
-</body>
-</html>
-EOT
File.open(writefile, "w") do |f|
f.puts s
+ end
+ end
+
+ def join_with_separator(value, sep)
+ if value.kind_of? Array
+ value.join(sep)
+ else
+ value
end
end
end
end