peregrin 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +148 -0
- data/bin/peregrin +6 -0
- data/lib/formats/epub.rb +553 -0
- data/lib/formats/ochook.rb +113 -0
- data/lib/formats/zhook.rb +394 -0
- data/lib/peregrin/book.rb +87 -0
- data/lib/peregrin/chapter.rb +31 -0
- data/lib/peregrin/component.rb +12 -0
- data/lib/peregrin/componentizer.rb +118 -0
- data/lib/peregrin/outliner.rb +204 -0
- data/lib/peregrin/property.rb +16 -0
- data/lib/peregrin/resource.rb +24 -0
- data/lib/peregrin/version.rb +5 -0
- data/lib/peregrin/zip_patch.rb +11 -0
- data/lib/peregrin.rb +139 -0
- data/test/conversion_test.rb +80 -0
- data/test/formats/epub_test.rb +159 -0
- data/test/formats/ochook_test.rb +104 -0
- data/test/formats/zhook_test.rb +219 -0
- data/test/test_helper.rb +16 -0
- data/test/utils/componentizer_test.rb +78 -0
- data/test/utils/outliner_test.rb +49 -0
- metadata +135 -0
@@ -0,0 +1,204 @@
|
|
1
|
+
class Peregrin::Outliner
|
2
|
+
|
3
|
+
REGEXES = {
|
4
|
+
:section_root => /^BLOCKQUOTE|BODY|DETAILS|FIELDSET|FIGURE|TD$/i,
|
5
|
+
:section_content => /^ARTICLE|ASIDE|NAV|SECTION$/i,
|
6
|
+
:heading => /^H[1-6]|HGROUP$/i
|
7
|
+
}
|
8
|
+
|
9
|
+
class Utils
|
10
|
+
|
11
|
+
def self.section_root?(el)
|
12
|
+
element_name_is?(el, REGEXES[:section_root])
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def self.section_content?(el)
|
17
|
+
element_name_is?(el, REGEXES[:section_content])
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def self.heading?(el)
|
22
|
+
element_name_is?(el, REGEXES[:heading])
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def self.named?(el, name)
|
27
|
+
element_name_is?(el, /^#{name}$/)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def self.heading_rank(el)
|
32
|
+
raise "Not a heading: #{el.inspect}" unless heading?(el)
|
33
|
+
if named?(el, 'HGROUP')
|
34
|
+
1.upto(6) { |n| return n if el.at_css("h#{n}") }
|
35
|
+
return 6 #raise "Heading not found in HGROUP: #{el.inspect}"
|
36
|
+
else
|
37
|
+
el.name.reverse.to_i
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def self.element_name_is?(el, pattern)
|
43
|
+
return false unless el
|
44
|
+
return false unless el.respond_to?(:name)
|
45
|
+
return false if el.name.nil? || el.name.empty?
|
46
|
+
el.name.upcase.match(pattern) ? true : false
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
class Section
|
53
|
+
|
54
|
+
attr_accessor :sections, :heading, :container, :node
|
55
|
+
|
56
|
+
|
57
|
+
def initialize(node = nil)
|
58
|
+
self.node = node
|
59
|
+
self.sections = []
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def append(subsection)
|
64
|
+
subsection.container = self
|
65
|
+
sections.push(subsection)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def empty?
|
70
|
+
heading_text.nil? && sections.all? { |sxn| sxn.empty? }
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def heading_text
|
75
|
+
return nil unless Utils.heading?(heading)
|
76
|
+
h = heading
|
77
|
+
h = h.at_css("h#{Utils.heading_rank(h)}") if Utils.named?(h, 'HGROUP')
|
78
|
+
return nil unless h && !h.content.strip.empty?
|
79
|
+
h.content.strip
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def heading_rank
|
84
|
+
# FIXME: some doubt as to whether 1 is the sensible default
|
85
|
+
Utils.heading?(heading) ? Utils.heading_rank(heading) : 1
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
def initialize(doc)
|
93
|
+
@document = doc
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def process(from)
|
98
|
+
@outlinee = nil
|
99
|
+
@outlines = {}
|
100
|
+
@section = Section.new
|
101
|
+
@stack = []
|
102
|
+
walk(from)
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def walk(node)
|
107
|
+
return unless node
|
108
|
+
enter_node(node)
|
109
|
+
node.children.each { |ch| walk(ch) }
|
110
|
+
exit_node(node)
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def enter_node(node)
|
115
|
+
return if Utils.heading?(@stack.last)
|
116
|
+
|
117
|
+
if Utils.section_content?(node) || Utils.section_root?(node)
|
118
|
+
@stack.push(@outlinee) unless @outlinee.nil?
|
119
|
+
@outlinee = node
|
120
|
+
@section = Section.new(node)
|
121
|
+
@outlines[@outlinee] = Section.new(node)
|
122
|
+
@outlines[@outlinee].sections = [@section]
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
126
|
+
return if @outlinee.nil?
|
127
|
+
|
128
|
+
if Utils.heading?(node)
|
129
|
+
node_rank = Utils.heading_rank(node)
|
130
|
+
if !@section.heading
|
131
|
+
@section.heading = node
|
132
|
+
elsif node_rank <= @outlines[@outlinee].sections.last.heading_rank
|
133
|
+
@section = Section.new
|
134
|
+
@section.heading = node
|
135
|
+
@outlines[@outlinee].sections.push(@section)
|
136
|
+
else
|
137
|
+
candidate = @section
|
138
|
+
while true
|
139
|
+
if node_rank > candidate.heading_rank
|
140
|
+
@section = Section.new
|
141
|
+
candidate.append(@section)
|
142
|
+
@section.heading = node
|
143
|
+
break
|
144
|
+
end
|
145
|
+
candidate = candidate.container
|
146
|
+
end
|
147
|
+
end
|
148
|
+
@stack.push(node)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
def exit_node(node)
|
154
|
+
if Utils.heading?(@stack.last)
|
155
|
+
@stack.pop if @stack.last == node
|
156
|
+
return
|
157
|
+
end
|
158
|
+
|
159
|
+
if Utils.section_content?(node) && !@stack.empty?
|
160
|
+
@outlinee = @stack.pop
|
161
|
+
@section = @outlines[@outlinee].sections.last
|
162
|
+
@outlines[node].sections.each { |s| @section.append(s) }
|
163
|
+
return
|
164
|
+
end
|
165
|
+
|
166
|
+
if Utils.section_root?(node) && !@stack.empty?
|
167
|
+
@outlinee = @stack.pop
|
168
|
+
@section = @outlines[@outlinee].sections.last
|
169
|
+
while @section.sections.any?
|
170
|
+
@section = @section.sections.last
|
171
|
+
end
|
172
|
+
return
|
173
|
+
end
|
174
|
+
|
175
|
+
if Utils.section_content?(node) || Utils.section_root?(node)
|
176
|
+
@section = @outlines[@outlinee].sections.first
|
177
|
+
return
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
def to_html
|
183
|
+
curse = lambda { |section, is_root|
|
184
|
+
below = section.sections.collect { |ch|
|
185
|
+
ch_out = curse.call(ch, false).strip
|
186
|
+
(ch_out.nil? || ch_out.empty?) ? "" : "<li>#{ch_out}</li>"
|
187
|
+
}.join.strip
|
188
|
+
below = (below.nil? || below.empty?) ? "" : "<ol>#{below}</ol>\n"
|
189
|
+
if is_root
|
190
|
+
below
|
191
|
+
else
|
192
|
+
heading = block_given? ? yield(section, below) : section.heading_text
|
193
|
+
"#{heading}#{below}"
|
194
|
+
end
|
195
|
+
}
|
196
|
+
curse.call(result_root, true)
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
def result_root
|
201
|
+
@outlines[@outlinee]
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Books have metadata. Each unit of metadata (each metadatum?) is a 'property'
|
2
|
+
# of the book.
|
3
|
+
#
|
4
|
+
# A property has a key, a value and an optional set of attributes.
|
5
|
+
#
|
6
|
+
class Peregrin::Property
|
7
|
+
|
8
|
+
attr_accessor :key, :value, :attributes
|
9
|
+
|
10
|
+
def initialize(key, value, attributes = {})
|
11
|
+
@key = key
|
12
|
+
@value = value
|
13
|
+
@attributes = attributes
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Any file that is a part of the book but not one of its linear sections (ie,
|
2
|
+
# Components) is a Resource.
|
3
|
+
#
|
4
|
+
# Resources can potentially be quite large, so as far as possible we don't
|
5
|
+
# store their contents in memory.
|
6
|
+
#
|
7
|
+
class Peregrin::Resource
|
8
|
+
|
9
|
+
attr_accessor :src, :attributes
|
10
|
+
attr_writer :media_type
|
11
|
+
|
12
|
+
def initialize(src, media_type = nil, attributes = {})
|
13
|
+
@src = src
|
14
|
+
@media_type = media_type || MIME::Types.of(File.basename(@src))
|
15
|
+
@media_type = @media_type.first if @media_type.kind_of?(Array)
|
16
|
+
@attributes = attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def media_type
|
21
|
+
@media_type ? @media_type.to_s : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/peregrin.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
module Peregrin
|
2
|
+
|
3
|
+
# Required libraries
|
4
|
+
require 'fileutils'
|
5
|
+
require 'uri'
|
6
|
+
require 'zipruby'
|
7
|
+
require 'nokogiri'
|
8
|
+
require 'mime/types'
|
9
|
+
|
10
|
+
# Require libs in this directory
|
11
|
+
[
|
12
|
+
"peregrin/version",
|
13
|
+
"peregrin/zip_patch",
|
14
|
+
"peregrin/book",
|
15
|
+
"peregrin/resource",
|
16
|
+
"peregrin/component",
|
17
|
+
"peregrin/chapter",
|
18
|
+
"peregrin/property",
|
19
|
+
"peregrin/componentizer",
|
20
|
+
"peregrin/outliner",
|
21
|
+
"formats/epub",
|
22
|
+
"formats/zhook",
|
23
|
+
"formats/ochook"
|
24
|
+
].each { |lib|
|
25
|
+
require lib
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
class Main
|
30
|
+
|
31
|
+
def self.run(args)
|
32
|
+
if args.size == 1
|
33
|
+
src = args.first
|
34
|
+
validate(src) and inspect(src)
|
35
|
+
elsif args.size == 2
|
36
|
+
src, dest = args
|
37
|
+
validate(src) and convert(src, dest) and inspect(dest)
|
38
|
+
else
|
39
|
+
usage
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def self.usage
|
45
|
+
puts "Peregrin [http://ochook.org/peregrin]"
|
46
|
+
puts "Version: #{VERSION}"
|
47
|
+
puts "A tool for inspecting Zhooks, Ochooks and EPUB ebooks,"
|
48
|
+
puts "and converting between them."
|
49
|
+
puts ""
|
50
|
+
puts "Usage: peregrin srcpath [destpath]"
|
51
|
+
puts ""
|
52
|
+
puts "If one path given, validates ebook at that path and outputs analysis."
|
53
|
+
puts "If two paths given, converts from srcpath to destpath and outputs "
|
54
|
+
puts "analysis of converted ebook."
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def self.validate(path)
|
59
|
+
klass = format_for_path(path)
|
60
|
+
klass.validate(path)
|
61
|
+
true
|
62
|
+
rescue UnknownFileFormat => e
|
63
|
+
exit_with("Unknown file format: #{path}")
|
64
|
+
rescue => e
|
65
|
+
exit_with("Invalid #{klass::FORMAT}: #{path}", "Reason - #{e}")
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def self.convert(src_path, dest_path, src_klass = nil, dest_klass = nil)
|
70
|
+
src_klass ||= format_for_path(src_path)
|
71
|
+
dest_klass ||= format_for_path(dest_path)
|
72
|
+
|
73
|
+
src_ook = src_klass.read(src_path)
|
74
|
+
|
75
|
+
# FIXME: how do we do these options? User-specified? Dest-format-specified?
|
76
|
+
options = {}
|
77
|
+
options[:componentize] = true if dest_klass == Peregrin::Epub
|
78
|
+
book = src_ook.to_book(options)
|
79
|
+
|
80
|
+
dest_ook = dest_klass.new(book)
|
81
|
+
dest_ook.write(dest_path)
|
82
|
+
validate(dest_path)
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def self.inspect(path)
|
87
|
+
klass = format_for_path(path)
|
88
|
+
ook = klass.read(path)
|
89
|
+
book = ook.to_book
|
90
|
+
puts "[#{klass::FORMAT}]"
|
91
|
+
puts "\nCover\n #{book.cover.src}"
|
92
|
+
puts "\nComponents [#{book.components.size}]"
|
93
|
+
book.components.each { |cmpt| puts " #{cmpt.src}" }
|
94
|
+
puts "\nResources [#{book.resources.size}]"
|
95
|
+
book.resources.each { |res| puts " #{res.src}" }
|
96
|
+
puts "\nChapters"
|
97
|
+
book.chapters.each { |chp| print_chapter_title(chp, "- ") }
|
98
|
+
puts "\nProperties [#{book.properties.size}]"
|
99
|
+
book.properties.each { |property|
|
100
|
+
puts " #{property.key}: #{property.value}" unless property.value.empty?
|
101
|
+
}
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def self.format_for_path(path)
|
109
|
+
return Peregrin::Zhook if File.extname(path) == ".zhook"
|
110
|
+
return Peregrin::Epub if File.extname(path) == ".epub"
|
111
|
+
return Peregrin::Ochook if File.directory?(path) || !File.exists?(path)
|
112
|
+
raise UnknownFileFormat.new(path)
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def self.exit_with(*remarks)
|
117
|
+
remarks.each { |rm| puts(rm) }
|
118
|
+
false
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def self.print_chapter_title(chp, padd)
|
123
|
+
puts "#{padd}#{chp.title}"
|
124
|
+
chp.children.each { |ch|
|
125
|
+
print_chapter_title(ch, " "+padd)
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
|
131
|
+
class UnknownFileFormat < RuntimeError
|
132
|
+
def initialize(path = nil)
|
133
|
+
@page = path
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Peregrin::Tests::ConversionTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_epub_to_ochook
|
6
|
+
conversion_test(
|
7
|
+
Peregrin::Epub,
|
8
|
+
Peregrin::Ochook,
|
9
|
+
'test/fixtures/epubs/strunk.epub',
|
10
|
+
'test/output/conversions/epub_to_ochook'
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def test_epub_to_zhook
|
16
|
+
conversion_test(
|
17
|
+
Peregrin::Epub,
|
18
|
+
Peregrin::Zhook,
|
19
|
+
'test/fixtures/epubs/strunk.epub',
|
20
|
+
'test/output/conversions/epub_to_zhook.zhook'
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def test_ochook_to_epub
|
26
|
+
conversion_test(
|
27
|
+
Peregrin::Ochook,
|
28
|
+
Peregrin::Epub,
|
29
|
+
'test/fixtures/ochooks/basic',
|
30
|
+
'test/output/conversions/ochook_to_epub.epub',
|
31
|
+
:componentize => true
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def test_ochook_to_zhook
|
37
|
+
conversion_test(
|
38
|
+
Peregrin::Ochook,
|
39
|
+
Peregrin::Zhook,
|
40
|
+
'test/fixtures/ochooks/basic',
|
41
|
+
'test/output/conversions/ochook_to_zhook.zhook',
|
42
|
+
:componentize => true
|
43
|
+
)
|
44
|
+
assert_nil(@dest_ook.send(:index).root['manifest'])
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def test_zhook_to_epub
|
49
|
+
conversion_test(
|
50
|
+
Peregrin::Zhook,
|
51
|
+
Peregrin::Epub,
|
52
|
+
'test/fixtures/zhooks/flat.zhook',
|
53
|
+
'test/output/conversions/zhook_to_epub.epub',
|
54
|
+
:componentize => true
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def test_zhook_to_ochook
|
60
|
+
conversion_test(
|
61
|
+
Peregrin::Zhook,
|
62
|
+
Peregrin::Ochook,
|
63
|
+
'test/fixtures/zhooks/flat.zhook',
|
64
|
+
'test/output/conversions/zhook_to_ochook',
|
65
|
+
:componentize => true
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def conversion_test(src_klass, dest_klass, src, dest, to_book_options = {})
|
73
|
+
@src_ook = src_klass.read(src)
|
74
|
+
@dest_ook = dest_klass.new(@src_ook.to_book(to_book_options))
|
75
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
76
|
+
@dest_ook.write(dest)
|
77
|
+
assert_nothing_raised { dest_klass.validate(dest) }
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Peregrin::Tests::EpubTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
# A fairly trivial book-in, book-out test.
|
6
|
+
def test_book_to_book
|
7
|
+
epub = Peregrin::Epub.new(strunk_book)
|
8
|
+
book = epub.to_book
|
9
|
+
assert_equal(22, book.components.length)
|
10
|
+
assert_equal(6, book.chapters.length)
|
11
|
+
assert_equal("William Strunk Jr.", book.property_for('creator'))
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def test_write_to_epub
|
16
|
+
epub = Peregrin::Epub.new(strunk_book)
|
17
|
+
epub.write('test/output/strunk_test.epub')
|
18
|
+
assert(File.exists?('test/output/strunk_test.epub'))
|
19
|
+
assert_nothing_raised {
|
20
|
+
Peregrin::Epub.validate("test/output/strunk_test.epub")
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def test_heading_depth
|
26
|
+
epub = Peregrin::Epub.new(strunk_book)
|
27
|
+
assert_equal(2, epub.send(:heading_depth))
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def test_epub_validation
|
32
|
+
assert_nothing_raised {
|
33
|
+
Peregrin::Epub.validate("test/fixtures/epubs/strunk.epub")
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def test_extracting_metadata
|
39
|
+
epub = Peregrin::Epub.read("test/fixtures/epubs/strunk.epub")
|
40
|
+
assert_equal("The Elements of Style", epub.to_book.property_for('title'))
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def test_extracting_components
|
45
|
+
epub = Peregrin::Epub.read("test/fixtures/epubs/strunk.epub")
|
46
|
+
book = epub.to_book
|
47
|
+
assert_equal(
|
48
|
+
["cover.xml", "title.xml", "about.xml", "main0.xml", "main1.xml", "main2.xml", "main3.xml", "main4.xml", "main5.xml", "main6.xml", "main7.xml", "main8.xml", "main9.xml", "main10.xml", "main11.xml", "main12.xml", "main13.xml", "main14.xml", "main15.xml", "main16.xml", "main17.xml", "main18.xml", "main19.xml", "main20.xml", "main21.xml", "similar.xml", "feedbooks.xml"],
|
49
|
+
book.components.collect { |cmpt| cmpt.src }
|
50
|
+
)
|
51
|
+
assert_equal(
|
52
|
+
["css/page.css", "css/feedbooks.css", "css/title.css", "css/about.css", "css/main.css", "images/logo-feedbooks-tiny.png", "images/logo-feedbooks.png", "images/cover.png"],
|
53
|
+
book.resources.collect { |res| res.src }
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def test_extracting_contents
|
59
|
+
epub = Peregrin::Epub.read("test/fixtures/epubs/strunk.epub")
|
60
|
+
assert_equal(2, epub.send(:heading_depth))
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def test_extracting_cover
|
65
|
+
# Cover image referenced from metadata
|
66
|
+
epub = Peregrin::Epub.read("test/fixtures/epubs/covers/cover_in_meta.epub")
|
67
|
+
assert_equal("cover.png", epub.to_book.cover.src)
|
68
|
+
|
69
|
+
# First image in a component listed in the guide as 'cover'
|
70
|
+
epub = Peregrin::Epub.read("test/fixtures/epubs/covers/cover_in_guide.epub")
|
71
|
+
assert_equal("cover.png", epub.to_book.cover.src)
|
72
|
+
|
73
|
+
# A component with the id of 'cover-image'.
|
74
|
+
epub = Peregrin::Epub.read(
|
75
|
+
"test/fixtures/epubs/covers/cover-image_in_manifest.epub"
|
76
|
+
)
|
77
|
+
assert_equal("cover.png", epub.to_book.cover.src)
|
78
|
+
|
79
|
+
# First image in component with the id of 'cover'.
|
80
|
+
epub = Peregrin::Epub.read(
|
81
|
+
"test/fixtures/epubs/covers/cover_in_manifest.epub"
|
82
|
+
)
|
83
|
+
assert_equal("cover.png", epub.to_book.cover.src)
|
84
|
+
|
85
|
+
# First image in first component.
|
86
|
+
epub = Peregrin::Epub.read(
|
87
|
+
"test/fixtures/epubs/covers/cover_in_first_cmpt.epub"
|
88
|
+
)
|
89
|
+
assert_equal("cover.png", epub.to_book.cover.src)
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def test_read_epub_to_write_epub
|
94
|
+
epub = Peregrin::Epub.read("test/fixtures/epubs/strunk.epub")
|
95
|
+
epub.write("test/output/strunk_test2.epub")
|
96
|
+
assert(File.exists?('test/output/strunk_test2.epub'))
|
97
|
+
assert_nothing_raised {
|
98
|
+
Peregrin::Epub.validate("test/output/strunk_test2.epub")
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def strunk_book
|
106
|
+
book = Peregrin::Book.new
|
107
|
+
0.upto(21) { |i|
|
108
|
+
path = "main#{i}.xml"
|
109
|
+
book.add_component(
|
110
|
+
path,
|
111
|
+
IO.read("test/fixtures/epubs/strunk/OPS/#{path}")
|
112
|
+
)
|
113
|
+
}
|
114
|
+
pos = 0
|
115
|
+
chp = book.add_chapter(
|
116
|
+
"Chapter 1 - Introductory",
|
117
|
+
pos+=1,
|
118
|
+
"main0.xml"
|
119
|
+
)
|
120
|
+
chp = book.add_chapter(
|
121
|
+
"Chapter 2 - Elementary Rules of Usage",
|
122
|
+
pos+=1,
|
123
|
+
"main1.xml"
|
124
|
+
)
|
125
|
+
chp.add_child(
|
126
|
+
"1. Form the possessive singular of nounds with 's",
|
127
|
+
pos+=1,
|
128
|
+
"main1.xml#section_98344"
|
129
|
+
)
|
130
|
+
chp = book.add_chapter(
|
131
|
+
"Chapter 3 - Elementary Principles of Composition",
|
132
|
+
pos+=1,
|
133
|
+
"main9.xml"
|
134
|
+
)
|
135
|
+
chp = book.add_chapter(
|
136
|
+
"Chapter 4 - A Few Matters of Form",
|
137
|
+
pos+=1,
|
138
|
+
"main19.xml"
|
139
|
+
)
|
140
|
+
chp = book.add_chapter(
|
141
|
+
"Chapter 5 - Words and Expressions Commonly Misused",
|
142
|
+
pos+=1,
|
143
|
+
"main20.xml"
|
144
|
+
)
|
145
|
+
chp = book.add_chapter(
|
146
|
+
"Chapter 6 - Words Commonly Misspelled",
|
147
|
+
pos+=1,
|
148
|
+
"main21.xml"
|
149
|
+
)
|
150
|
+
book.add_property("title", "The Elements of Style")
|
151
|
+
book.add_property("creator", "William Strunk Jr.")
|
152
|
+
book.add_resource("css/main.css")
|
153
|
+
book.read_resource_proc = lambda { |resource|
|
154
|
+
IO.read("test/fixtures/epubs/strunk/OPS/#{resource.src}")
|
155
|
+
}
|
156
|
+
book
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|