walterdavis-eeepub 0.6.2

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.
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