review 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +1 -0
  3. data/ChangeLog +100 -0
  4. data/README.rdoc +11 -12
  5. data/Rakefile +6 -2
  6. data/bin/review-check +1 -1
  7. data/bin/review-compile +13 -4
  8. data/bin/review-epubmaker +172 -24
  9. data/bin/review-epubmaker-ng +8 -161
  10. data/bin/review-index +1 -1
  11. data/bin/review-init +17 -5
  12. data/bin/review-pdfmaker +40 -19
  13. data/bin/review-preproc +1 -1
  14. data/bin/review-validate +2 -2
  15. data/bin/review-vol +1 -1
  16. data/debian/control +2 -2
  17. data/doc/format.rdoc +23 -6
  18. data/doc/format_idg.rdoc +3 -3
  19. data/doc/libepubmaker/config.yaml +163 -0
  20. data/doc/quickstart.rdoc +27 -27
  21. data/doc/sample.yaml +10 -5
  22. data/lib/epubmaker.rb +2 -5
  23. data/lib/epubmaker/content.rb +9 -6
  24. data/lib/epubmaker/epubv2.rb +233 -109
  25. data/lib/epubmaker/epubv3.rb +83 -119
  26. data/lib/epubmaker/producer.rb +50 -20
  27. data/lib/epubmaker/resource.rb +22 -8
  28. data/lib/review/book/base.rb +1 -0
  29. data/lib/review/book/chapter.rb +2 -2
  30. data/lib/review/book/compilable.rb +1 -1
  31. data/lib/review/book/index.rb +33 -27
  32. data/lib/review/book/parameters.rb +3 -2
  33. data/lib/review/book/part.rb +1 -1
  34. data/lib/review/builder.rb +10 -42
  35. data/lib/review/compiler.rb +1 -1
  36. data/lib/review/configure.rb +3 -3
  37. data/lib/review/epubmaker.rb +428 -0
  38. data/lib/review/htmlbuilder.rb +45 -95
  39. data/lib/review/htmlutils.rb +2 -0
  40. data/lib/review/i18n.yaml +25 -0
  41. data/lib/review/idgxmlbuilder.rb +11 -9
  42. data/lib/review/inaobuilder.rb +1 -1
  43. data/lib/review/latexbuilder.rb +7 -6
  44. data/lib/review/latexutils.rb +6 -0
  45. data/lib/review/makerhelper.rb +4 -2
  46. data/lib/review/markdownbuilder.rb +8 -0
  47. data/lib/review/sec_counter.rb +71 -0
  48. data/lib/review/topbuilder.rb +0 -1
  49. data/lib/review/version.rb +2 -2
  50. data/review.gemspec +4 -4
  51. data/test/sample-book/README.md +2 -2
  52. data/test/sample-book/src/Rakefile +2 -2
  53. data/test/sample-book/src/config.yml +4 -4
  54. data/test/sample-book/src/images/cover.jpg +0 -0
  55. data/test/sample-book/src/sty/{samplemacro.sty → reviewmacro.sty} +1 -1
  56. data/test/test_book_parameter.rb +1 -1
  57. data/test/test_epubmaker.rb +77 -15
  58. data/test/test_epubmaker_cmd.rb +11 -7
  59. data/test/test_helper.rb +7 -0
  60. data/test/test_htmlbuilder.rb +39 -6
  61. data/test/test_idgxmlbuilder.rb +14 -2
  62. data/test/test_inaobuilder.rb +2 -1
  63. data/test/test_latexbuilder.rb +23 -2
  64. data/test/test_makerhelper.rb +19 -3
  65. data/test/test_markdownbuilder.rb +35 -0
  66. data/test/test_pdfmaker_cmd.rb +11 -7
  67. data/test/test_topbuilder.rb +36 -2
  68. metadata +18 -18
  69. data/VERSION +0 -1
  70. data/doc/libepubmaker/sample.yaml +0 -90
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
  # = resource.rb -- Message resources for EPUBMaker.
3
3
  #
4
- # Copyright (c) 2010 Kenshi Muto
4
+ # Copyright (c) 2010-2013 Kenshi Muto
5
5
  #
6
6
  # This program is free software.
7
7
  # You can distribute or modify this program under the terms of
@@ -21,44 +21,58 @@ module EPUBMaker
21
21
  rescue
22
22
  @hash = __send__ :en
23
23
  end
24
-
24
+
25
25
  @hash.each_pair do |k, v|
26
26
  @hash[k] = params[k] unless params[k].nil?
27
27
  end
28
28
  end
29
-
29
+
30
30
  # Return message translation for +key+.
31
31
  def v(key)
32
32
  return @hash[key]
33
33
  end
34
-
34
+
35
35
  private
36
- # English messages.
36
+ # English message catalog
37
37
  def en
38
38
  {
39
39
  "toctitle" => "Table of Contents",
40
40
  "covertitle" => "Cover",
41
41
  "titlepagetitle" => "Title Page",
42
+ "originaltitle" => "Title Page of Original",
43
+ "credittitle" => "Credit",
42
44
  "colophontitle" => "Colophon",
45
+ "profiletitle" => "Profile",
46
+ "advtitle" => "Advertisement",
43
47
  "c-aut" => "Author",
48
+ "c-csl" => "Consultant",
44
49
  "c-dsr" => "Designer",
45
50
  "c-ill" => "Illustrator",
46
51
  "c-edt" => "Editor",
52
+ "c-pht" => "Director of Photography",
53
+ "c-trl" => "Translator",
47
54
  "c-prt" => "Publisher",
48
55
  }
49
56
  end
50
-
51
- # Japanese messages.
57
+
58
+ # Japanese message catalog
52
59
  def ja
53
60
  {
54
61
  "toctitle" => "目次",
55
62
  "covertitle" => "表紙",
56
- "titlepagetitle" => "権利表記",
63
+ "titlepagetitle" => "大扉",
64
+ "originaltitle" => "原書大扉",
65
+ "credittitle" => "クレジット",
57
66
  "colophontitle" => "奥付",
67
+ "advtitle" => "広告",
68
+ "profiletitle" => "著者紹介",
58
69
  "c-aut" => "著 者",
70
+ "c-csl" => "監 修",
59
71
  "c-dsr" => "デザイン",
60
72
  "c-ill" => "イラスト",
61
73
  "c-edt" => "編 集",
74
+ "c-pht" => "撮 影",
75
+ "c-trl" => "翻 訳",
62
76
  "c-prt" => "発行所",
63
77
  }
64
78
  end
@@ -64,6 +64,7 @@ module ReVIEW
64
64
  :ext,
65
65
  :image_dir,
66
66
  :image_types,
67
+ :image_types=,
67
68
  :page_metric
68
69
 
69
70
  def parts
@@ -22,8 +22,8 @@ module ReVIEW
22
22
  book = (books[File.expand_path(basedir)] ||= Book.load(basedir))
23
23
  begin
24
24
  book.chapter(File.basename(path, '.*'))
25
- rescue KeyError => err
26
- raise FileNotFound, "no such file: #{path}"
25
+ rescue KeyError
26
+ raise FileNotFound, "No such chapter in your book. Check if the catalog files contain the chapter. : #{path}"
27
27
  end
28
28
  }
29
29
  end
@@ -43,7 +43,7 @@ module ReVIEW
43
43
  f.each_line {|l|
44
44
  l = convert_inencoding(l, ReVIEW.book.param["inencoding"])
45
45
  if l =~ /\A=+/
46
- @title = l.sub(/\A=+/, '').strip
46
+ @title = l.sub(/\A=+(\[.+?\])?(\{.+?\})?/, '').strip
47
47
  break
48
48
  end
49
49
  }
@@ -132,6 +132,8 @@ module ReVIEW
132
132
 
133
133
  class ImageIndex < Index
134
134
  class Item
135
+ @@entries = nil
136
+
135
137
  def initialize(id, number)
136
138
  @id = id
137
139
  @number = number
@@ -167,39 +169,43 @@ module ReVIEW
167
169
  @chapid = chapid
168
170
  @basedir = basedir
169
171
  @types = types
172
+
173
+ @@entries ||= get_entries
174
+ end
175
+
176
+ def get_entries
177
+ Dir.glob(File.join(@basedir, "**/*.*"))
170
178
  end
171
179
 
172
180
  # internal use only
173
181
  def find_pathes(id)
174
- if ReVIEW.book.param["subdirmode"]
175
- re = /\A#{id}(?i:#{@types.join('|')})\z/x
176
- entries().select {|ent| re =~ ent }\
177
- .sort_by {|ent| @types.index(File.extname(ent).downcase) }\
178
- .map {|ent| "#{@basedir}/#{@chapid}/#{ent}" }
179
- elsif ReVIEW.book.param["singledirmode"]
180
- re = /\A#{id}(?i:#{@types.join('|')})\z/x
181
- entries().select {|ent| re =~ ent }\
182
- .sort_by {|ent| @types.index(File.extname(ent).downcase) }\
183
- .map {|ent| "#{@basedir}/#{ent}" }
184
- else
185
- re = /\A#{@chapid.gsub('+', '\\\+').gsub('-', '\\\-')}-#{id}(?i:#{@types.join('|')})\z/x
186
- entries().select {|ent| re =~ ent }\
187
- .sort_by {|ent| @types.index(File.extname(ent).downcase) }\
188
- .map {|ent| "#{@basedir}/#{ent}" }
189
- end
190
- end
182
+ pathes = []
191
183
 
192
- private
184
+ # 1. <basedir>/<builder>/<chapid>/<id>.<ext>
185
+ target = "#{@basedir}/#{ReVIEW.book.param['builder']}/#{@chapid}/#{id}"
186
+ @types.each {|ext| pathes.push("#{target}#{ext}") if @@entries.include?("#{target}#{ext}")}
193
187
 
194
- def entries
195
- # @entries: do not cache for graph
196
- if ReVIEW.book.param["subdirmode"]
197
- @entries = Dir.entries(File.join(@basedir, @chapid))
198
- else
199
- @entries = Dir.entries(@basedir)
200
- end
201
- rescue Errno::ENOENT
202
- @entries = []
188
+ # 2. <basedir>/<builder>/<chapid>-<id>.<ext>
189
+ target = "#{@basedir}/#{ReVIEW.book.param['builder']}/#{@chapid}-#{id}"
190
+ @types.each {|ext| pathes.push("#{target}#{ext}") if @@entries.include?("#{target}#{ext}")}
191
+
192
+ # 3. <basedir>/<builder>/<id>.<ext>
193
+ target = "#{@basedir}/#{ReVIEW.book.param['builder']}/#{id}"
194
+ @types.each {|ext| pathes.push("#{target}#{ext}") if @@entries.include?("#{target}#{ext}")}
195
+
196
+ # 4. <basedir>/<chapid>/<id>.<ext>
197
+ target = "#{@basedir}/#{@chapid}/#{id}"
198
+ @types.each {|ext| pathes.push("#{target}#{ext}") if @@entries.include?("#{target}#{ext}")}
199
+
200
+ # 5. <basedir>/<chapid>-<id>.<ext>
201
+ target = "#{@basedir}/#{@chapid}-#{id}"
202
+ @types.each {|ext| pathes.push("#{target}#{ext}") if @@entries.include?("#{target}#{ext}")}
203
+
204
+ # 6. <basedir>/<id>.<ext>
205
+ target = "#{@basedir}/#{id}"
206
+ @types.each {|ext| pathes.push("#{target}#{ext}") if @@entries.include?("#{target}#{ext}")}
207
+
208
+ return pathes
203
209
  end
204
210
  end
205
211
 
@@ -45,7 +45,8 @@ module ReVIEW
45
45
  PageMetric.new(const_get_safe(mod, :LINES_PER_PAGE_list) || 46,
46
46
  const_get_safe(mod, :COLUMNS_list) || 80,
47
47
  const_get_safe(mod, :LINES_PER_PAGE_text) || 30,
48
- const_get_safe(mod, :COLUMNS_text) || 74) # 37zw
48
+ const_get_safe(mod, :COLUMNS_text) || 74, # 37zw
49
+ const_get_safe(mod, :PAGE_PER_KBYTE) || 1) # 37zw
49
50
  end
50
51
 
51
52
  def Parameters.const_get_safe(mod, name)
@@ -89,7 +90,7 @@ module ReVIEW
89
90
  path_param :postdef_file
90
91
  attr_reader :ext
91
92
  path_param :image_dir
92
- attr_reader :image_types
93
+ attr_accessor :image_types
93
94
  attr_reader :page_metric
94
95
 
95
96
  end
@@ -20,7 +20,7 @@ module ReVIEW
20
20
  @number = number
21
21
  @chapters = chapters
22
22
  @path = name
23
- @name = name ? File.basename(name, '.*') : nil
23
+ @name = name ? File.basename(name, '.re') : nil
24
24
  end
25
25
 
26
26
  attr_reader :number
@@ -10,6 +10,7 @@
10
10
  require 'review/book/index'
11
11
  require 'review/exception'
12
12
  require 'review/textutils'
13
+ require 'review/compiler'
13
14
  require 'stringio'
14
15
  require 'cgi'
15
16
 
@@ -60,9 +61,9 @@ module ReVIEW
60
61
  alias :raw_result result
61
62
 
62
63
  def print(*s)
63
- @output.print *s.map{|i|
64
+ @output.print(*s.map{|i|
64
65
  convert_outencoding(i, ReVIEW.book.param["outencoding"])
65
- }
66
+ })
66
67
  end
67
68
 
68
69
  def puts(*s)
@@ -119,7 +120,7 @@ module ReVIEW
119
120
 
120
121
  begin
121
122
  table_header id, caption unless caption.nil?
122
- rescue KeyError => err
123
+ rescue KeyError
123
124
  error "no such table: #{id}"
124
125
  end
125
126
  return if rows.empty?
@@ -171,7 +172,7 @@ module ReVIEW
171
172
  end
172
173
 
173
174
  def inline_chapref(id)
174
- @chapter.env.chapter_index.display_string(id)
175
+ compile_inline @chapter.env.chapter_index.display_string(id)
175
176
  rescue KeyError
176
177
  error "unknown chapter: #{id}"
177
178
  nofunc_text("[UnknownChapter:#{id}]")
@@ -276,35 +277,6 @@ module ReVIEW
276
277
  end
277
278
  end
278
279
 
279
- def find_pathes(id)
280
- if ReVIEW.book.param["subdirmode"]
281
- re = /\A#{id}(?i:#{@chapter.name.join('|')})\z/x
282
- entries().select {|ent| re =~ ent }\
283
- .sort_by {|ent| @book.image_types.index(File.extname(ent).downcase) }\
284
- .map {|ent| "#{@book.basedir}/#{@chapter.name}/#{ent}" }
285
- elsif ReVIEW.book.param["singledirmode"]
286
- re = /\A#{id}(?i:#{@chapter.name.join('|')})\z/x
287
- entries().select {|ent| re =~ ent }\
288
- .sort_by {|ent| @book.image_types.index(File.extname(ent).downcase) }\
289
- .map {|ent| "#{@book.basedir}/#{ent}" }
290
- else
291
- re = /\A#{@chapter.name}-#{id}(?i:#{@book.image_types.join('|')})\z/x
292
- entries().select {|ent| re =~ ent }\
293
- .sort_by {|ent| @book.image_types.index(File.extname(ent).downcase) }\
294
- .map {|ent| "#{@book.basedir}/#{ent}" }
295
- end
296
- end
297
-
298
- def entries
299
- if ReVIEW.book.param["subdirmode"]
300
- @entries ||= Dir.entries(File.join(@book.basedir + @book.image_dir, @chapter.name))
301
- else
302
- @entries ||= Dir.entries(@book.basedir + @book.image_dir)
303
- end
304
- rescue Errno::ENOENT
305
- @entries = []
306
- end
307
-
308
280
  def warn(msg)
309
281
  $stderr.puts "#{@location}: warning: #{msg}"
310
282
  end
@@ -368,14 +340,10 @@ module ReVIEW
368
340
  end
369
341
 
370
342
  def graph(lines, id, command, caption = nil)
371
- dir = @book.basedir + @book.image_dir
372
- file = "#{@chapter.name}-#{id}.#{image_ext}"
373
- if ReVIEW.book.param["subdirmode"]
374
- dir = File.join(dir, @chapter.name)
375
- file = "#{id}.#{image_ext}"
376
- elsif ReVIEW.book.param["singledirmode"]
377
- file = "#{id}.#{image_ext}"
378
- end
343
+ c = self.class.to_s.gsub(/ReVIEW::/, '').gsub(/Builder/, '').downcase
344
+ dir = File.join(@book.basedir, @book.image_dir, c)
345
+ Dir.mkdir(dir) unless File.exist?(dir)
346
+ file = "#{id}.#{image_ext}"
379
347
  file_path = File.join(dir, file)
380
348
 
381
349
  line = CGI.unescapeHTML(lines.join("\n"))
@@ -392,7 +360,7 @@ module ReVIEW
392
360
  warn cmd
393
361
  system cmd
394
362
 
395
- image(lines, id, caption)
363
+ image(lines, id, caption ||= "")
396
364
  end
397
365
 
398
366
  def image_ext
@@ -412,7 +412,7 @@ module ReVIEW
412
412
 
413
413
  def compile_paragraph(f)
414
414
  buf = []
415
- f.until_match(%r<\A//|\A\#@|\A===+>) do |line|
415
+ f.until_match(%r<\A//|\A\#@>) do |line|
416
416
  break if line.strip.empty?
417
417
  buf.push text(line.sub(/^(\t+)\s*/) {|m| "<!ESCAPETAB!>" * m.size}.strip.gsub(/<!ESCAPETAB!>/, "\t"))
418
418
  end
@@ -4,8 +4,8 @@ module ReVIEW
4
4
  def self.values
5
5
  { # These parameters can be overridden by YAML file.
6
6
  "bookname"=> "example", # it defines epub file name also
7
- "booktitle" => "ReVIEW EPUBサンプル",
8
- "title" => "example",
7
+ "booktitle" => "Re:VIEW EPUBサンプル",
8
+ "title" => nil,
9
9
  "aut" => nil, # author
10
10
  "prt" => nil, # printer(publisher)
11
11
  "asn" => nil, # associated name
@@ -30,7 +30,7 @@ module ReVIEW
30
30
  "epubversion" => 2,
31
31
  "titlepage" => true, # Use title page
32
32
  "toc" => true, # Use table of contents
33
- "colophon" => false, # Use colophon
33
+ "colophon" => nil, # Use colophon
34
34
  "debug" => nil, # debug flag
35
35
  }
36
36
  end
@@ -0,0 +1,428 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Copyright (c) 2010-2014 Kenshi Muto and Masayoshi Takahashi
4
+ #
5
+ # This program is free software.
6
+ # You can distribute or modify this program under the terms of
7
+ # the GNU LGPL, Lesser General Public License version 2.1.
8
+ # For details of the GNU LGPL, see the file "COPYING".
9
+ #
10
+ require 'review'
11
+ require 'rexml/document'
12
+ require 'rexml/streamlistener'
13
+ require 'epubmaker'
14
+
15
+ module ReVIEW
16
+ class EPUBMaker
17
+ include ::EPUBMaker
18
+ include REXML
19
+
20
+ def initialize
21
+ @epub = nil
22
+ @tochtmltxt = "toc-html.txt"
23
+ end
24
+
25
+ def log(s)
26
+ puts s unless @params["debug"].nil?
27
+ end
28
+
29
+ def load_yaml(yamlfile)
30
+ @params = ReVIEW::Configure.values.merge(YAML.load_file(yamlfile)) # FIXME:設定がRe:VIEW側とepubmaker/producer.rb側の2つに分かれて面倒
31
+ @epub = Producer.new(@params)
32
+ @epub.load(yamlfile)
33
+ @params = @epub.params
34
+ end
35
+
36
+ def produce(yamlfile, bookname=nil)
37
+ load_yaml(yamlfile)
38
+ bookname = @params["bookname"] if bookname.nil?
39
+ log("Loaded yaml file (#{yamlfile}). I will produce #{bookname}.epub.")
40
+
41
+ File.unlink("#{bookname}.epub") if File.exist?("#{bookname}.epub")
42
+ FileUtils.rm_rf(bookname) if @params["debug"] && File.exist?(bookname)
43
+
44
+ Dir.mktmpdir(bookname, Dir.pwd) do |basetmpdir|
45
+ log("Created first temporary directory as #{basetmpdir}.")
46
+
47
+ log("Call hook_beforeprocess. (#{@params["hook_beforeprocess"]})")
48
+ call_hook(@params["hook_beforeprocess"], basetmpdir)
49
+
50
+ copy_stylesheet(basetmpdir)
51
+
52
+ copy_frontmatter(basetmpdir)
53
+ log("Call hook_afterfrontmatter. (#{@params["hook_afterfrontmatter"]})")
54
+ call_hook(@params["hook_afterfrontmatter"], basetmpdir)
55
+
56
+ build_body(basetmpdir, yamlfile)
57
+ log("Call hook_afterbody. (#{@params["hook_afterbody"]})")
58
+ call_hook(@params["hook_afterbody"], basetmpdir)
59
+
60
+ copy_backmatter(basetmpdir)
61
+ log("Call hook_afterbackmatter. (#{@params["hook_afterbackmatter"]})")
62
+ call_hook(@params["hook_afterbackmatter"], basetmpdir)
63
+
64
+ push_contents(basetmpdir)
65
+
66
+ copy_images(@params["imagedir"], "#{basetmpdir}/images")
67
+ copy_images("covers", "#{basetmpdir}/images")
68
+ copy_images("adv", "#{basetmpdir}/images")
69
+ copy_images(@params["fontdir"], "#{basetmpdir}/fonts", @params["font_ext"])
70
+ log("Call hook_aftercopyimage. (#{@params["hook_aftercopyimage"]})")
71
+ call_hook(@params["hook_aftercopyimage"], basetmpdir)
72
+
73
+ @epub.import_imageinfo("#{basetmpdir}/images", basetmpdir)
74
+ @epub.import_imageinfo("#{basetmpdir}/fonts", basetmpdir, @params["font_ext"])
75
+
76
+ epubtmpdir = @params["debug"].nil? ? nil : "#{Dir.pwd}/#{bookname}"
77
+ Dir.mkdir(bookname) unless @params["debug"].nil?
78
+ log("Call ePUB producer.")
79
+ @epub.produce("#{bookname}.epub", basetmpdir, epubtmpdir)
80
+ log("Finished.")
81
+
82
+ end
83
+ end
84
+
85
+ def call_hook(filename, *params)
86
+ if !filename.nil? && File.exist?(filename) && FileTest.executable?(filename)
87
+ system(filename, *params)
88
+ end
89
+ end
90
+
91
+ def copy_images(imagedir, destdir, allow_exts=nil)
92
+ return nil unless File.exist?(imagedir)
93
+ allow_exts = @params["image_ext"] if allow_exts.nil?
94
+ FileUtils.mkdir_p(destdir) unless FileTest.directory?(destdir)
95
+ recursive_copy_images(imagedir, destdir, allow_exts)
96
+ end
97
+
98
+ def recursive_copy_images(imagedir, destdir, allow_exts)
99
+ Dir.open(imagedir) do |dir|
100
+ dir.each do |fname|
101
+ next if fname =~ /\A\./
102
+ if FileTest.directory?("#{imagedir}/#{fname}")
103
+ recursive_copy_images("#{imagedir}/#{fname}", "#{destdir}/#{fname}", allow_exts)
104
+ else
105
+ if fname =~ /\.(#{allow_exts.join("|")})\Z/i
106
+ Dir.mkdir(destdir) unless File.exist?(destdir)
107
+ log("Copy #{imagedir}/#{fname} to the temporary directory.")
108
+ FileUtils.cp("#{imagedir}/#{fname}", destdir)
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def build_body(basetmpdir, yamlfile)
116
+ @precount = 0
117
+ @bodycount = 0
118
+ @postcount = 0
119
+
120
+ @manifeststr = ""
121
+ @ncxstr = ""
122
+ @tocdesc = Array.new
123
+ # toccount = 2 ## not used
124
+
125
+ basedir = Dir.pwd
126
+ base_path = Pathname.new(basedir)
127
+ ReVIEW::Book.load(basedir).parts.each do |part|
128
+ htmlfile = nil
129
+ if part.name.present?
130
+ if part.file?
131
+ build_chap(part, base_path, basetmpdir, yamlfile, true)
132
+ else
133
+ htmlfile = "part_#{part.number}.#{@params["htmlext"]}"
134
+ build_part(part, basetmpdir, htmlfile)
135
+ title = ReVIEW::I18n.t("part", part.number)
136
+ title += ReVIEW::I18n.t("chapter_postfix") + part.name.strip if part.name.strip.present?
137
+ write_tochtmltxt(basetmpdir, "0\t#{htmlfile}\t#{title}")
138
+ end
139
+ end
140
+
141
+ part.chapters.each do |chap|
142
+ build_chap(chap, base_path, basetmpdir, yamlfile, nil)
143
+ end
144
+
145
+ end
146
+ end
147
+
148
+ def build_part(part, basetmpdir, htmlfile)
149
+ log("Create #{htmlfile} from a template.")
150
+ File.open("#{basetmpdir}/#{htmlfile}", "w") do |f|
151
+ f.puts header(CGI.escapeHTML(@params["booktitle"]))
152
+ f.puts <<EOT
153
+ <div class="part">
154
+ <h1 class="part-number">#{ReVIEW::I18n.t("part", part.number)}</h1>
155
+ EOT
156
+ if part.name.strip.present?
157
+ f.puts <<EOT
158
+ <h2 class="part-title">#{part.name.strip}</h2>
159
+ EOT
160
+ end
161
+
162
+ f.puts <<EOT
163
+ </div>
164
+ EOT
165
+ f.puts footer
166
+ end
167
+ end
168
+
169
+ def build_chap(chap, base_path, basetmpdir, yamlfile, ispart=nil)
170
+ filename = ""
171
+ if !ispart.nil?
172
+ filename = chap.path
173
+ else
174
+ filename = Pathname.new(chap.path).relative_path_from(base_path).to_s
175
+ end
176
+ id = filename.sub(/\.re\Z/, "")
177
+
178
+ if @params["rename_for_legacy"] && ispart.nil?
179
+ if chap.on_PREDEF?
180
+ @precount += 1
181
+ id = sprintf("pre%02d", @precount)
182
+ elsif chap.on_POSTDEF?
183
+ @postcount += 1
184
+ id = sprintf("post%02d", @postcount)
185
+ else
186
+ @bodycount += 1
187
+ id = sprintf("chap%02d", @bodycount)
188
+ end
189
+ end
190
+
191
+ htmlfile = "#{id}.#{@params["htmlext"]}"
192
+ log("Create #{htmlfile} from #{filename}.")
193
+
194
+ level = @params["secnolevel"]
195
+
196
+ if !ispart.nil?
197
+ level = @params["part_secnolevel"]
198
+ else
199
+ level = @params["pre_secnolevel"] if chap.on_PREDEF?
200
+ level = @params["post_secnolevel"] if chap.on_POSTDEF?
201
+ end
202
+
203
+ stylesheet = ""
204
+ if @params["stylesheet"].size > 0
205
+ stylesheet = "--stylesheet=#{@params["stylesheet"].join(",")}"
206
+ end
207
+
208
+ system("review-compile --yaml=#{yamlfile} --target=html --level=#{level} --htmlversion=#{@params["htmlversion"]} --epubversion=#{@params["epubversion"]} #{stylesheet} #{@params["params"]} #{filename} > \"#{basetmpdir}/#{htmlfile}\"")
209
+
210
+ write_info_body(basetmpdir, id, htmlfile, ispart)
211
+ end
212
+
213
+ def write_info_body(basetmpdir, id, filename, ispart=nil)
214
+ headlines = []
215
+ # FIXME:nonumを修正する必要あり
216
+ Document.parse_stream(File.new("#{basetmpdir}/#{filename}"), ReVIEWHeaderListener.new(headlines))
217
+ first = true
218
+ headlines.each do |headline|
219
+ headline["level"] = 0 if !ispart.nil? && headline["level"] == 1
220
+ if first.nil?
221
+ write_tochtmltxt(basetmpdir, "#{headline["level"]}\t#{filename}##{headline["id"]}\t#{headline["title"]}")
222
+ else
223
+ write_tochtmltxt(basetmpdir, "#{headline["level"]}\t#{filename}\t#{headline["title"]}\tforce_include=true")
224
+ first = nil
225
+ end
226
+ end
227
+ end
228
+
229
+ def push_contents(basetmpdir)
230
+ File.open("#{basetmpdir}/#{@tochtmltxt}") do |f|
231
+ f.each_line do |l|
232
+ force_include = nil
233
+ customid = nil
234
+ level, file, title, custom = l.chomp.split("\t")
235
+ unless custom.nil?
236
+ # custom setting
237
+ vars = custom.split(/,\s*/)
238
+ vars.each do |var|
239
+ k, v = var.split("=")
240
+ case k
241
+ when "id"
242
+ customid = v
243
+ when "force_include"
244
+ force_include = true
245
+ end
246
+ end
247
+ end
248
+ next if level.to_i > @params["toclevel"] && force_include.nil?
249
+ log("Push #{file} to ePUB contents.")
250
+
251
+ if customid.nil?
252
+ @epub.contents.push(Content.new("file" => file, "level" => level.to_i, "title" => title))
253
+ else
254
+ @epub.contents.push(Content.new("id" => customid, "file" => file, "level" => level.to_i, "title" => title))
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ def copy_stylesheet(basetmpdir)
261
+ if @params["stylesheet"].size > 0
262
+ @params["stylesheet"].each do |sfile|
263
+ FileUtils.cp(sfile, basetmpdir)
264
+ @epub.contents.push(Content.new("file" => sfile))
265
+ end
266
+ end
267
+ end
268
+
269
+ def copy_frontmatter(basetmpdir)
270
+ FileUtils.cp(@params["cover"], "#{basetmpdir}/#{File.basename(@params["cover"])}") if !@params["cover"].nil? && File.exist?(@params["cover"])
271
+
272
+ if @params["titlepage"]
273
+ if @params["titlepagefile"].nil?
274
+ build_titlepage(basetmpdir, "titlepage.#{@params["htmlext"]}")
275
+ else
276
+ FileUtils.cp(@params["titlepagefile"], "titlepage.#{@params["htmlext"]}")
277
+ end
278
+ write_tochtmltxt(basetmpdir, "1\ttitlepage.#{@params["htmlext"]}\t#{@epub.res.v("titlepagetitle")}")
279
+ end
280
+
281
+ if !@params["originaltitlefile"].nil? && File.exist?(@params["originaltitlefile"])
282
+ FileUtils.cp(@params["originaltitlefile"], "#{basetmpdir}/#{File.basename(@params["originaltitlefile"])}")
283
+ write_tochtmltxt(basetmpdir, "1\t#{File.basename(@params["originaltitlefile"])}\t#{@epub.res.v("originaltitle")}")
284
+ end
285
+
286
+ if !@params["creditfile"].nil? && File.exist?(@params["creditfile"])
287
+ FileUtils.cp(@params["creditfile"], "#{basetmpdir}/#{File.basename(@params["creditfile"])}")
288
+ write_tochtmltxt(basetmpdir, "1\t#{File.basename(@params["creditfile"])}\t#{@epub.res.v("credittitle")}")
289
+ end
290
+ end
291
+
292
+ def build_titlepage(basetmpdir, htmlfile)
293
+ File.open("#{basetmpdir}/#{htmlfile}", "w") do |f|
294
+ f.puts header(CGI.escapeHTML(@params["booktitle"]))
295
+ f.puts <<EOT
296
+ <div class="titlepage">
297
+ <h1 class="tp-title">#{CGI.escapeHTML(@params["booktitle"])}</h1>
298
+ EOT
299
+
300
+ if @params["aut"]
301
+ f.puts <<EOT
302
+ <h2 class="tp-author">#{@params["aut"].join(", ")}</h2>
303
+ EOT
304
+ end
305
+ if @params["prt"]
306
+ f.puts <<EOT
307
+ <h3 class="tp-publisher">#{@params["prt"].join(", ")}</h3>
308
+ EOT
309
+ end
310
+
311
+ f.puts <<EOT
312
+ </div>
313
+ EOT
314
+ f.puts footer
315
+ end
316
+ end
317
+
318
+ def copy_backmatter(basetmpdir)
319
+ if @params["profile"]
320
+ FileUtils.cp(@params["profile"], "#{basetmpdir}/#{File.basename(@params["profile"])}")
321
+ write_tochtmltxt(basetmpdir, "1\t#{File.basename(@params["profile"])}\t#{@epub.res.v("profiletitle")}")
322
+ end
323
+
324
+ if @params["advfile"]
325
+ FileUtils.cp(@params["advfile"], "#{basetmpdir}/#{File.basename(@params["advfile"])}")
326
+ write_tochtmltxt(basetmpdir, "1\t#{File.basename(@params["advfile"])}\t#{@epub.res.v("advtitle")}")
327
+ end
328
+
329
+ if @params["colophon"]
330
+ if @params["colophon"].instance_of?(String) # FIXME:このやり方はやめる?
331
+ FileUtils.cp(@params["colophon"], "#{basetmpdir}/colophon.#{@params["htmlext"]}")
332
+ else
333
+ File.open("#{basetmpdir}/colophon.#{@params["htmlext"]}", "w") {|f| @epub.colophon(f) }
334
+ end
335
+ write_tochtmltxt(basetmpdir, "1\tcolophon.#{@params["htmlext"]}\t#{@epub.res.v("colophontitle")}")
336
+ end
337
+ end
338
+
339
+ def write_tochtmltxt(basetmpdir, s)
340
+ File.open("#{basetmpdir}/#{@tochtmltxt}", "a") do |f|
341
+ f.puts s
342
+ end
343
+ end
344
+
345
+ def header(title)
346
+ # titleはすでにエスケープ済みと想定
347
+ s = <<EOT
348
+ <?xml version="1.0" encoding="UTF-8"?>
349
+ EOT
350
+ if @params["htmlversion"] == 5
351
+ s << <<EOT
352
+ <!DOCTYPE html>
353
+ <html xml:lang='ja' xmlns:ops='http://www.idpf.org/2007/ops' xmlns='http://www.w3.org/1999/xhtml'>
354
+ <head>
355
+ <meta charset="UTF-8" />
356
+ EOT
357
+ else
358
+ s << <<EOT
359
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
360
+ <html xml:lang='ja' xmlns:ops='http://www.idpf.org/2007/ops' xmlns='http://www.w3.org/1999/xhtml'>
361
+ <head>
362
+ <meta http-equiv='Content-Type' content='text/html;charset=UTF-8' />
363
+ <meta http-equiv='Content-Style-Type' content='text/css' />
364
+ EOT
365
+ end
366
+ if @params["stylesheet"].size > 0
367
+ @params["stylesheet"].each do |sfile|
368
+ s << <<EOT
369
+ <link rel='stylesheet' type='text/css' href='#{sfile}' />
370
+ EOT
371
+ end
372
+ end
373
+ s << <<EOT
374
+ <meta content='Re:VIEW' name='generator'/>
375
+ <title>#{title}</title>
376
+ </head>
377
+ <body>
378
+ EOT
379
+ end
380
+
381
+ def footer
382
+ <<EOT
383
+ </body>
384
+ </html>
385
+ EOT
386
+ end
387
+
388
+ class ReVIEWHeaderListener
389
+ include REXML::StreamListener
390
+ def initialize(headlines)
391
+ @level = nil
392
+ @content = ""
393
+ @headlines = headlines
394
+ end
395
+
396
+ def tag_start(name, attrs)
397
+ if name =~ /\Ah(\d+)/
398
+ unless @level.nil?
399
+ raise "#{name}, #{attrs}"
400
+ end
401
+ @level = $1.to_i
402
+ @id = attrs["id"] if !attrs["id"].nil?
403
+ elsif !@level.nil?
404
+ if name == "img" && !attrs["alt"].nil?
405
+ @content << attrs["alt"]
406
+ elsif name == "a" && !attrs["id"].nil?
407
+ @id = attrs["id"]
408
+ end
409
+ end
410
+ end
411
+
412
+ def tag_end(name)
413
+ if name =~ /\Ah\d+/
414
+ @headlines.push({"level" => @level, "id" => @id, "title" => @content}) unless @id.nil?
415
+ @content = ""
416
+ @level = nil
417
+ @id = nil
418
+ end
419
+ end
420
+
421
+ def text(text)
422
+ unless @level.nil?
423
+ @content << text.gsub("\t", " ") # FIXME:区切り文字
424
+ end
425
+ end
426
+ end
427
+ end
428
+ end