lib/epubmaker/epubv2.rb in review-1.4.0 vs lib/epubmaker/epubv2.rb in review-1.5.0

- old
+ new

@@ -1,33 +1,28 @@ # encoding: utf-8 # = epubv2.rb -- EPUB version 2 producer. # -# Copyright (c) 2010-2014 Kenshi Muto and Masayoshi Takahashi +# Copyright (c) 2010-2015 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 'epubmaker/epubcommon' require 'cgi' module EPUBMaker # EPUBv2 is EPUB version 2 producer. - class EPUBv2 + class EPUBv2 < EPUBCommon # Construct object with parameter hash +params+ and message resource hash +res+. def initialize(producer) - @producer = producer + super end - # Return mimetype content. - def mimetype - "application/epub+zip" - end - # Return opf file content. def opf s = <<EOT <?xml version="1.0" encoding="UTF-8"?> <package version="2.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookId"> @@ -86,46 +81,30 @@ end s 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/ - s << %Q[ <meta name="cover" content="#{item.id}"/>\n] - file = item.file - break - end - end - raise "coverimage #{@producer.params["coverimage"]} not found. Abort." if file.nil? - end - s - end - def opf_manifest s = "" s << <<EOT <manifest> <item id="ncx" href="#{@producer.params["bookname"]}.ncx" media-type="application/x-dtbncx+xml"/> <item id="#{@producer.params["bookname"]}" href="#{@producer.params["cover"]}" media-type="application/xhtml+xml"/> EOT - s << %Q[ <item id="toc" href="#{@producer.params["bookname"]}-toc.#{@producer.params["htmlext"]}" media-type="application/xhtml+xml"/>\n] unless @producer.params["mytoc"].nil? + s << %Q[ <item id="toc" href="#{@producer.params["bookname"]}-toc.#{@producer.params["htmlext"]}" media-type="application/xhtml+xml"/>\n] if @producer.params["toc"] && @producer.params["mytoc"] @producer.contents.each do |item| next if item.file =~ /#/ # skip subgroup s << %Q[ <item id="#{item.id}" href="#{item.file}" media-type="#{item.media}"/>\n] end s << %Q[ </manifest>\n] s end def opf_tocx - if @producer.params["cover_linear"] && @producer.params["cover_linear"] != "no" + if @producer.params["epubmaker"]["cover_linear"] && @producer.params["epubmaker"]["cover_linear"] != "no" cover_linear = "yes" else cover_linear = "no" end @@ -175,409 +154,20 @@ </ncx> EOT 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 - 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> - </docAuthor> -EOT - end - - def ncx_navmap(indentarray) - s = <<EOT - <navMap> - <navPoint id="top" playOrder="1"> - <navLabel> - <text>#{CGI.escapeHTML(@producer.params["title"])}</text> - </navLabel> - <content src="#{@producer.params["cover"]}"/> - </navPoint> -EOT - - nav_count = 2 - - unless @producer.params["mytoc"].nil? - s << <<EOT - <navPoint id="toc" playOrder="#{nav_count}"> - <navLabel> - <text>#{@producer.res.v("toctitle")}</text> - </navLabel> - <content src="#{@producer.params["bookname"]}-toc.#{@producer.params["htmlext"]}"/> - </navPoint> -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 << <<EOT - <navPoint id="nav-#{nav_count}" playOrder="#{nav_count}"> - <navLabel> - <text>#{indent[level]}#{CGI.escapeHTML(item.title)}</text> - </navLabel> - <content src="#{item.file}"/> - </navPoint> -EOT - nav_count += 1 - end - - s << <<EOT - </navMap> -EOT - 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 - end - - # Return cover content. - def cover(type=nil) - bodyext = 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 - end - raise "coverimage #{@producer.params["coverimage"]} not found. Abort." if file.nil? - s << <<EOT - <div id="cover-image" class="cover-image"> - <img src="#{file}" alt="#{CGI.escapeHTML(@producer.params["title"])}" class="max"/> - </div> -EOT - end - - s << <<EOT -</body> -</html> -EOT - s - 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 - - if @producer.params["aut"] - s << <<EOT - <p> - <br /> - <br /> - </p> - <h2 class="tp-author">#{CGI.escapeHTML(@producer.params["aut"].join(", "))}</h2> -EOT - end - - if @producer.params["prt"] - s << <<EOT - <p> - <br /> - <br /> - <br /> - <br /> - </p> - <h3 class="tp-publisher">#{CGI.escapeHTML(@producer.params["prt"].join(", "))}</h3> -EOT - end - - s << <<EOT -</body> -</html> -EOT - - s - end - - # Return colophon content. - def colophon - s = common_header - s << <<EOT - <title>#{@producer.res.v("colophontitle")}</title> -</head> -<body> - <div class="colophon"> -EOT - - if @producer.params["subtitle"].nil? - s << <<EOT - <p class="title">#{CGI.escapeHTML(@producer.params["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> -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) ? "初版" : "第#{edit + 1}版" # FIXME:i18n - revstr = "第#{rev + 1}刷" - if item =~ /\A\d+\-\d+\-\d+\Z/ - s << %Q[ <p>#{date_to_s(item)} #{editstr}#{revstr} 発行</p>\n] # FIXME:i18n - else - # custom date with string - item.match(/\A(\d+\-\d+\-\d+)[\s ](.+)/) do |m| - s << %Q[ <p>#{date_to_s(m[1])} #{m[2]}</p>\n] - end - end - end - end - else - s << %Q[ <p>#{date_to_s(@producer.params["date"])} 発行</p>\n] #FIXME:i18n - end - s << %Q[ </div>\n] - end - - s << %Q[ <table class="colophon">\n] - s << %Q[ <tr><th>#{@producer.res.v("c-aut")}</th><td>#{CGI.escapeHTML(@producer.params["aut"].join(", "))}</td></tr>\n] unless @producer.params["aut"].nil? - s << %Q[ <tr><th>#{@producer.res.v("c-csl")}</th><td>#{CGI.escapeHTML(@producer.params["csl"].join(", "))}</td></tr>\n] unless @producer.params["csl"].nil? - s << %Q[ <tr><th>#{@producer.res.v("c-trl")}</th><td>#{CGI.escapeHTML(@producer.params["trl"].join(", "))}</td></tr>\n] unless @producer.params["trl"].nil? - s << %Q[ <tr><th>#{@producer.res.v("c-dsr")}</th><td>#{CGI.escapeHTML(@producer.params["dsr"].join(", "))}</td></tr>\n] unless @producer.params["dsr"].nil? - s << %Q[ <tr><th>#{@producer.res.v("c-ill")}</th><td>#{CGI.escapeHTML(@producer.params["ill"].join(", "))}</td></tr>\n] unless @producer.params["ill"].nil? - s << %Q[ <tr><th>#{@producer.res.v("c-edt")}</th><td>#{CGI.escapeHTML(@producer.params["edt"].join(", "))}</td></tr>\n] unless @producer.params["edt"].nil? - s << %Q[ <tr><th>#{@producer.res.v("c-prt")}</th><td>#{CGI.escapeHTML(@producer.params["prt"].join(", "))}</td></tr>\n] unless @producer.params["prt"].nil? - s << %Q[ <tr><th>#{@producer.res.v("c-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]}" - 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] - end - s << <<EOT - </table> -EOT - if !@producer.params["rights"].nil? && @producer.params["rights"].size > 0 - s << %Q[ <p class="copyright">#{@producer.params["rights"].join("<br />")}</p>] - end - - s << <<EOT - </div> -</body> -</html> -EOT - s - end - - def date_to_s(date) - ymd = date.to_s.split('-') - "#{ymd[0]}年#{ymd[1].sub(/\A0/, '')}月#{ymd[2].sub(/\A0/, '')}日" # FIXME:i18n - 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 - - if @producer.params["flattoc"].nil? - s << hierarchy_ncx("ul") - else - s << flat_ncx("ul", @producer.params["flattocindent"]) - end - - s << <<EOT -</body> -</html> -EOT - s - end - - def hierarchy_ncx(type) - require 'rexml/document' - level = 1 - find_jump = nil - has_part = nil - toclevel = @producer.params["toclevel"].to_i - - # check part existance - @producer.contents.each do |item| - if item.notoc.nil? && item.chaptype == "part" - has_part = true - break - end - end - - if !has_part.nil? - @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: 部があるときに前後の処理が困難 - end - toclevel += 1 - end - - doc = REXML::Document.new(%Q[<#{type} class="toc-h#{level}"><li /></#{type}>]) - - e = doc.root.elements[1] # first <li/> - @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 - es = e.add_element("span") - 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) do |n| - e = e.parent.parent - end - 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 "found level jumping in table of contents. consider to use 'flattoc: true' for strict ePUB validator." unless find_jump.nil? - - doc.to_s.gsub("<li/>", "").gsub("</li>", "</li>\n").gsub("href='", "href=\"").gsub(" class='", " class=\"").gsub("'>", "\">").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.params["toclevel"].to_i - is = indent == true ? " " * item.level : "" - s << %Q[<li><a href="#{item.file}">#{is}#{CGI.escapeHTML(item.title)}</a></li>\n] - end - s << %Q[</#{type}>\n] - - s - end - # Produce EPUB file +epubfile+. # +basedir+ points the directory has contents. # +tmpdir+ defines temporary directory. def produce(epubfile, basedir, tmpdir) produce_write_common(basedir, tmpdir) - File.open("#{tmpdir}/OEBPS/#{@producer.params["bookname"]}.ncx", "w") {|f| @producer.ncx(f, @producer.params["ncxindent"]) } + File.open("#{tmpdir}/OEBPS/#{@producer.params["bookname"]}.ncx", "w") {|f| @producer.ncx(f, @producer.params["epubmaker"]["ncxindent"]) } File.open("#{tmpdir}/OEBPS/#{@producer.params["bookname"]}-toc.#{@producer.params["htmlext"]}", "w") {|f| @producer.mytoc(f) } unless @producer.params["mytoc"].nil? - @producer.call_hook(@producer.params["hook_prepack"], tmpdir) + @producer.call_hook(@producer.params["epubmaker"]["hook_prepack"], tmpdir) export_zip(tmpdir, epubfile) - end - - def produce_write_common(basedir, tmpdir) - File.open("#{tmpdir}/mimetype", "w") {|f| @producer.mimetype(f) } - - Dir.mkdir("#{tmpdir}/META-INF") unless File.exist?("#{tmpdir}/META-INF") - File.open("#{tmpdir}/META-INF/container.xml", "w") {|f| @producer.container(f) } - - Dir.mkdir("#{tmpdir}/OEBPS") unless File.exist?("#{tmpdir}/OEBPS") - File.open("#{tmpdir}/OEBPS/#{@producer.params["bookname"]}.opf", "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) } - 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}")) unless File.exist?(File.dirname("#{tmpdir}/OEBPS/#{item.file}")) - FileUtils.cp(fname, "#{tmpdir}/OEBPS/#{item.file}") - end - end - - def export_zip(tmpdir, epubfile) - Dir.chdir(tmpdir) {|d| `#{@producer.params["zip_stage1"]} #{epubfile} mimetype` } - Dir.chdir(tmpdir) {|d| `#{@producer.params["zip_stage2"]} #{epubfile} META-INF OEBPS #{@producer.params["zip_addpath"]}` } - end - - def legacy_cover_and_title_file(loadfile, writefile) - s = common_header - s << <<EOT - <title>#{@producer.params["booktitle"]}</title> -</head> -<body> -EOT - 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 private # Return common XHTML headder