review 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.travis.yml +9 -0
  2. data/ChangeLog +326 -0
  3. data/Rakefile +3 -5
  4. data/VERSION +1 -1
  5. data/bin/review-compile +50 -50
  6. data/bin/review-epubmaker +62 -75
  7. data/bin/review-epubmaker-ng +185 -0
  8. data/bin/review-index +2 -1
  9. data/bin/review-pdfmaker +158 -101
  10. data/bin/review-vol +6 -2
  11. data/doc/format.rdoc +111 -46
  12. data/doc/libepubmaker/sample.yaml +90 -0
  13. data/doc/quickstart.rdoc +188 -0
  14. data/doc/sample.yaml +8 -0
  15. data/lib/epubmaker.rb +28 -0
  16. data/lib/epubmaker/content.rb +82 -0
  17. data/lib/epubmaker/epubv2.rb +419 -0
  18. data/lib/epubmaker/epubv3.rb +249 -0
  19. data/lib/epubmaker/producer.rb +204 -0
  20. data/lib/epubmaker/resource.rb +66 -0
  21. data/lib/review.rb +1 -1
  22. data/lib/review/book.rb +27 -4
  23. data/lib/review/builder.rb +153 -20
  24. data/lib/review/compiler.rb +61 -10
  25. data/lib/review/ewbbuilder.rb +3 -2
  26. data/lib/review/htmlbuilder.rb +174 -67
  27. data/lib/review/i18n.rb +30 -0
  28. data/lib/review/i18n.yaml +23 -0
  29. data/lib/review/idgxmlbuilder.rb +110 -63
  30. data/lib/review/index.rb +34 -12
  31. data/lib/review/latexbuilder.rb +128 -33
  32. data/lib/review/latexutils.rb +18 -1
  33. data/lib/review/textbuilder.rb +17 -0
  34. data/lib/review/tocparser.rb +3 -1
  35. data/lib/review/tocprinter.rb +1 -0
  36. data/lib/review/topbuilder.rb +397 -198
  37. data/review.gemspec +101 -100
  38. data/test/test_book.rb +27 -0
  39. data/test/test_epubmaker.rb +507 -0
  40. data/test/test_helper.rb +8 -0
  41. data/test/test_htmlbuilder.rb +295 -10
  42. data/test/test_i18n.rb +64 -0
  43. data/test/test_idgxmlbuilder.rb +268 -10
  44. data/test/test_latexbuilder.rb +316 -20
  45. data/test/test_preprocessor.rb +23 -0
  46. data/test/test_topbuilder.rb +246 -0
  47. metadata +46 -53
  48. data/doc/format.re +0 -505
  49. data/test/test_index.rb +0 -15
@@ -0,0 +1,90 @@
1
+ # YAMLファイルのサンプル
2
+ # このファイルはUTF-8エンコーディングで記述してください
3
+
4
+ # ブック名(ファイル名になるもの。ASCII範囲の文字を使用)
5
+ bookname: sample
6
+ # 記述言語
7
+ language: ja
8
+ # 書名
9
+ title: ReVIEW EPUBのサンプル
10
+
11
+ # 固有IDに使用するドメイン。指定しない場合には、時刻に基づくランダムUUIDが入る
12
+ # urnid: urn:uid:http://example.com/some-book-title/1.0.2/
13
+
14
+ # isbn: ISBN。省略した場合はランダム生成したUUIDが入る
15
+
16
+ # 著者名。複数人いる場合はYAMLの配列形式で指定できる
17
+ aut: Kenshi Muto
18
+
19
+ # 以下はオプション(配列書式で複数指定可能)。a-が付いているものはcreator側、
20
+ # 付いていないものはcontributor側(二次協力者)に入る
21
+ # a-adp, adp: 異なるメディア向けに作り直した者
22
+ # a-ann, ann: 注釈記述者
23
+ # a-arr, arr: アレンジした者
24
+ # a-art, art: グラフィックデザインおよび芸術家
25
+ # a-asn, asn: 関連・かつての所有者・関係者
26
+ # a-aqt, aqt: 大きく引用された人物
27
+ # a-aft, aft: 後書き・奥付の責任者
28
+ # a-aui, aui: 序論・序文・前書きの責任者
29
+ # a-ant, ant: 目録責任者
30
+ # a-bkp, bkp: メディア制作責任者
31
+ # a-clb, clb: 限定参加または補足者
32
+ # a-cmm, cmm: 解釈・分析・考察者
33
+ # a-dsr, dsr: デザイナ
34
+ # a-edt, edt: 編集者
35
+ # a-ill, ill: イラストレータ
36
+ # a-lyr, lyr: 歌詞作成者
37
+ # a-mdc, mdc: メタデータセットの一次的責任者
38
+ # a-mus, mus: 音楽家
39
+ # a-nrt, nrt: 語り手
40
+ # a-oth, oth: その他
41
+ # a-pht, pht: 撮影責任者
42
+ # a-prt, prt: 出版社
43
+ # a-red, red: 項目の枠組起草者
44
+ # a-rev, rev: 評論者
45
+ # a-spn, spn: 援助者
46
+ # a-ths, ths: 監督者
47
+ # a-trc, trc: 筆記・タイプ作業者
48
+ # a-trl, trl: 翻訳者
49
+
50
+ # rights: 権利表記
51
+ # date: 刊行日(省略した場合は実行時の日付)
52
+ # description: 説明
53
+ # subject: 短い説明用タグ。配列で複数指定可
54
+ # type: 書籍のカテゴリーなど(複数指定可)
55
+ # format: メディアタイプおよび特徴(複数指定可)
56
+ # source: 出版物生成の重要なリソース情報(複数指定可)
57
+ # relation: 補助的リソース(複数指定可)
58
+ # coverage: 内容の範囲や領域(複数指定可)
59
+
60
+ # htmlext: HTMLファイルの拡張子(省略した場合はhtml)
61
+ # cover: カバーページのファイル名(省略した場合はbookname.xhtmlになる)
62
+ #
63
+ # coverimage: カバー用画像
64
+ #
65
+ # CSSファイル(配列で複数指定することも可)
66
+ #stylesheet: stylesheet.css
67
+
68
+ # 目次として抽出するレベル
69
+ toclevel: 3
70
+ # セクション番号を表示するレベル
71
+ secnolevel: 2
72
+ # NCX目次の見出しレベルごとの飾り(配列で設定)
73
+ ncxindent:
74
+ -
75
+ - -
76
+
77
+ # EPUB標準の目次以外に目次を作成するか
78
+ mytoc:
79
+ # mytocがtrueの場合に作成する目次ファイル名(省略するとtoc.xhtml)
80
+ # tocfile: toc.xhtml
81
+
82
+ # 表紙の後に権利表記ページを作成するか。デフォルトでは作成されない。ファイル名を指定するとそのファイルが使われる(data配列にも指定しておく必要がある)
83
+ # titlepage: title.xhtml
84
+
85
+ # 奥付を作成するか。デフォルトでは作成されない。trueを指定するとデフォルトの奥付、ファイル名を指定するとそれがcolophon.xhtmlとしてコピーされる(data配列にも指定しておく必要がある)
86
+ # colophon: true
87
+ # pubhistory: 奥付履歴
88
+
89
+ # XHTML生成後に実行するプログラム。$1:HTMLの生成されたディレクトリ $2:ReVIEWファイルのあるディレクトリ $3:起動時指定のyamlファイル名
90
+ # posthook: hook.sh
@@ -0,0 +1,188 @@
1
+ = ReVIEWクイックスタートガイド
2
+
3
+ ReVIEW は、EWB や RD あるいは Wiki に似た簡易フォーマットで記述したテキストファイルを、目的に応じて各種の形式に変換するツールセットです。
4
+
5
+ 平易な文法ながらも、コンピュータ関係のドキュメント作成のための多くの機能を備えており、テキスト、LaTeX、HTML、XML といった形式に変換できます。独自のカスタマイズも簡単です。
6
+
7
+ ReVIEW は GNU Lesser General Public License Version 2.1 に基づいて配布されており、自由に利用、改変、再配布できます。このライセンスは、ReVIEW を使ってあなたが作成しようとする文書とは無関係であり、あなたの文書はこのライセンスに強制されることはありません。ReVIEW のツールセットあるいは ReVIEW を組み込んだシステムを配布あるいは販売しようとしているときには、ライセンスファイル COPYING をよく確認してください。
8
+
9
+ このドキュメントでは、ReVIEW のセットアップから変換の例までを簡単に説明します。
10
+
11
+ == セットアップ
12
+
13
+ ReVIEW は Ruby 言語で記述されており、Linux/Unix 互換システムで動作します。Mac OS X および Windows Cygwin でも動作可能です。Ruby gem、Git、Subversion のいずれかを使ってダウンロード・展開します。
14
+
15
+ なお、ReVIEW フォーマット自体は文字で表現されたタグが付いている以外は単なるテキストファイルなので、エディタ、OS についてはまったく制限はありません。
16
+
17
+ === Ruby gemを使う場合
18
+
19
+ 機能セットがまとまった区切りごとに、ReVIEW の開発チームが ReVIEW の gem を更新しています。
20
+
21
+ 次のように ReVIEW の gem をインストールします。
22
+
23
+ gem install review
24
+
25
+ Ruby gem の bin ディレクトリにパスを通すようにしておいてください。
26
+
27
+ インストール後、最新の gem に追従するには次のようにします。
28
+
29
+ gem update review
30
+
31
+ === Gitを使う場合
32
+
33
+ ReVIEW は GitHub で開発されており、バージョン管理ツールの Git を使って最新の ReVIEW コードを入手できます。Git は分岐が容易なので、独自のカスタマイズを施すのにも向いています。
34
+
35
+ 初めて取得するときには、次のようにします (コピーを作っています)。
36
+
37
+ git clone git://github.com/kmuto/review.git
38
+
39
+ review というディレクトリに展開されるので、review/bin にパスを通すようにしておいてください。
40
+
41
+ 最新の開発に追従するには次のようにします。
42
+
43
+ git pull
44
+
45
+ === Subversionを使う場合
46
+
47
+ Git の最新コピーは、別のバージョン管理ツールの Subversion 向けにも提供しています (古い環境では Subversion のクライアントしか入っていないことがあります)。
48
+
49
+ 初めて取得するときには、次のようにします (コピーを作っています)。
50
+
51
+ svn co https://kmuto.jp/svn/review/trunk review
52
+
53
+ review というディレクトリに展開されるので、review/bin にパスを通すようにしておいてください。
54
+
55
+ 最新の開発に追従するには次のようにします。
56
+
57
+ svn up
58
+
59
+ = ReVIEW テキストの作成と変換
60
+
61
+ セットアップを終えたら、ReVIEW フォーマットのテキストを作り、変換できるようになります。次に ReVIEW フォーマットテキストの簡単な例を示します。これを sample.re といった名前で保存します (拡張子も自由ですが、.re 拡張子を推奨します)。
62
+
63
+ = はじめてのReVIEW
64
+
65
+ //lead{
66
+ 「Hello, ReVIEW.」
67
+ //}
68
+
69
+ == ReVIEWとは
70
+
71
+ @<b>{ReVIEW}は、EWBやRDあるいはWikiに似た簡易フォーマットで記述したテキストファイルを、目的に応じて各種の形式に変換するツールセットです。
72
+
73
+ 平易な文法ながらも、コンピュータ関係のドキュメント作成のための多くの機能を備えており、次のような形式に変換できます。
74
+
75
+ * テキスト(指示タグ付き)
76
+ * LaTeX
77
+ * HTML
78
+ * XML
79
+
80
+ 現在入手手段としては次の3つがあります。
81
+
82
+ 1. Ruby gem
83
+ 2. Git
84
+ 3. Subversion
85
+
86
+ ホームページは@<tt>{https://github.com/kmuto/review/wiki/}です。
87
+
88
+ テキストファイルの文字エンコーディングには、UTF-8 を使うことをお勧めします。ReVIEW は日本語文字エンコーディングとして UTF-8、EUC-JP、Shift-JIS、JIS を扱うことができ、入力ファイルについては自動判別、出力ファイルについても選択可能 (デフォルトは UTF-8) ですが、入力・出力のいずれにおいても、使用可能な文字についての制限が少ない UTF-8 が最適です。
89
+
90
+ 次に、章構成ファイルの CHAPS ファイルを同じディレクトリに用意します。このファイルには、ReVIEW フォーマットファイルの名前を格納します。
91
+
92
+ sample.re
93
+
94
+ CHAPS ファイルの1行目に書いたものが第1章、2行目に書いたものが第2章、……と構成されます (CHAPS に似たものとして、前付けを列挙する PREDEF ファイル、後付けを列挙する POSTDEF ファイルがあります)。
95
+
96
+ sample.re から目的の形式に変換するには、review-compile コマンドを使います。
97
+
98
+ review-compile --target text sample.re > sample.txt ←テキストにする
99
+ review-compile --target html sample.re > sample.html ←HTMLにする
100
+ review-compile --target latex sample.re > sample.tex ←LaTeXにする
101
+ review-compile --target idgxml sample.re > sample.xml ←XMLにする
102
+
103
+ 上記では各ファイル個別に変換することを想定して、標準出力をリダイレクトする書式を掲載していますが、-a オプションを付ければ、CHAPS、PREDEF、POSTDEF に従ってすべてのファイルを変換できます。
104
+
105
+ review-compile --target html -a ←すべてのファイルをHTMLにする
106
+
107
+ sample.re を HTML に変換すると、次のようになります。
108
+
109
+ <?xml version="1.0" encoding="UTF-8"?>
110
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
111
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ops="http://www.idpf.org/2007/ops" xml:lang="ja">
112
+ <head>
113
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
114
+ <meta http-equiv="Content-Style-Type" content="text/css" />
115
+ <meta name="generator" content="ReVIEW" />
116
+ <title>はじめてのReVIEW</title>
117
+ </head>
118
+ <body>
119
+ <h1><a id="h1" />第1章 はじめてのReVIEW</h1>
120
+ <div class="lead">
121
+ <p>「Hello, ReVIEW.」</p>
122
+ </div>
123
+
124
+ <h2><a id="h1-1" />1.1 ReVIEWとは</h2>
125
+ <p><b>ReVIEW</b>は、EWBやRDあるいはWikiに似た簡易フォーマットで記述したテキストファイルを、目的に応じて各種の形式に変換するツールセットです。</p>
126
+ <p>平易な文法ながらも、コンピュータ関係のドキュメント作成のための多くの機能を備えており、次のような形式に変換できます。</p>
127
+ <ul>
128
+ <li>テキスト(指示タグ付き)</li>
129
+ <li>LaTeX</li>
130
+ <li>HTML</li>
131
+ <li>XML</li>
132
+ </ul>
133
+ <p>現在入手手段としては次の3つがあります。</p>
134
+ <ol>
135
+ <li>Ruby gem</li>
136
+ <li>Git</li>
137
+ <li>Subversion</li>
138
+ </ol>
139
+ <p>ホームページは<tt>https://github.com/kmuto/review/wiki/</tt>です。</p>
140
+ </body>
141
+ </html>
142
+
143
+ ReVIEW フォーマットについての詳細は、format.rdoc (オンラインでは [[https://github.com/kmuto/review/blob/master/doc/format.rdoc]] ) を参照してください。
144
+
145
+ review-compile を含め、ほとんどのコマンドは --help オプションを付けるとオプションについてのヘルプが表示されます。review-compile には多数のオプションがあるので確認してください。
146
+
147
+ なお、--target で毎回指定するのは面倒なので、review-compile に対するシンボリックリンクを作成しておくとよいでしょう。「review2...」のコマンド名で呼び出せるようになります。
148
+
149
+ cd ReVIEWのインストールされたパス/bin
150
+ ln -s review-compile review2text
151
+ ln -s review-compile review2html
152
+ ln -s review-compile review2latex
153
+ ln -s review-compile review2idgxml
154
+
155
+ == プリプロセッサ、ボリューム表示
156
+
157
+ #@mapfile、#@maprange、#@mapoutput のタグを使って、指定のファイルの内容あるいはコマンドの実行結果を挿入できます。挿入・更新を行うには、プリプロセッサとなる review-preproc コマンドを使います。
158
+
159
+ review-preproc ファイル > 結果ファイル ←標準出力をリダイレクト
160
+ または
161
+ review-preproc --replace ファイル ←ファイルを更新したもので上書き
162
+
163
+ 各章の分量などを表示するには、review-vol コマンドを使います。
164
+
165
+ review-vol
166
+
167
+ より細かな見出し一覧などを出したいときには、review-index コマンドを使うのもよいでしょう。
168
+
169
+ review-index --level 掘り下げる見出しレベル数 -a
170
+
171
+ == PDF 化と EPUB 化
172
+
173
+ review-pdfmaker コマンドで PDF ブックの作成、review-epubmaker コマンドで EPUB ファイルの作成ができます。
174
+
175
+ PDF を作成するには、pTeXLive2009 以上の環境が必要です。EPUB を作成するには、zip コマンドが必要です (MathML も使いたいときには、[[http://www.hinet.mydns.jp/?mathml.rb]] の MathML ライブラリも必要です)。
176
+
177
+ いずれのコマンドも、必要な設定情報を記した YAML ファイルを引数に指定して実行します。YAML ファイルのサンプルは、sample.yaml (オンラインでは[[https://github.com/kmuto/review/blob/master/doc/sample.yaml]]) としてこのドキュメントと同じディレクトリに収録しています。
178
+
179
+ review-pdfmaker YAMLファイル ←PDFの作成
180
+ review-epubmaker YAMLファイル ←EPUBの作成
181
+
182
+ == クレジット
183
+
184
+ ReVIEW は、青木峰郎によって最初に作成されました。武藤健志がこの開発・保守を引き継ぎ、2010年12月時点では、武藤健志、高橋征義、角征典が開発・保守を継続しています。
185
+
186
+ バグ・パッチの報告、開発者用メーリングリストなどについての情報は、
187
+ [[https://github.com/kmuto/review/wiki]]
188
+ を参照してください。
data/doc/sample.yaml CHANGED
@@ -34,16 +34,24 @@ aut: 吟遊詩人
34
34
  stylesheet: stylesheet.css
35
35
  # LaTeX用のスタイルファイル(styディレクトリ以下に置くこと)
36
36
  # texstyle: samplemacro
37
+ # LaTeX用のdocumentclassを指定する
38
+ # texdocumentclass: ["jsarticle", "b5paper,oneside"]
37
39
  # 目次として抽出するレベル
38
40
  toclevel: 3
39
41
  # セクション番号を表示するレベル
40
42
  secnolevel: 2
43
+ # EPUBのバージョン(現時点では2または3。デフォルトは2)
44
+ epubversion: 2
45
+ # HTMLのバージョン(現時点では4または5。デフォルトは4。epubversionを3にした場合は自動で5に設定される)
46
+ htmlversion: 4
41
47
  # EPUB標準の目次以外に目次を作成するか
42
48
  # mytoc: nil
43
49
  # 奥付を作成するか。デフォルトでは作成されない。trueを指定するとデフォルトの奥付、ファイル名を指定するとそれがcolophon.htmlとしてコピーされる
44
50
  # colophon: true
45
51
  # XHTML生成後に実行するプログラム。$1:HTMLの生成されたディレクトリ $2:ReVIEWファイルのあるディレクトリ $3:起動時指定のyamlファイル名
46
52
  # posthook: hook.sh
53
+ # EPUBで表紙をコンテンツに含めるか。デフォルト(no)では作成されない。yesにするとiBooks等でも最初に表紙が表示されるようになる
54
+ # cover_linear: yes
47
55
  # review-compileに渡すパラメータ
48
56
  params: --stylesheet=sample.css
49
57
  # デバッグフラグ。nilでないときには一時ファイルをカレントディレクトリに作成し、削除もしない
data/lib/epubmaker.rb ADDED
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ # = epubmaker.rb -- EPUB production set.
3
+ #
4
+ # Copyright (c) 2010 Kenshi Muto
5
+ #
6
+ # This program is free software.
7
+ # You can distribute or modify this program under the terms of
8
+ # the GNU LGPL, Lesser General Public License version 2.1.
9
+ # For details of the GNU LGPL, see the file "COPYING".
10
+ #
11
+ # == Quick usage
12
+ # (If you put xhtml files on current directory and put figures in
13
+ # images subdirectory)
14
+ #
15
+ # require 'epubmaker'
16
+ # epub = EPUBMaker::Producer.new
17
+ # params = epub.load("config.yaml")
18
+ # epub.contents.push(EPUBMaker::Content.new({"file" => "ch01.xhtml"}))
19
+ # epub.contents.push(EPUBMaker::Content.new({"file" => "ch02.xhtml"}))
20
+ # ...
21
+ # epub.importImageInfo("images")
22
+ # epub.produce
23
+
24
+ require 'epubmaker/producer'
25
+ require 'epubmaker/resource'
26
+ require 'epubmaker/content'
27
+ require 'epubmaker/epubv2'
28
+ require 'epubmaker/epubv3'
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+ # = content.rb -- Content object for EPUBMaker.
3
+ #
4
+ # Copyright (c) 2010 Kenshi Muto
5
+ #
6
+ # This program is free software.
7
+ # You can distribute or modify this program under the terms of
8
+ # the GNU LGPL, Lesser General Public License version 2.1.
9
+ # For details of the GNU LGPL, see the file "COPYING".
10
+ #
11
+
12
+ module EPUBMaker
13
+
14
+ # EPUBMaker::Content describes a content data for EPUBMaker. EPUBMaker#contents takes an array of Content.
15
+ class Content
16
+ # ID
17
+ attr_accessor :id
18
+ # File path (can accept #<anchor> suffix also)
19
+ attr_accessor :file
20
+ # MIME type
21
+ attr_accessor :media
22
+ # Title
23
+ attr_accessor :title
24
+ # Header level (from 1)
25
+ attr_accessor :level
26
+ # Show in TOC? nil:No.
27
+ attr_accessor :notoc
28
+
29
+ # :call-seq:
30
+ # initialize(file, id, media, title, level, notoc)
31
+ # initialize(hash)
32
+ # Construct Content object by passing a sequence of parameters or hash.
33
+ # Keys of +hash+ relate with each parameters.
34
+ # +file+ (or +hash+["file"]) is required. Others are optional.
35
+ def initialize(fileorhash, id=nil, media=nil, title=nil, level=nil, notoc=nil)
36
+ if fileorhash.instance_of?(Hash)
37
+ @id = fileorhash["id"]
38
+ @file = fileorhash["file"]
39
+ @media = fileorhash["media"]
40
+ @title = fileorhash["title"]
41
+ @level = fileorhash["level"]
42
+ @notoc = fileorhash["notoc"]
43
+ else
44
+ @file = fileorhash
45
+ @id = id
46
+ @media = media
47
+ @title = title
48
+ @level = level
49
+ @notoc = notoc
50
+ end
51
+ complement
52
+ end
53
+
54
+ def ==(obj)
55
+ if self.class != obj.class
56
+ return false
57
+ end
58
+ [self.id, self.file, self.media, self.title, self.level, self.notoc] ==
59
+ [obj.id, obj.file, obj.media, obj.title, obj.level, obj.notoc]
60
+ end
61
+
62
+ private
63
+
64
+ # Complement other parameters from file parameter.
65
+ def complement
66
+ @id = @file.gsub(/[\\\/\.]/, '-') if @id.nil?
67
+ @media = @file.sub(/.+\./, '').downcase if !@file.nil? && @media.nil?
68
+
69
+ @media = "application/xhtml+xml" if @media == "xhtml" || @media == "xml" || @media == "html"
70
+ @media = "text/css" if @media == "css"
71
+ @media = "image/jpeg" if @media == "jpg" || @media == "jpeg" || @media == "image/jpg"
72
+ @media = "image/png" if @media == "png"
73
+ @media = "image/gif" if @media == "gif"
74
+ @media = "image/svg" if @media == "svg"
75
+ @media = "image/svg+xml" if @media == "svg" || @media == "image/svg"
76
+
77
+ if @id.nil? || @file.nil? || @media.nil?
78
+ raise "Type error: #{id}, #{file}, #{media}, #{title}, #{notoc}"
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,419 @@
1
+ # encoding: utf-8
2
+ # = epubv2.rb -- EPUB version 2 producer.
3
+ #
4
+ # Copyright (c) 2010 Kenshi Muto and Masayoshi Takahashi
5
+ #
6
+ # This program is free software.
7
+ # You can distribute or modify this program under the terms of
8
+ # the GNU LGPL, Lesser General Public License version 2.1.
9
+ # For details of the GNU LGPL, see the file "COPYING".
10
+ #
11
+
12
+ require 'epubmaker/producer'
13
+
14
+ module EPUBMaker
15
+
16
+ # EPUBv2 is EPUB version 2 producer.
17
+ class EPUBv2
18
+ # Construct object with parameter hash +params+ and message resource hash +res+.
19
+ def initialize(producer)
20
+ @producer = producer
21
+ end
22
+
23
+ # Return mimetype content.
24
+ def mimetype
25
+ return <<EOT
26
+ application/epub+zip
27
+ EOT
28
+ end
29
+
30
+ # Return opf file content.
31
+ def opf
32
+ s = <<EOT
33
+ <?xml version="1.0" encoding="UTF-8"?>
34
+ <package version="2.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookId">
35
+ <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
36
+ EOT
37
+ %w[title language date type format source description relation coverage subject rights].each do |item|
38
+ next if @producer.params[item].nil?
39
+ if @producer.params[item].instance_of?(Array)
40
+ s << @producer.params[item].map {|i| %Q[ <dc:#{item}>#{i}</dc:#{item}>\n]}.join
41
+ else
42
+ s << %Q[ <dc:#{item}>#{@producer.params[item]}</dc:#{item}>\n]
43
+ end
44
+ end
45
+
46
+ # ID
47
+ if @producer.params["isbn"].nil?
48
+ s << %Q[ <dc:identifier id="BookId">#{@producer.params["urnid"]}</dc:identifier>\n]
49
+ else
50
+ s << %Q[ <dc:identifier id="BookId" opf:scheme="ISBN">#{@producer.params["isbn"]}</dc:identifier>\n]
51
+ end
52
+
53
+ # creator
54
+ %w[aut 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-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].each do |role|
55
+ next if @producer.params[role].nil?
56
+ @producer.params[role].each do |v|
57
+ s << %Q[ <dc:creator opf:role="#{role.sub('a-', '')}">#{v}</dc:creator>\n]
58
+ end
59
+ end
60
+ # contributor
61
+ %w[adp ann arr art asn aqt aft aui ant bkp clb cmm dsr edt ill lyr mdc mus nrt oth pht prt red rev spn ths trc trl].each do |role|
62
+ next if @producer.params[role].nil?
63
+ @producer.params[role].each do |v|
64
+ s << %Q[ <dc:contributor opf:role="#{role}">#{v}</dc:contributor>\n]
65
+ if role == "prt"
66
+ s << %Q[ <dc:publisher>#{v}</dc:publisher>\n]
67
+ end
68
+ end
69
+ end
70
+
71
+ if @producer.params["coverimage"]
72
+ @producer.contents.each do |item|
73
+ if item.media =~ /\Aimage/ && item.file =~ /#{@producer.params["coverimage"]}\Z/
74
+ s << %Q[ <meta name="cover" content="#{item.id}"/>\n]
75
+ break
76
+ end
77
+ end
78
+ end
79
+
80
+ s << %Q[ </metadata>\n]
81
+
82
+ # manifest
83
+ s << <<EOT
84
+ <manifest>
85
+ <item id="ncx" href="#{@producer.params["bookname"]}.ncx" media-type="application/x-dtbncx+xml"/>
86
+ <item id="#{@producer.params["bookname"]}" href="#{@producer.params["cover"]}" media-type="application/xhtml+xml"/>
87
+ EOT
88
+
89
+ s << %Q[ <item id="toc" href="#{@producer.params["tocfile"]}" media-type="application/xhtml+xml"/>\n] unless @producer.params["mytoc"].nil?
90
+
91
+ @producer.contents.each do |item|
92
+ next if item.file =~ /#/ # skip subgroup
93
+ s << %Q[ <item id="#{item.id}" href="#{item.file}" media-type="#{item.media}"/>\n]
94
+ end
95
+ s << %Q[ </manifest>\n]
96
+
97
+ # tocx
98
+ s << %Q[ <spine toc="ncx">\n]
99
+ s << %Q[ <itemref idref="#{@producer.params["bookname"]}" linear="no"/>\n]
100
+ s << %Q[ <itemref idref="toc" />\n] unless @producer.params["mytoc"].nil?
101
+
102
+ @producer.contents.each do |item|
103
+ next if item.media !~ /xhtml\+xml/ # skip non XHTML
104
+ s << %Q[ <itemref idref="#{item.id}"/>\n] if item.notoc.nil?
105
+ end
106
+ s << %Q[ </spine>\n]
107
+
108
+ # guide
109
+ s << %Q[ <guide>\n]
110
+ s << %Q[ <reference type="cover" title="#{@producer.res.v("covertitle")}" href="#{@producer.params["cover"]}"/>\n]
111
+ s << %Q[ <reference type="title-page" title="#{@producer.res.v("titlepagetitle")}" href="#{@producer.params["titlepage"]}"/>\n] unless @producer.params["titlepage"].nil?
112
+ s << %Q[ <reference type="toc" title="#{@producer.res.v("toctitle")}" href="#{@producer.params["tocfile"]}"/>\n] unless @producer.params["mytoc"].nil?
113
+ s << %Q[ <reference type="colophon" title="#{@producer.res.v("colophontitle")}" href="colophon.#{@producer.params["htmlext"]}"/>\n] unless @producer.params["colophon"].nil? # FIXME: path
114
+ s << %Q[ </guide>\n]
115
+ s << %Q[</package>\n]
116
+ return s
117
+ end
118
+
119
+ # Return ncx content. +indentarray+ defines prefix string for each level.
120
+ def ncx(indentarray)
121
+ s = <<EOT
122
+ <?xml version="1.0" encoding="UTF-8"?>
123
+ <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
124
+ <head>
125
+ <meta name="dtb:depth" content="1"/>
126
+ <meta name="dtb:totalPageCount" content="0"/>
127
+ <meta name="dtb:maxPageNumber" content="0"/>
128
+ EOT
129
+ if @producer.params["isbn"].nil?
130
+ s << %Q[ <meta name="dtb:uid" content="#{@producer.params["urnid"]}"/>\n]
131
+ else
132
+ s << %Q[ <meta name="dtb:uid" content="#{@producer.params["isbn"]}"/>\n]
133
+ end
134
+
135
+ s << <<EOT
136
+ </head>
137
+ <docTitle>
138
+ <text>#{@producer.params["title"]}</text>
139
+ </docTitle>
140
+ <docAuthor>
141
+ <text>#{@producer.params["aut"].nil? ? "" : @producer.params["aut"].join(", ")}</text>
142
+ </docAuthor>
143
+ <navMap>
144
+ <navPoint id="top" playOrder="1">
145
+ <navLabel>
146
+ <text>#{@producer.params["title"]}</text>
147
+ </navLabel>
148
+ <content src="#{@producer.params["cover"]}"/>
149
+ </navPoint>
150
+ EOT
151
+
152
+ nav_count = 2
153
+
154
+ unless @producer.params["mytoc"].nil?
155
+ s << <<EOT
156
+ <navPoint id="toc" playOrder="#{nav_count}">
157
+ <navLabel>
158
+ <text>#{@producer.res.v("toctitle")}</text>
159
+ </navLabel>
160
+ <content src="#{@producer.params["tocfile"]}"/>
161
+ </navPoint>
162
+ EOT
163
+ nav_count += 1
164
+ end
165
+
166
+ @producer.contents.each do |item|
167
+ next if item.title.nil?
168
+ indent = indentarray.nil? ? [""] : indentarray
169
+ level = item.level.nil? ? 0 : (item.level - 1)
170
+ level = indent.size - 1 if level >= indent.size
171
+ s << <<EOT
172
+ <navPoint id="nav-#{nav_count}" playOrder="#{nav_count}">
173
+ <navLabel>
174
+ <text>#{indent[level]}#{item.title}</text>
175
+ </navLabel>
176
+ <content src="#{item.file}"/>
177
+ </navPoint>
178
+ EOT
179
+ nav_count += 1
180
+ end
181
+
182
+ s << <<EOT
183
+ </navMap>
184
+ </ncx>
185
+ EOT
186
+ return s
187
+ end
188
+
189
+ # Return container content.
190
+ def container
191
+ s = <<EOT
192
+ <?xml version="1.0" encoding="UTF-8"?>
193
+ <container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
194
+ <rootfiles>
195
+ <rootfile full-path="OEBPS/#{@producer.params["bookname"]}.opf" media-type="application/oebps-package+xml" />
196
+ </rootfiles>
197
+ </container>
198
+ EOT
199
+ return s
200
+ end
201
+
202
+ # Return cover content.
203
+ def cover
204
+ s = common_header
205
+ s << <<EOT
206
+ <title>#{@producer.params["title"]}</title>
207
+ </head>
208
+ <body>
209
+ EOT
210
+ if @producer.params["coverimage"].nil?
211
+ s << <<EOT
212
+ <h1 class="cover-title">#{@producer.params["title"]}</h1>
213
+ EOT
214
+ else
215
+ file = nil
216
+ @producer.contents.each do |item|
217
+ if item.media =~ /\Aimage/ && item.file =~ /#{@producer.params["coverimage"]}\Z/ # /
218
+ file = item.file
219
+ break
220
+ end
221
+ end
222
+ raise "coverimage #{@producer.params["coverimage"]} not found. Abort." if file.nil?
223
+ s << <<EOT
224
+ <div id="cover-image" class="cover-image">
225
+ <img src="#{file}" alt="#{@producer.params["title"]}" class="max"/>
226
+ </div>
227
+ EOT
228
+ end
229
+
230
+ s << <<EOT
231
+ </body>
232
+ </html>
233
+ EOT
234
+ return s
235
+ end
236
+
237
+ # Return title (copying) content.
238
+ def titlepage
239
+ s = common_header
240
+ s << <<EOT
241
+ <title>#{@producer.params["title"]}</title>
242
+ </head>
243
+ <body>
244
+ <h1 class="tp-title">#{@producer.params["title"]}</h1>
245
+ EOT
246
+
247
+ if @producer.params["aut"]
248
+ s << <<EOT
249
+ <p>
250
+ <br />
251
+ <br />
252
+ </p>
253
+ <h2 class="tp-author">#{@producer.params["aut"]}</h2>
254
+ EOT
255
+ end
256
+
257
+ if @producer.params["prt"]
258
+ s << <<EOT
259
+ <p>
260
+ <br />
261
+ <br />
262
+ <br />
263
+ <br />
264
+ </p>
265
+ <h3 class="tp-publisher">#{@producer.params["prt"]}</h3>
266
+ EOT
267
+ end
268
+
269
+ s << <<EOT
270
+ </body>
271
+ </html>
272
+ EOT
273
+ return s
274
+ end
275
+
276
+ # Return colophon content.
277
+ def colophon
278
+ s = common_header
279
+ s << <<EOT
280
+ <title>#{@producer.res.v("colophontitle")}</title>
281
+ </head>
282
+ <body>
283
+ <div class="colophon">
284
+ <p class="title">#{@producer.params["title"]}</p>
285
+ EOT
286
+
287
+ if @producer.params["pubhistory"]
288
+ s << %Q[ <div class="pubhistory">\n <p>#{@producer.params["pubhistory"].gsub(/\n/, "<br />")}</p>\n </div>\n] # FIXME: should be array?
289
+ end
290
+
291
+ s << %Q[ <table class="colophon">\n]
292
+ s << %Q[ <tr><th>#{@producer.res.v("c-aut")}</th><td>#{@producer.params["aut"]}</td></tr>\n] if @producer.params["aut"]
293
+ s << %Q[ <tr><th>#{@producer.res.v("c-dsr")}</th><td>#{@producer.params["dsr"]}</td></tr>\n] if @producer.params["dsr"]
294
+ s << %Q[ <tr><th>#{@producer.res.v("c-ill")}</th><td>#{@producer.params["ill"]}</td></tr>\n] if @producer.params["ill"]
295
+ s << %Q[ <tr><th>#{@producer.res.v("c-edt")}</th><td>#{@producer.params["edt"]}</td></tr>\n] if @producer.params["edt"]
296
+ s << %Q[ <tr><th>#{@producer.res.v("c-prt")}</th><td>#{@producer.params["prt"]}</td></tr>\n] if @producer.params["prt"]
297
+ s << <<EOT
298
+ </table>
299
+ </div>
300
+ </body>
301
+ </html>
302
+ EOT
303
+ return s
304
+ end
305
+
306
+ # Return own toc content.
307
+ def mytoc
308
+ s = common_header
309
+ s << <<EOT
310
+ <title>#{@producer.res.v("toctitle")}</title>
311
+ </head>
312
+ <body>
313
+ <h1 class="toc-title">#{@producer.res.v("toctitle")}</h1>
314
+ <ul class="toc-h1">
315
+ EOT
316
+
317
+ # FIXME: indent
318
+ current = 1
319
+ init_item = true
320
+ @producer.contents.each do |item|
321
+ next if !item.notoc.nil? || item.level.nil? || item.file.nil? || item.title.nil? || item.level > @producer.params["toclevel"].to_i
322
+ if item.level > current
323
+ s << %Q[\n<ul class="toc-h#{item.level}">\n]
324
+ current = item.level
325
+ elsif item.level < current
326
+ (current - 1).downto(item.level) do |n|
327
+ s << %Q[</li>\n</ul>\n]
328
+ end
329
+ s << %Q[</li>\n]
330
+ current = item.level
331
+ elsif init_item
332
+ # noop
333
+ else
334
+ s << %Q[</li>\n]
335
+ end
336
+ s << %Q[<li><a href="#{item.file}">#{item.title}</a>]
337
+ init_item = false
338
+ end
339
+
340
+ (current - 1).downto(1) do |n|
341
+ s << %Q[</li>\n</ul>\n]
342
+ end
343
+ if !init_item
344
+ s << %Q[</li>\n]
345
+ end
346
+ s << <<EOT
347
+ </ul>
348
+ </body>
349
+ </html>
350
+ EOT
351
+ return s
352
+ end
353
+
354
+ # Produce EPUB file +epubfile+.
355
+ # +basedir+ points the directory has contents.
356
+ # +tmpdir+ defines temporary directory.
357
+ def produce(epubfile, basedir, tmpdir)
358
+ File.open("#{tmpdir}/mimetype", "w") {|f| @producer.mimetype(f) }
359
+
360
+ Dir.mkdir("#{tmpdir}/META-INF") unless File.exist?("#{tmpdir}/META-INF")
361
+ File.open("#{tmpdir}/META-INF/container.xml", "w") {|f| @producer.container(f) }
362
+
363
+ Dir.mkdir("#{tmpdir}/OEBPS") unless File.exist?("#{tmpdir}/OEBPS")
364
+ File.open("#{tmpdir}/OEBPS/#{@producer.params["bookname"]}.opf", "w") {|f| @producer.opf(f) }
365
+ File.open("#{tmpdir}/OEBPS/#{@producer.params["bookname"]}.ncx", "w") {|f| @producer.ncx(f, @producer.params["ncxindent"]) }
366
+ File.open("#{tmpdir}/OEBPS/#{@producer.params["tocfile"]}", "w") {|f| @producer.mytoc(f) } unless @producer.params["mytoc"].nil?
367
+
368
+ if File.exist?("#{basedir}/#{@producer.params["cover"]}")
369
+ FileUtils.cp("#{basedir}/#{@producer.params["cover"]}", "#{tmpdir}/OEBPS")
370
+ else
371
+ File.open("#{tmpdir}/OEBPS/#{@producer.params["cover"]}", "w") {|f| @producer.cover(f) }
372
+ end
373
+
374
+ # FIXME:colophon and titlepage should be included in @producer.contents.
375
+
376
+ @producer.contents.each do |item|
377
+ next if item.file =~ /#/ # skip subgroup
378
+ fname = "#{basedir}/#{item.file}"
379
+ raise "#{fname} doesn't exist. Abort." unless File.exist?(fname)
380
+ FileUtils.mkdir_p(File.dirname("#{tmpdir}/OEBPS/#{item.file}")) unless File.exist?(File.dirname("#{tmpdir}/OEBPS/#{item.file}"))
381
+ FileUtils.cp(fname, "#{tmpdir}/OEBPS/#{item.file}")
382
+ end
383
+
384
+ fork {
385
+ Dir.chdir(tmpdir) {|d|
386
+ exec("zip -0X #{epubfile} mimetype")
387
+ }
388
+ }
389
+ Process.waitall
390
+ fork {
391
+ Dir.chdir(tmpdir) {|d|
392
+ exec("zip -Xr9D #{epubfile} META-INF OEBPS")
393
+ }
394
+ }
395
+ Process.waitall
396
+ end
397
+
398
+ private
399
+
400
+ # Return common XHTML headder
401
+ def common_header
402
+ s =<<EOT
403
+ <?xml version="1.0" encoding="UTF-8"?>
404
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
405
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ops="http://www.idpf.org/2007/ops" xml:lang="#{@producer.params["language"]}">
406
+ <head>
407
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
408
+ <meta http-equiv="Content-Style-Type" content="text/css"/>
409
+ <meta name="generator" content="EPUBMaker::Producer"/>
410
+ EOT
411
+
412
+ @producer.params["stylesheet"].each do |file|
413
+ s << %Q[ <link rel="stylesheet" type="text/css" href="#{file}"/>\n]
414
+ end
415
+ return s
416
+ end
417
+ end
418
+
419
+ end