#!/usr/bin/env ruby
# encoding: utf-8
#
# Copyright (c) 2010-2014 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 'tmpdir'
require 'fileutils'
require 'yaml'
require 'optparse'
require 'rexml/document'
require 'time'
require 'pathname'
bindir = Pathname.new(__FILE__).realpath.dirname
$LOAD_PATH.unshift((bindir + '../lib').realpath)
require 'uuid'
require 'review'
require 'review/i18n'
require 'review/htmlutils'
include ReVIEW::HTMLUtils
$essential_files = ['top', 'toc', 'colophon']
def main
opts = OptionParser.new
opts.version = ReVIEW::VERSION
opts.banner = "Usage: #{File.basename($0)} [options] configfile"
opts.on('-h', '--help', 'print this message and quit.') {
puts opts.help
exit 0
}
opts.on('--[no-]debug', 'Keep temporary files.') do |debug|
config["debug"] = debug
end
begin
opts.parse!
rescue OptionParser::ParseError => err
$stderr.puts err.message
$stderr.puts opts.help
exit 1
end
if ARGV.empty?
$stderr.puts opts.help
exit 1
end
yamlfile = ARGV[0]
values = ReVIEW::Configure.values.merge(YAML.load_file(yamlfile))
bookname = values["bookname"]
$essential_files <<= bookname
tmp = values["debug"].nil? ? Dir.mktmpdir : "."
@bookdir = "#{tmp}/#{bookname}-epub"
@epubversion = values["epubversion"] || 2
@htmlversion = values["htmlversion"] || 4
if @epubversion == 3
## if epubversion is 3, htmlversion should be 5
@htmlversion = 5
end
if File.exist?("#{bookname}.epub")
STDERR.puts "#{bookname}.epub exists. Please remove or rename first."
exit 1
end
if File.exist?(@bookdir)
STDERR.puts "#{@bookdir} directory exists. Please remove or rename first."
exit 1
end
@identifier = values["urnid"].nil? ? "urn:uuid:#{UUID.create}" : values["urnid"]
Dir.mkdir(@bookdir)
# MIME type
File.open("#{@bookdir}/mimetype", "w") {|f|
f.write "application/epub+zip"
}
Dir.mkdir("#{@bookdir}/OEBPS")
# XHTML
@manifeststr = ""
@ncxstr = ""
@tocdesc = Array.new
basedir = Dir.pwd
base_path = Pathname.new(basedir)
ReVIEW::Book.load(basedir).parts.each do |part|
if part.name.present?
if part.file?
filename = (base_path + part.path).to_s
output_chaps_by_file(filename, values)
htmlfile = File.basename(filename.chomp.strip,".*")+".html"
else
htmlfile = "part_#{part.number}.html"
make_part_page(part, htmlfile, values)
file_id = "part_#{part.number}"
part_name = "#{ReVIEW::I18n.t("part", part.number)} #{part.name.strip}"
@tocdesc << [1, htmlfile, nil, part_name]
@manifeststr << %Q( \n)
@ncxstr << %Q(\n)
end
end
part.chapters.each do |chap|
filename = Pathname.new(chap.path).relative_path_from(base_path).to_s
output_chaps_by_file(filename, values)
end
end
# images
if File.exist?("images")
allow_exts = values["image_ext"] || %w(png gif jpg jpeg svg ttf woff otf)
Dir.mkdir("#{@bookdir}/OEBPS/images")
image_files = ReVIEW::MakerHelper.copy_images_to_dir("images", "#{@bookdir}/OEBPS/images", :exts=>allow_exts)
image_files.each do |image_file|
dirname = File.dirname(image_file)
fname = File.basename(image_file)
figid = getFigId(dirname.gsub(%r|/|,'-')+"-"+fname)
mime = nil
fname_pattern = /\.(#{allow_exts.join("|")})$/i
next unless fname =~ fname_pattern
case fname.downcase.match(fname_pattern)[1]
when "png"
mime = "image/png"
when "gif"
mime = "image/gif"
when "jpg", "jpeg"
mime = "image/jpeg"
when "svg"
mime = "image/svg+xml"
when "ttf", "otf"
mime = "application/vnd.ms-opentype"
when "woff"
mime = "application/font-woff"
else
raise "unsupported type #{fname}"
end
if @epubversion == 3 && File.join("images",values["coverimage"]) == image_file
properties = ' properties="cover-image"'
end
@manifeststr << %Q( \n)
end
end
# fonts
fontdir = values["fontdir"] || "fonts"
if File.exist?(fontdir)
font_ext = values["font_ext"]
Dir.mkdir("#{@bookdir}/OEBPS/#{fontdir}")
font_files = ReVIEW::MakerHelper.copy_images_to_dir(fontdir, "#{@bookdir}/OEBPS/#{fontdir}", :exts=>font_ext)
font_files.each do |font_file|
dirname = File.dirname(font_file)
fname = File.basename(font_file)
font_id = "font-"+fname
mime = nil
fname_pattern = /\.(#{font_ext.join("|")})$/i
next unless fname =~ fname_pattern
case fname.downcase.match(fname_pattern)[1]
when "svg"
mime = "image/svg+xml"
when "ttf", "otf"
mime = "application/vnd.ms-opentype"
when "woff"
mime = "application/font-woff"
else
raise "unsupported type #{fname}"
end
@manifeststr << %Q( \n)
end
end
# container
Dir.mkdir("#{@bookdir}/META-INF")
File.open("#{@bookdir}/META-INF/container.xml", "w") {|f|
f.puts <
EOT
}
# opf (meta info)
if @epubversion == 3
make_opf_filev3(values, bookname)
else
make_opf_file(values, bookname)
end
# ncx (toc)
if values["toc"]
File.open("#{@bookdir}/OEBPS/#{bookname}.ncx", "w") {|f|
f.puts <
#{values["booktitle"]}
#{values["aut"].nil? ? "" : values["aut"]}
#{values["booktitle"]}
EOT
nav_count = 2
if values["mytoc"]
f.puts <
目次
EOT
nav_count = 3
end
@tocdesc.each {|item|
level, file, id, content = item
# values["level"]
next if level > values["toclevel"].to_i
indent = ""
if level > values["secnolevel"].to_i
indent = "- "
end
fragment_id = (id ? '#'+id : '')
f.puts <
#{indent}#{strip_html(content)}
EOT
nav_count += 1
}
f.puts <
EOT
}
end
# Cover page
File.open("#{@bookdir}/OEBPS/#{bookname}.html", "w") {|f|
f.puts <
EOT
doctype = make_doctype()
f.puts doctype
f.puts <
EOT
if @htmlversion == 4
f.puts <
EOT
else
f.puts <
EOT
end
f.puts <#{values["booktitle"]}
EOT
if !values["coverfile"].nil? && File.exist?(values["coverfile"])
File.open(values["coverfile"]) {|f2|
f2.each_line {|l|
f.puts l
}
}
else
f.puts <#{values["booktitle"]}
EOT
end
f.puts <
EOT
}
if values["backcoverfile"]
make_backcover_image(bookname, values)
end
# Title page
File.open("#{@bookdir}/OEBPS/top.html", "w") {|f|
f.puts <
EOT
doctype = make_doctype()
f.puts doctype
f.puts <
EOT
if @htmlversion == 4
f.puts <
EOT
else
f.puts <
EOT
end
f.puts <#{values["booktitle"]}
EOT
if !values["titlepagefile"].nil? && File.exist?(values["titlepagefile"])
File.open(values["titlepagefile"]) {|f2|
f2.each_line {|l|
f.puts l
}
}
else
f.puts <#{values["booktitle"]}
EOT
if values["aut"]
f.puts <
#{join_names(values["aut"])}
EOT
end
if values["prt"]
f.puts <
#{values["prt"]}
EOT
end
end
f.puts <
EOT
}
# Additional toc page
if values["toc"] && values["mytoc"]
File.open("#{@bookdir}/OEBPS/toc.html", "w") {|f|
if @epubversion == 3
listelm = "ol"
else
listelm = "ul"
end
f.puts <
EOT
doctype = make_doctype()
f.puts doctype
f.puts <
EOT
if @htmlversion == 4
f.puts <
EOT
else
f.puts <
EOT
end
f.puts <目次
EOT
f.puts %q() if @epubversion == 3
f.puts <
EOT
}
end
# stylesheet
if File.exist?(values["stylesheet"])
FileUtils.cp values["stylesheet"], "#{@bookdir}/OEBPS/#{values["stylesheet"]}"
else
File.open("#{@bookdir}/OEBPS/#{values["stylesheet"]}", "w") {|f|
f.puts <)
else
%q()
end
end
def make_opf_file(values, bookname)
File.open("#{@bookdir}/OEBPS/#{bookname}.opf", "w") {|f|
f.puts <
#{values["booktitle"]}
EOT
f.puts %Q(#{values["aut"]}) unless values["aut"].nil? # FIXME: support multiple members
f.puts %Q(#{values["prt"]}) unless values["prt"].nil?
f.puts %Q(#{values["date"]}) unless values["date"].nil?
f.puts %Q(#{values["rights"]}) unless values["rights"].nil?
f.puts %Q(#{values["asn"]}) unless values["asn"].nil?
f.puts %Q(#{values["ant"]}) unless values["ant"].nil?
f.puts %Q(#{values["clb"]}) unless values["clb"].nil?
f.puts %Q(#{values["edt"]}) unless values["edt"].nil?
f.puts %Q(#{values["dsr"]}) unless values["dsr"].nil?
f.puts %Q(#{values["ill"]}) unless values["ill"].nil?
f.puts %Q(#{values["pht"]}) unless values["pht"].nil?
f.puts %Q(#{values["trl"]}) unless values["trl"].nil?
f.puts %Q(#{values["description"]}) unless values["description"].nil?
if values["coverimage"]
f.puts %Q()
end
f.puts <ja
#{@identifier}
EOT
if values["toc"]
f.puts <
EOT
end
f.puts <
EOT
if values["toc"] && values["mytoc"]
f.puts <
EOT
end
f.puts @manifeststr
if values["colophon"]
f.puts <
EOT
end
if values["backcoverfile"]
f.puts <
EOT
end
if values["cover_linear"] && values["cover_linear"] != "no"
cover_linear = "yes"
else
cover_linear = "no"
end
f.puts <
EOT
if values["toc"] && values["mytoc"]
f.puts <
EOT
end
f.puts @ncxstr
if values["colophon"]
f.puts <
EOT
end
if values["backcoverfile"]
f.puts <
EOT
end
f.puts <
EOT
if values["titlepage"]
f.puts <
EOT
end
if values["toc"] && values["mytoc"]
f.puts <
EOT
end
f.puts <
EOT
}
end
def make_opf_filev3(values, bookname)
File.open("#{@bookdir}/OEBPS/#{bookname}.opf", "w") {|f|
f.puts <
#{values["booktitle"]}
EOT
unless values["aut"].nil? # FIXME: support multiple members
f.puts %Q(#{values["aut"]})
f.puts %Q(aut)
end
f.puts %Q(#{values["prt"]}) unless values["prt"].nil?
f.puts %Q(#{values["date"]}) unless values["date"].nil?
f.puts %Q(#{values["rights"]}) unless values["rights"].nil?
%w(asn ant clb edt dsr ill pht trl).each do |attr|
unless values[attr].nil?
f.puts %Q(#{values[attr]})
f.puts %Q(#{attr})
end
end
f.puts %Q(#{values["description"]}) unless values["description"].nil?
f.puts %Q(#{Time.now.utc.iso8601(0)})
if values["coverimage"]
f.puts %Q()
end
f.puts <ja
#{@identifier}
EOT
if values["toc"]
f.puts <
EOT
end
f.puts <
EOT
if values["toc"] && values["mytoc"]
f.puts <
EOT
end
f.puts @manifeststr
if values["colophon"]
f.puts <
EOT
end
if values["backcoverfile"]
f.puts <
EOT
end
if values["cover_linear"] && values["cover_linear"] != "no"
cover_linear = "yes"
else
cover_linear = "no"
end
f.puts <
EOT
if values["toc"] && values["mytoc"]
f.puts <
EOT
end
f.puts @ncxstr
if values["colophon"]
f.puts <
EOT
end
if values["backcoverfile"]
f.puts <
EOT
end
f.puts <
EOT
if values["titlepage"]
f.puts <
EOT
end
if values["toc"] && values["mytoc"]
f.puts <
EOT
end
f.puts <
EOT
}
end
def output_chaps(chapsfile, values)
File.open(chapsfile) {|chaps|
chaps.each_line {|l|
next if l =~ /^#/
output_chaps_by_file(l, values)
}
}
end
def output_chaps_by_file(l, values)
file_id = File.basename(l.chomp.strip,".*")
if (idx = $essential_files.index(file_id))
if idx == $essential_files.size - 1
STDERR.puts "#{file_id} is book name. Please rename #{l.chomp.strip}."
else
STDERR.puts "#{file_id} is special name. Please rename #{l.chomp.strip}."
end
exit 1
end
filename = "#{file_id}.html"
system("#{ReVIEW::MakerHelper.bindir}/review-compile --target=html --level=#{values["secnolevel"]} --htmlversion=#{values["htmlversion"]} --epubversion=#{values["epubversion"]} #{values["params"]} #{l} > #{@bookdir}/OEBPS/#{filename}")
getanchors("#{@bookdir}/OEBPS/#{filename}")
if @epubversion == 3 && include_mathml?("#{@bookdir}/OEBPS/#{filename}")
@manifeststr << %Q( \n)
else
@manifeststr << %Q( \n)
end
@ncxstr << %Q(\n)
end
def include_mathml?(filename)
File.open(filename) {|f|
REXML::Document.new(f).each_element("//math"){
return true
}
return false
}
rescue
false
end
def getFigId(filename)
figid = filename.sub(/\.(png|gif|jpg|jpeg|svg)$/, '')
"fig-#{figid}"
end
def getTitle(filename)
File.open(filename) {|f|
return REXML::Document.new(f).elements["//html/head/title"].text
}
end
def getanchors(filename)
File.open(filename) {|f|
file = filename.sub(/.+\//, '')
f.each_line {|l|
if l =~ /\A]*><\/a>(.+?)<\/h/
# level, ID, content
@tocdesc << [$1.to_i, file, $2, $3]
end
}
}
end
def join_names(param)
if param.respond_to?(:join)
param.join(ReVIEW::I18n.t("names_splitter"))
else
param
end
end
def make_colophon_page(tmp,bookname,values)
header = <
EOT
header += make_doctype()
header += <
EOT
if @htmlversion == 4
header += <
EOT
else
header += <
EOT
end
header += <#{values["booktitle"]}
EOT
footer = <
EOT
colophon_path = "#{@bookdir}/OEBPS/colophon.html"
colophon = values["colophon"]
if colophon.kind_of?(String) && File.exist?(colophon)
File.open(colophon_path, "w") {|f|
f.puts header
File.open(values["colophon"]) {|f2|
f2.each_line {|l|
f.puts l
}
}
f.puts footer
}
else
File.open(colophon_path, "w") {|f|
f.puts header
f.puts <
#{values["booktitle"]}
EOT
if values["pubhistory"]
f.puts %Q[\n
#{values["pubhistory"].gsub(/\n/,"
")}
\n
]
end
f.puts <
EOT
f.puts %Q[\n 著 者 | #{join_names(values["aut"]) + ReVIEW::I18n.t("author_postfix")} | \n
] if values["aut"]
f.puts %Q[\n 監 修 | #{join_names(values["csl"]) + ReVIEW::I18n.t("supervisor_postfix")} | \n
] if values["csl"]
f.puts %Q[\n 翻 訳 | #{join_names(values["trl"]) + ReVIEW::I18n.t("translator_postfix")} | \n
] if values["trl"]
f.puts %Q[\n デザイン | #{join_names(values["dsr"])} | \n
] if values["dsr"]
f.puts %Q[\n イラスト | #{join_names(values["ill"])} | \n
] if values["ill"]
f.puts %Q[\n 表 紙 | #{join_names(values["cov"])} | \n
] if values["cov"]
f.puts %Q[\n 編 集 | #{join_names(values["edt"])} | \n
] if values["edt"]
f.puts %Q[\n 発行所 | #{values["prt"]} | \n
] if values["prt"]
f.puts <
EOT
if values["rights"]
f.puts %Q[#{values["rights"]}
]
end
f.puts ""
f.puts footer
}
end
end
def make_part_page(part, filename, values)
File.open("#{@bookdir}/OEBPS/#{filename}", "w") {|f|
f.puts <<-EOT
EOT
doctype = make_doctype()
f.puts doctype
if @htmlversion == 4
header = <<-EOT
#{values["booktitle"]}
EOT
else
header = <<-EOT
#{values["booktitle"]}
EOT
end
f.puts header
f.puts <<-EOT
#{ReVIEW::I18n.t("part", part.number)}
EOT
if part.name.strip.present?
f.puts <<-EOT
#{part.name.strip}
EOT
end
f.puts <<-EOT
EOT
}
end
def make_backcover_image(bookname, values)
backcoverfile = values["backcoverfile"]
File.open("#{@bookdir}/OEBPS/#{backcoverfile}", "w") {|f|
f.puts <
EOT
doctype = make_doctype()
f.puts doctype
f.puts <
EOT
if @htmlversion == 4
f.puts <
EOT
else
f.puts <
EOT
end
f.puts <#{values["booktitle"]}
EOT
if File.exist?(backcoverfile)
File.open(backcoverfile) {|f2|
f2.each_line {|l|
f.puts l
}
}
else
f.puts <
EOT
end
f.puts <
EOT
}
end
main