walterdavis-eeepub 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/eeepub/ocf.rb ADDED
@@ -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
data/lib/eeepub/opf.rb ADDED
@@ -0,0 +1,149 @@
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
+ :author,
13
+ :publisher,
14
+ :rights,
15
+ :manifest,
16
+ :spine,
17
+ :guide,
18
+ :ncx,
19
+ :toc
20
+
21
+ default_value :toc, 'ncx'
22
+ default_value :unique_identifier, 'BookId'
23
+ default_value :title, 'Untitled'
24
+ default_value :language, 'en'
25
+
26
+ attr_alias :files, :manifest
27
+
28
+ def identifier
29
+ case @identifier
30
+ when Array
31
+ @identifier
32
+ when String
33
+ [{:value => @identifier, :id => unique_identifier}]
34
+ when Hash
35
+ @identifier[:id] = unique_identifier
36
+ [@identifier]
37
+ else
38
+ @identifier
39
+ end
40
+ end
41
+
42
+ def spine
43
+ @spine ||
44
+ complete_manifest.
45
+ select { |i| i[:media_type] == 'application/xhtml+xml' }.
46
+ map { |i| i[:id]}
47
+ end
48
+
49
+ def build_xml(builder)
50
+ builder.package :xmlns => "http://www.idpf.org/2007/opf",
51
+ 'unique-identifier' => unique_identifier,
52
+ 'version' => "2.0" do
53
+
54
+ build_metadata(builder)
55
+ build_manifest(builder)
56
+ build_spine(builder)
57
+ build_guide(builder)
58
+ end
59
+ end
60
+
61
+ def build_metadata(builder)
62
+ builder.metadata 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
63
+ 'xmlns:dcterms' => "http://purl.org/dc/terms/",
64
+ 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
65
+ 'xmlns:opf' => "http://www.idpf.org/2007/opf" do
66
+
67
+ identifier.each do |i|
68
+ attrs = {}
69
+ attrs['opf:scheme'] = i[:scheme] if i[:scheme]
70
+ attrs[:id] = i[:id] if i[:id]
71
+ builder.dc :identifier, i[:value], attrs
72
+ end
73
+
74
+ [:title, :language, :subject, :description, :relation, :creator, :author, :publisher, :date, :rights].each do |i|
75
+ value = self.send(i)
76
+ next unless value
77
+
78
+ [value].flatten.each do |v|
79
+ case v
80
+ when Hash
81
+ builder.dc i, v[:value], convert_to_xml_attributes(v.reject {|k, v| k == :value})
82
+ else
83
+ builder.dc i, v
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def build_manifest(builder)
91
+ builder.manifest do
92
+ complete_manifest.each do |i|
93
+ builder.item :id => i[:id], :href => i[:href], 'media-type' => i[:media_type]
94
+ end
95
+ end
96
+ end
97
+
98
+ def build_spine(builder)
99
+ builder.spine :toc => toc do
100
+ spine.each do |i|
101
+ builder.itemref :idref => i
102
+ end
103
+ end
104
+ end
105
+
106
+ def build_guide(builder)
107
+ return if guide.nil? || guide.empty?
108
+
109
+ builder.guide do
110
+ guide.each do |i|
111
+ builder.reference convert_to_xml_attributes(i)
112
+ end
113
+ end
114
+ end
115
+
116
+ def complete_manifest
117
+ item_id_cache = {}
118
+
119
+ result = manifest.map do |i|
120
+ case i
121
+ when String
122
+ id = create_unique_item_id(i, item_id_cache)
123
+ href = i
124
+ media_type = guess_media_type(i)
125
+ when Hash
126
+ id = i[:id] || create_unique_item_id(i[:href], item_id_cache)
127
+ href = i[:href]
128
+ media_type = i[:media_type] || guess_media_type(i[:href])
129
+ end
130
+ {:id => id, :href => href, :media_type => media_type}
131
+ end
132
+
133
+ result += [{:id => 'ncx', :href => ncx, :media_type => 'application/x-dtbncx+xml'}] if ncx
134
+ result
135
+ end
136
+
137
+ def create_unique_item_id(filename, id_cache)
138
+ basename = File.basename(filename)
139
+ unless id_cache[basename]
140
+ id_cache[basename] = 0
141
+ name = basename
142
+ else
143
+ name = "#{basename}-#{id_cache[basename]}"
144
+ end
145
+ id_cache[basename] += 1
146
+ name
147
+ end
148
+ end
149
+ end
data/lib/eeepub.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'eeepub/container_item'
2
+ require 'eeepub/opf'
3
+ require 'eeepub/ocf'
4
+ require 'eeepub/ncx'
5
+ require 'eeepub/maker'
6
+ require 'eeepub/easy'
7
+
8
+ module EeePub
9
+ # Make ePub
10
+ #
11
+ # @param [Proc] block the block for initialize EeePub::Maker
12
+ def self.make(&block)
13
+ EeePub::Maker.new(&block)
14
+ end
15
+ end
@@ -0,0 +1,67 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "EeePub::Easy" do
4
+ before do
5
+ @easy = EeePub::Easy.new do
6
+ title 'sample'
7
+ creator 'jugyo'
8
+ author 'foo bar'
9
+ identifier 'http://example.com/book/foo', :scheme => 'URL'
10
+ uid 'http://example.com/book/foo'
11
+ end
12
+
13
+ @easy.sections << ['1. foo', <<HTML]
14
+ <?xml version="1.0" encoding="UTF-8"?>
15
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
16
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
17
+ <head>
18
+ <title>foo</title>
19
+ </head>
20
+ <body>
21
+ <p>
22
+ foo foo foo foo foo foo
23
+ </p>
24
+ </body>
25
+ </html>
26
+ HTML
27
+
28
+ @easy.sections << ['2. bar', <<HTML]
29
+ <?xml version="1.0" encoding="UTF-8"?>
30
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
31
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
32
+ <head>
33
+ <title>bar</title>
34
+ </head>
35
+ <body>
36
+ <p>
37
+ bar bar bar bar bar bar
38
+ </p>
39
+ </body>
40
+ </html>
41
+ HTML
42
+
43
+ @easy.assets << 'image.png'
44
+ end
45
+
46
+ it 'spec for prepare' do
47
+ Dir.mktmpdir do |dir|
48
+ mock(FileUtils).cp('image.png', dir)
49
+
50
+ @easy.send(:prepare, dir)
51
+
52
+ file1 = File.join(dir, 'section_0.html')
53
+ file2 = File.join(dir, 'section_1.html')
54
+ File.exists?(file1).should be_true
55
+ File.exists?(file2).should be_true
56
+ File.read(file1).should == @easy.sections[0][1]
57
+ File.read(file2).should == @easy.sections[1][1]
58
+
59
+ @easy.instance_variable_get(:@nav).should == [
60
+ {:label => '1. foo', :content => 'section_0.html'},
61
+ {:label => '2. bar', :content => 'section_1.html'}
62
+ ]
63
+
64
+ @easy.instance_variable_get(:@files).should == [file1, file2, 'image.png']
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,129 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "EeePub::Maker" do
4
+ before do
5
+ @maker = EeePub::Maker.new do
6
+ title 'sample'
7
+ creator 'jugyo'
8
+ author 'foo bar'
9
+ publisher 'jugyo.org'
10
+ date "2010-05-06"
11
+ language 'en'
12
+ subject 'epub sample'
13
+ description 'this is epub sample'
14
+ rights 'xxx'
15
+ relation 'xxx'
16
+ identifier 'http://example.com/book/foo', :scheme => 'URL'
17
+ uid 'http://example.com/book/foo'
18
+ ncx_file 'toc.ncx'
19
+ opf_file 'content.opf'
20
+ files ['foo.html', 'bar.html']
21
+ nav [
22
+ {:label => '1. foo', :content => 'foo.html'},
23
+ {:label => '1. bar', :content => 'bar.html'}
24
+ ]
25
+ end
26
+ end
27
+
28
+ it { @maker.instance_variable_get(:@titles).should == ['sample'] }
29
+ it { @maker.instance_variable_get(:@creators).should == ['jugyo'] }
30
+ it { @maker.instance_variable_get(:@authors).should == ['foo bar'] }
31
+ it { @maker.instance_variable_get(:@publishers).should == ['jugyo.org'] }
32
+ it { @maker.instance_variable_get(:@dates).should == ["2010-05-06"] }
33
+ it { @maker.instance_variable_get(:@identifiers).should == [{:value => 'http://example.com/book/foo', :scheme => 'URL'}] }
34
+ it { @maker.instance_variable_get(:@uid).should == 'http://example.com/book/foo' }
35
+ it { @maker.instance_variable_get(:@ncx_file).should == 'toc.ncx' }
36
+ it { @maker.instance_variable_get(:@opf_file).should == 'content.opf' }
37
+ it { @maker.instance_variable_get(:@files).should == ['foo.html', 'bar.html'] }
38
+ it {
39
+ @maker.instance_variable_get(:@nav).should == [
40
+ {:label => '1. foo', :content => 'foo.html'},
41
+ {:label => '1. bar', :content => 'bar.html'}
42
+ ]
43
+ }
44
+
45
+ it 'should save' do
46
+ stub(FileUtils).cp.with_any_args
47
+ mock(Dir).mktmpdir {|i| i.call('/tmp')}
48
+ mock(EeePub::NCX).new(
49
+ :title => "sample",
50
+ :nav => [
51
+ {:label => '1. foo', :content => 'foo.html'},
52
+ {:label => '1. bar', :content => 'bar.html'}
53
+ ],
54
+ :uid => "http://example.com/book/foo"
55
+ ) { stub!.save }
56
+ mock(EeePub::OPF).new(
57
+ :title => ["sample"],
58
+ :creator => ["jugyo"],
59
+ :author => ["foo bar"],
60
+ :date => ["2010-05-06"],
61
+ :language => ['en'],
62
+ :subject => ['epub sample'],
63
+ :description => ['this is epub sample'],
64
+ :rights => ['xxx'],
65
+ :relation => ['xxx'],
66
+ :ncx => "toc.ncx",
67
+ :publisher => ["jugyo.org"],
68
+ :identifier => [{:value => "http://example.com/book/foo", :scheme => "URL"}],
69
+ :manifest => ['foo.html', 'bar.html']
70
+ ) { stub!.save }
71
+ mock(EeePub::OCF).new(
72
+ :container => "content.opf",
73
+ :dir => '/tmp'
74
+ ) { stub!.save }
75
+
76
+ @maker.save('test.epub')
77
+ end
78
+
79
+ describe "files as hash" do
80
+ before do
81
+ @maker = EeePub::Maker.new do
82
+ title 'sample'
83
+ creator 'jugyo'
84
+ author 'foo bar'
85
+ publisher 'jugyo.org'
86
+ date "2010-05-06"
87
+ language 'en'
88
+ subject 'epub sample'
89
+ description 'this is epub sample'
90
+ rights 'xxx'
91
+ relation 'xxx'
92
+ identifier 'http://example.com/book/foo', :scheme => 'URL'
93
+ uid 'http://example.com/book/foo'
94
+ ncx_file 'toc.ncx'
95
+ opf_file 'content.opf'
96
+ files [{'foo.html' => 'foo/bar'}, {'bar.html' => 'foo/bar/baz'}]
97
+ nav [
98
+ {:label => '1. foo', :content => 'foo.html'},
99
+ {:label => '1. bar', :content => 'bar.html'}
100
+ ]
101
+ end
102
+ end
103
+
104
+ it 'should save' do
105
+ stub(FileUtils).cp.with_any_args
106
+ stub(FileUtils).mkdir_p.with_any_args
107
+ mock(Dir).mktmpdir {|i| i.call('/tmp')}
108
+ mock(EeePub::NCX).new.with_any_args { stub!.save }
109
+ mock(EeePub::OPF).new(
110
+ :title => ["sample"],
111
+ :creator => ["jugyo"],
112
+ :author => ["foo bar"],
113
+ :date => ["2010-05-06"],
114
+ :language => ['en'],
115
+ :subject => ['epub sample'],
116
+ :description => ['this is epub sample'],
117
+ :rights => ['xxx'],
118
+ :relation => ['xxx'],
119
+ :ncx => "toc.ncx",
120
+ :publisher => ["jugyo.org"],
121
+ :identifier => [{:value => "http://example.com/book/foo", :scheme => "URL"}],
122
+ :manifest => ["foo/bar/foo.html", "foo/bar/baz/bar.html"]
123
+ ) { stub!.save }
124
+ mock(EeePub::OCF).new.with_any_args { stub!.save }
125
+
126
+ @maker.save('test.epub')
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,80 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+
6
+ describe "EeePub::NCX" do
7
+ before do
8
+ @ncx = EeePub::NCX.new(
9
+ :uid => 'uid',
10
+ :nav_map => [
11
+ {:label => 'foo', :content => 'foo.html'},
12
+ {:label => 'bar', :content => 'bar.html'}
13
+ ]
14
+ )
15
+ end
16
+
17
+ it 'should set default values' do
18
+ @ncx.depth.should == 1
19
+ @ncx.total_page_count.should == 0
20
+ @ncx.max_page_number.should == 0
21
+ @ncx.doc_title.should == 'Untitled'
22
+ end
23
+
24
+ it 'should make xml' do
25
+ doc = Nokogiri::XML(@ncx.to_xml)
26
+ head = doc.at('head')
27
+ head.should_not be_nil
28
+
29
+ head.at("//xmlns:meta[@name='dtb:uid']")['content'].should == @ncx.uid
30
+ head.at("//xmlns:meta[@name='dtb:depth']")['content'].should == @ncx.depth.to_s
31
+ head.at("//xmlns:meta[@name='dtb:totalPageCount']")['content'].should == @ncx.total_page_count.to_s
32
+ head.at("//xmlns:meta[@name='dtb:maxPageNumber']")['content'].should == @ncx.max_page_number.to_s
33
+ head.at("//xmlns:docTitle/xmlns:text").inner_text.should == @ncx.doc_title
34
+
35
+ nav_map = doc.at('navMap')
36
+ nav_map.should_not be_nil
37
+ nav_map.search('navPoint').each_with_index do |nav_point, index|
38
+ expect = @ncx.nav_map[index]
39
+ nav_point.attribute('id').value.should == "navPoint-#{index + 1}"
40
+ nav_point.attribute('playOrder').value.should == (index + 1).to_s
41
+ nav_point.at('navLabel').at('text').inner_text.should == expect[:label]
42
+ nav_point.at('content').attribute('src').value.should == expect[:content]
43
+ end
44
+ end
45
+
46
+ context 'nested nav_map' do
47
+ before do
48
+ @ncx.nav = [
49
+ {:label => 'foo', :content => 'foo.html',
50
+ :nav => [
51
+ {:label => 'foo-1', :content => 'foo-1.html'},
52
+ {:label => 'foo-2', :content => 'foo-2.html'}
53
+ ],
54
+ },
55
+ {:label => 'bar', :content => 'bar.html'}
56
+ ]
57
+ end
58
+
59
+ it 'should make xml' do
60
+ doc = Nokogiri::XML(@ncx.to_xml)
61
+ nav_map = doc.at('navMap')
62
+
63
+ nav_map.search('navMap/navPoint').each_with_index do |nav_point, index|
64
+ expect = @ncx.nav_map[index]
65
+ nav_point.attribute('id').value.should == "navPoint-#{index + 1}"
66
+ nav_point.attribute('playOrder').value.should == (index + 1).to_s
67
+ nav_point.at('navLabel').at('text').inner_text.should == expect[:label]
68
+ nav_point.at('content').attribute('src').value.should == expect[:content]
69
+ end
70
+
71
+ nav_map.search('navPoint/navPoint').each_with_index do |nav_point, index|
72
+ expect = @ncx.nav[0][:nav][index]
73
+ nav_point.attribute('id').value.should == "navPoint-#{index + 2}"
74
+ nav_point.attribute('playOrder').value.should == (index + 2).to_s
75
+ nav_point.at('navLabel').at('text').inner_text.should == expect[:label]
76
+ nav_point.at('content').attribute('src').value.should == expect[:content]
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+
6
+ describe "EeePub::OCF" do
7
+ before do
8
+ @tmpdir = File.join(Dir.tmpdir, 'eeepub_test')
9
+ FileUtils.mkdir_p(@tmpdir)
10
+ @container = EeePub::OCF::Container.new('foo.opf')
11
+ @ocf = EeePub::OCF.new(:dir => @tmpdir, :container => @container)
12
+ end
13
+
14
+ after do
15
+ FileUtils.rm_rf(@tmpdir)
16
+ end
17
+
18
+ it 'should return rootfiles' do
19
+ @container.rootfiles.should == [{:full_path => 'foo.opf', :media_type => 'application/oebps-package+xml'}]
20
+ end
21
+
22
+ it 'should specify container as String' do
23
+ ocf = EeePub::OCF.new(:dir => @tmpdir, :container => 'foo.opf')
24
+ ocf.container.rootfiles == [{:full_path => 'foo.opf', :media_type => 'application/oebps-package+xml'}]
25
+ end
26
+
27
+ it 'should make xml' do
28
+ doc = Nokogiri::XML(@container.to_xml)
29
+ rootfiles = doc.at('rootfiles')
30
+ rootfiles.should_not be_nil
31
+ rootfiles.search('rootfile').each_with_index do |rootfile, index|
32
+ expect = @container.rootfiles[index]
33
+ rootfile.attribute('full-path').value.should == expect[:full_path]
34
+ rootfile.attribute('media-type').value.should == expect[:media_type]
35
+ end
36
+ end
37
+
38
+ it 'should make epub' do
39
+ output_path = File.join('eeepub_test.epub')
40
+ @ocf.save(output_path)
41
+ File.exists?(output_path)
42
+ end
43
+ end