sudothinker-eeepub 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,125 @@
1
+ require 'tmpdir'
2
+ require 'fileutils'
3
+
4
+ module EeePub
5
+ # The class to make ePub easily
6
+ #
7
+ # @example
8
+ # epub = EeePub.make do
9
+ # title 'sample'
10
+ # creator 'jugyo'
11
+ # publisher 'jugyo.org'
12
+ # date '2010-05-06'
13
+ # identifier 'http://example.com/book/foo', :scheme => 'URL'
14
+ # uid 'http://example.com/book/foo'
15
+ #
16
+ # files ['/path/to/foo.html', '/path/to/bar.html']
17
+ # nav [
18
+ # {:label => '1. foo', :content => 'foo.html', :nav => [
19
+ # {:label => '1.1 foo-1', :content => 'foo.html#foo-1'}
20
+ # ]},
21
+ # {:label => '1. bar', :content => 'bar.html'}
22
+ # ]
23
+ # end
24
+ # epub.save('sample.epub')
25
+ class Maker
26
+ [
27
+ :title,
28
+ :creator,
29
+ :publisher,
30
+ :date,
31
+ :language,
32
+ :subject,
33
+ :description,
34
+ :rights,
35
+ :relation
36
+ ].each do |name|
37
+ class_eval <<-DELIM
38
+ def #{name}(value)
39
+ @#{name}s ||= []
40
+ @#{name}s << value
41
+ end
42
+ DELIM
43
+ end
44
+
45
+ [
46
+ :uid,
47
+ :files,
48
+ :nav,
49
+ :ncx_file,
50
+ :opf_file
51
+ ].each do |name|
52
+ define_method(name) do |arg|
53
+ instance_variable_set("@#{name}", arg)
54
+ end
55
+ end
56
+
57
+ def identifier(id, options)
58
+ @identifiers ||= []
59
+ @identifiers << {:value => id, :scheme => options[:scheme]}
60
+ end
61
+
62
+ # @param [Proc] block the block for initialize
63
+ def initialize(&block)
64
+ @files ||= []
65
+ @nav ||= []
66
+ @ncx_file ||= 'toc.ncx'
67
+ @opf_file ||= 'content.opf'
68
+
69
+ instance_eval(&block) if block_given?
70
+ end
71
+
72
+ # Save as ePub file
73
+ #
74
+ # @param [String] filename the ePub file name to save
75
+ def save(filename)
76
+ Dir.mktmpdir do |dir|
77
+ @files.each do |file|
78
+ case file
79
+ when String
80
+ FileUtils.cp(file, dir)
81
+ when Hash
82
+ file_path, dir_path = *file.first
83
+ dest_dir = File.join(dir, dir_path)
84
+ FileUtils.mkdir_p(dest_dir)
85
+ FileUtils.cp(file_path, dest_dir)
86
+ end
87
+ end
88
+
89
+ NCX.new(
90
+ :uid => @uid,
91
+ :title => @titles[0],
92
+ :nav => @nav
93
+ ).save(File.join(dir, @ncx_file))
94
+
95
+ OPF.new(
96
+ :title => @titles,
97
+ :identifier => @identifiers,
98
+ :creator => @creators,
99
+ :publisher => @publishers,
100
+ :date => @dates,
101
+ :language => @languages,
102
+ :subject => @subjects,
103
+ :description => @descriptions,
104
+ :rights => @rightss,
105
+ :relation => @relations,
106
+ :manifest => @files.map{|file|
107
+ case file
108
+ when String
109
+ File.basename(file)
110
+ when Hash
111
+ file_path, dir_path = *file.first
112
+ File.join(dir_path, File.basename(file_path))
113
+ end
114
+ },
115
+ :ncx => @ncx_file
116
+ ).save(File.join(dir, @opf_file))
117
+
118
+ OCF.new(
119
+ :dir => dir,
120
+ :container => @opf_file
121
+ ).save(filename)
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,68 @@
1
+ module EeePub
2
+ class NCX < ContainerItem
3
+ attr_accessor :uid,
4
+ :depth,
5
+ :total_page_count,
6
+ :max_page_number,
7
+ :doc_title,
8
+ :nav_map
9
+
10
+ attr_alias :title, :doc_title
11
+ attr_alias :nav, :nav_map
12
+
13
+ default_value :depth, 1
14
+ default_value :total_page_count, 0
15
+ default_value :max_page_number, 0
16
+ default_value :doc_title, 'Untitled'
17
+
18
+ def build_xml(builder)
19
+ builder.declare! :DOCTYPE, :ncx, :PUBLIC, "-//NISO//DTD ncx 2005-1//EN", "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"
20
+ builder.ncx :xmlns => "http://www.daisy.org/z3986/2005/ncx/", :version => "2005-1" do
21
+ build_head(builder)
22
+ builder.docTitle { builder.text doc_title }
23
+ build_nav_map(builder)
24
+ end
25
+ end
26
+
27
+ def build_head(builder)
28
+ builder.head do
29
+ {
30
+ :uid => uid,
31
+ :depth => depth,
32
+ :totalPageCount => total_page_count,
33
+ :maxPageNumber => max_page_number
34
+ }.each do |k, v|
35
+ builder.meta :name => "dtb:#{k}", :content => v
36
+ end
37
+ end
38
+ end
39
+
40
+ def build_nav_map(builder)
41
+ builder.navMap do
42
+ builder_nav_point(builder, nav_map)
43
+ end
44
+ end
45
+
46
+ def builder_nav_point(builder, nav_point, play_order = 1)
47
+ case nav_point
48
+ when Array
49
+ nav_point.each do |point|
50
+ play_order = builder_nav_point(builder, point, play_order)
51
+ end
52
+ when Hash
53
+ id = nav_point[:id] || "navPoint-#{play_order}"
54
+ builder.navPoint :id => id, :playOrder => play_order do
55
+ builder.navLabel { builder.text nav_point[:label] }
56
+ builder.content :src => nav_point[:content]
57
+ play_order += 1
58
+ if nav_point[:nav]
59
+ play_order = builder_nav_point(builder, nav_point[:nav], play_order)
60
+ end
61
+ end
62
+ else
63
+ raise "nav_point must be Array or Hash"
64
+ end
65
+ play_order
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,107 @@
1
+ module EeePub
2
+ # Class to create OCF
3
+ class OCF
4
+ # Class for 'container.xml' of OCF
5
+ class Container < ContainerItem
6
+ attr_accessor :rootfiles
7
+
8
+ # @param [String or Array or Hash]
9
+ #
10
+ # @example
11
+ # # with String
12
+ # EeePub::OCF::Container.new('container.opf')
13
+ #
14
+ # @example
15
+ # # with Array
16
+ # EeePub::OCF::Container.new(['container.opf', 'other.opf'])
17
+ #
18
+ # @example
19
+ # # with Hash
20
+ # EeePub::OCF::Container.new(
21
+ # :rootfiles => [
22
+ # {:full_path => 'container.opf', :media_type => 'application/oebps-package+xml'}
23
+ # ]
24
+ # )
25
+ def initialize(arg)
26
+ case arg
27
+ when String
28
+ set_values(
29
+ :rootfiles => [
30
+ {:full_path => arg, :media_type => guess_media_type(arg)}
31
+ ]
32
+ )
33
+ when Array
34
+ # TODO: spec
35
+ set_values(
36
+ :rootfiles => arg.keys.map { |k|
37
+ filename = arg[k]
38
+ {:full_path => filename, :media_type => guess_media_type(filename)}
39
+ }
40
+ )
41
+ when Hash
42
+ set_values(arg)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def build_xml(builder)
49
+ builder.container :xmlns => "urn:oasis:names:tc:opendocument:xmlns:container", :version => "1.0" do
50
+ builder.rootfiles do
51
+ rootfiles.each do |i|
52
+ builder.rootfile convert_to_xml_attributes(i)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ attr_accessor :dir, :container
60
+
61
+ # @param [Hash<Symbol, Object>] values the values of symbols and objects for OCF
62
+ #
63
+ # @example
64
+ # EeePub::OCF.new(
65
+ # :dir => '/path/to/dir',
66
+ # :container => 'container.opf'
67
+ # )
68
+ def initialize(values)
69
+ values.each do |k, v|
70
+ self.send(:"#{k}=", v)
71
+ end
72
+ end
73
+
74
+ # Set container
75
+ #
76
+ # @param [EeePub::OCF::Container or args for EeePub::OCF::Container]
77
+ def container=(arg)
78
+ if arg.is_a?(EeePub::OCF::Container)
79
+ @container = arg
80
+ else
81
+ # TODO: spec
82
+ @container = EeePub::OCF::Container.new(arg)
83
+ end
84
+ end
85
+
86
+ # Save as OCF
87
+ #
88
+ # @param [String] output_path the output file path of ePub
89
+ def save(output_path)
90
+ output_path = File.expand_path(output_path)
91
+
92
+ FileUtils.chdir(dir) do
93
+ File.open('mimetype', 'w') do |f|
94
+ f << 'application/epub+zip'
95
+ end
96
+
97
+ meta_inf = 'META-INF'
98
+ FileUtils.mkdir_p(meta_inf)
99
+
100
+ container.save(File.join(meta_inf, 'container.xml'))
101
+
102
+ %x(zip -X9 \"#{output_path}\" mimetype)
103
+ %x(zip -Xr9D \"#{output_path}\" * -xi mimetype)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,148 @@
1
+ module EeePub
2
+ class OPF < ContainerItem
3
+ attr_accessor :unique_identifier,
4
+ :title,
5
+ :language,
6
+ :identifier,
7
+ :date,
8
+ :subject,
9
+ :description,
10
+ :relation,
11
+ :creator,
12
+ :publisher,
13
+ :rights,
14
+ :manifest,
15
+ :spine,
16
+ :guide,
17
+ :ncx,
18
+ :toc
19
+
20
+ default_value :toc, 'ncx'
21
+ default_value :unique_identifier, 'BookId'
22
+ default_value :title, 'Untitled'
23
+ default_value :language, 'en'
24
+
25
+ attr_alias :files, :manifest
26
+
27
+ def identifier
28
+ case @identifier
29
+ when Array
30
+ @identifier
31
+ when String
32
+ [{:value => @identifier, :id => unique_identifier}]
33
+ when Hash
34
+ @identifier[:id] = unique_identifier
35
+ [@identifier]
36
+ else
37
+ @identifier
38
+ end
39
+ end
40
+
41
+ def spine
42
+ @spine ||
43
+ complete_manifest.
44
+ select { |i| i[:media_type] == 'application/xhtml+xml' }.
45
+ map { |i| i[:id]}
46
+ end
47
+
48
+ def build_xml(builder)
49
+ builder.package :xmlns => "http://www.idpf.org/2007/opf",
50
+ 'unique-identifier' => unique_identifier,
51
+ 'version' => "2.0" do
52
+
53
+ build_metadata(builder)
54
+ build_manifest(builder)
55
+ build_spine(builder)
56
+ build_guide(builder)
57
+ end
58
+ end
59
+
60
+ def build_metadata(builder)
61
+ builder.metadata 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
62
+ 'xmlns:dcterms' => "http://purl.org/dc/terms/",
63
+ 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
64
+ 'xmlns:opf' => "http://www.idpf.org/2007/opf" do
65
+
66
+ identifier.each do |i|
67
+ attrs = {}
68
+ attrs['opf:scheme'] = i[:scheme] if i[:scheme]
69
+ attrs[:id] = i[:id] if i[:id]
70
+ builder.dc :identifier, i[:value], attrs
71
+ end
72
+
73
+ [:title, :language, :subject, :description, :relation, :creator, :publisher, :date, :rights].each do |i|
74
+ value = self.send(i)
75
+ next unless value
76
+
77
+ [value].flatten.each do |v|
78
+ case v
79
+ when Hash
80
+ builder.dc i, v[:value], convert_to_xml_attributes(v.reject {|k, v| k == :value})
81
+ else
82
+ builder.dc i, v
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def build_manifest(builder)
90
+ builder.manifest do
91
+ complete_manifest.each do |i|
92
+ builder.item :id => i[:id], :href => i[:href], 'media-type' => i[:media_type]
93
+ end
94
+ end
95
+ end
96
+
97
+ def build_spine(builder)
98
+ builder.spine :toc => toc do
99
+ spine.each do |i|
100
+ builder.itemref :idref => i
101
+ end
102
+ end
103
+ end
104
+
105
+ def build_guide(builder)
106
+ return if guide.nil? || guide.empty?
107
+
108
+ builder.guide do
109
+ guide.each do |i|
110
+ builder.reference convert_to_xml_attributes(i)
111
+ end
112
+ end
113
+ end
114
+
115
+ def complete_manifest
116
+ item_id_cache = {}
117
+
118
+ result = manifest.map do |i|
119
+ case i
120
+ when String
121
+ id = create_unique_item_id(i, item_id_cache)
122
+ href = i
123
+ media_type = guess_media_type(i)
124
+ when Hash
125
+ id = i[:id] || create_unique_item_id(i[:href], item_id_cache)
126
+ href = i[:href]
127
+ media_type = i[:media_type] || guess_media_type(i[:href])
128
+ end
129
+ {:id => id, :href => href, :media_type => media_type}
130
+ end
131
+
132
+ result += [{:id => 'ncx', :href => ncx, :media_type => 'application/x-dtbncx+xml'}] if ncx
133
+ result
134
+ end
135
+
136
+ def create_unique_item_id(filename, id_cache)
137
+ basename = File.basename(filename)
138
+ unless id_cache[basename]
139
+ id_cache[basename] = 0
140
+ name = basename
141
+ else
142
+ name = "#{basename}-#{id_cache[basename]}"
143
+ end
144
+ id_cache[basename] += 1
145
+ name
146
+ end
147
+ end
148
+ end