epub-parser 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -41,6 +41,10 @@ module EPUB
41
41
  end
42
42
  @unique_identifier_id = elem['unique-identifier']
43
43
  @package.prefix = parse_prefix(extract_attribute(elem, 'prefix'))
44
+ if @package.prefix.key? 'rendition'
45
+ require 'epub/publication/fixed_layout'
46
+ EPUB::Publication.__send__ :include, EPUB::Publication::FixedLayout
47
+ end
44
48
 
45
49
  @package
46
50
  end
@@ -1,5 +1,5 @@
1
1
  module EPUB
2
2
  class Parser
3
- VERSION = "0.1.3"
3
+ VERSION = "0.1.4"
4
4
  end
5
5
  end
@@ -0,0 +1,209 @@
1
+ module EPUB
2
+ module Publication
3
+ module FixedLayout
4
+ PREFIX_KEY = 'rendition'.freeze
5
+ PREFIX_VALUE = 'http://www.idpf.org/vocab/rendition/#'.freeze
6
+
7
+ RENDITION_PROPERTIES = {
8
+ 'layout' => ['reflowable'.freeze, 'pre-paginated'.freeze].freeze,
9
+ 'orientation' => ['auto'.freeze, 'landscape'.freeze, 'portrait'.freeze].freeze,
10
+ 'spread' => ['auto'.freeze, 'none'.freeze, 'landscape'.freeze, 'portrait'.freeze, 'both'.freeze].freeze
11
+ }.freeze
12
+
13
+ class UnsupportedRenditionValue < StandardError; end
14
+
15
+ class << self
16
+ def included(package_class)
17
+ [
18
+ [Package, PackageMixin],
19
+ [Package::Metadata, MetadataMixin],
20
+ [Package::Spine::Itemref, ItemrefMixin],
21
+ [Package::Manifest::Item, ItemMixin],
22
+ [ContentDocument::XHTML, ContentDocumentMixin],
23
+ ].each do |(base, mixin)|
24
+ base.__send__ :include, mixin
25
+ end
26
+ end
27
+ end
28
+
29
+ module Rendition
30
+ # @note Call after defining #rendition_xxx and #renditionn_xxx=
31
+ def def_rendition_methods
32
+ RENDITION_PROPERTIES.each_key do |property|
33
+ alias_method property, "rendition_#{property}"
34
+ alias_method "#{property}=", "rendition_#{property}="
35
+ end
36
+ def_rendition_layout_methods
37
+ end
38
+
39
+ def def_rendition_layout_methods
40
+ property = 'layout'
41
+ RENDITION_PROPERTIES[property].each do |value|
42
+ method_name_base = value.gsub('-', '_')
43
+ writer_name = "#{method_name_base}="
44
+ define_method writer_name do |new_value|
45
+ new_prop = new_value ? value : values.find {|l| l != value}
46
+ __send__ "rendition_#{property}=", new_prop
47
+ end
48
+
49
+ maker_name = "make_#{method_name_base}"
50
+ define_method maker_name do
51
+ __send__ "rendition_#{property}=", value
52
+ end
53
+ destructive_method_name = "#{method_name_base}!"
54
+ alias_method destructive_method_name, maker_name
55
+
56
+ predicate_name = "#{method_name_base}?"
57
+ define_method predicate_name do
58
+ __send__("rendition_#{property}") == value
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ module PackageMixin
65
+ # @return [true, false]
66
+ def using_fixed_layout
67
+ prefix.has_key? PREFIX_KEY and
68
+ prefix[PREFIX_KEY] == PREFIX_VALUE
69
+ end
70
+ alias using_fixed_layout? using_fixed_layout
71
+
72
+ # @param using_fixed_layout [true, false]
73
+ def using_fixed_layout=(using_fixed_layout)
74
+ if using_fixed_layout
75
+ prefix[PREFIX_KEY] = PREFIX_VALUE
76
+ else
77
+ prefix.delete PREFIX_KEY
78
+ end
79
+ end
80
+ end
81
+
82
+ module MetadataMixin
83
+ extend Rendition
84
+
85
+ RENDITION_PROPERTIES.each_pair do |property, values|
86
+ define_method "rendition_#{property}" do
87
+ meta = metas.find {|m| m.property == "rendition:#{property}"}
88
+ meta ? meta.content : values.first
89
+ end
90
+
91
+ define_method "rendition_#{property}=" do |new_value|
92
+ raise UnsupportedRenditionValue, new_value unless values.include? new_value
93
+
94
+ prefixed_property = "rendition:#{property}"
95
+ values_to_be_deleted = values - [new_value]
96
+ metas.delete_if {|meta| meta.property == prefixed_property && values_to_be_deleted.include?(meta.content)}
97
+ unless metas.any? {|meta| meta.property == prefixed_property && meta.content == new_value}
98
+ meta = Package::Metadata::Meta.new
99
+ meta.property = prefixed_property
100
+ meta.content = new_value
101
+ metas << meta
102
+ end
103
+ new_value
104
+ end
105
+ end
106
+
107
+ def_rendition_methods
108
+ end
109
+
110
+ module ItemrefMixin
111
+ extend Rendition
112
+
113
+ PAGE_SPREAD_PROPERTY = 'center'
114
+ PAGE_SPREAD_PREFIX = 'rendition:page-spread-'
115
+
116
+ class << self
117
+ # @todo Define using Module#prepend after Ruby 2.0 will become popular
118
+ def included(base)
119
+ return if base.instance_methods.include? :page_spread_without_fixed_layout
120
+ base.__send__ :alias_method, :page_spread_without_fixed_layout, :page_spread
121
+ base.__send__ :alias_method, :page_spread_writer_without_fixed_layout, :page_spread=
122
+
123
+ prefixed_page_spread_property = "#{PAGE_SPREAD_PREFIX}#{PAGE_SPREAD_PROPERTY}"
124
+ base.__send__ :define_method, :page_spread do
125
+ property = page_spread_without_fixed_layout
126
+ return property if property
127
+ property = properties.find {|prop| prop == prefixed_page_spread_property}
128
+ property ? PAGE_SPREAD_PROPERTY : nil
129
+ end
130
+
131
+ base.__send__ :define_method, :page_spread= do |new_value|
132
+ if new_value == PAGE_SPREAD_PROPERTY
133
+ page_spread_writer_without_fixed_layout nil
134
+ properties << prefixed_page_spread_property
135
+ else
136
+ page_spread_writer_without_fixed_layout new_value
137
+ end
138
+ new_value
139
+ end
140
+ end
141
+ end
142
+
143
+ RENDITION_PROPERTIES.each do |property, values|
144
+ rendition_property_prefix = "rendition:#{property}-"
145
+
146
+ reader_name = "rendition_#{property}"
147
+ define_method reader_name do
148
+ prop_value = properties.find {|prop| prop.start_with? rendition_property_prefix}
149
+ prop_value ? prop_value.gsub(/\A#{Regexp.escape(rendition_property_prefix)}/, '') :
150
+ spine.package.metadata.__send__(reader_name)
151
+ end
152
+
153
+ writer_name = "#{reader_name}="
154
+ define_method writer_name do |new_value|
155
+ if new_value.nil?
156
+ properties.delete_if {|prop| prop.start_with? rendition_property_prefix}
157
+ return new_value
158
+ end
159
+
160
+ raise UnsupportedRenditionValue, new_value unless values.include? new_value
161
+
162
+ values_to_be_deleted = (values - [new_value]).map {|value| "#{rendition_property_prefix}#{value}"}
163
+ properties.delete_if {|prop| values_to_be_deleted.include? prop}
164
+ new_property = "#{rendition_property_prefix}#{new_value}"
165
+ properties << new_property unless properties.any? {|prop| prop == new_property}
166
+ new_value
167
+ end
168
+ end
169
+
170
+ def_rendition_methods
171
+ end
172
+
173
+ module ItemMixin
174
+ extend Rendition
175
+
176
+ RENDITION_PROPERTIES.each_key do |property|
177
+ define_method "rendition_#{property}" do
178
+ itemref.__send__ property
179
+ end
180
+
181
+ writer_name = "rendition_#{property}="
182
+ define_method writer_name do |value|
183
+ itemref.__send__ writer_name, value
184
+ end
185
+ end
186
+
187
+ def_rendition_methods
188
+ end
189
+
190
+ module ContentDocumentMixin
191
+ extend Rendition
192
+
193
+ RENDITION_PROPERTIES.each_key do |property|
194
+ reader_name = "rendition_#{property}"
195
+ define_method reader_name do
196
+ item.__send__ reader_name
197
+ end
198
+
199
+ writer_name = "rendition_#{property}="
200
+ define_method writer_name do |value|
201
+ item.__send__ writer_name, value
202
+ end
203
+ end
204
+
205
+ def_rendition_methods
206
+ end
207
+ end
208
+ end
209
+ end
@@ -33,6 +33,10 @@ module EPUB
33
33
  define_content_model model
34
34
  end
35
35
 
36
+ def initialize
37
+ @prefix = {}
38
+ end
39
+
36
40
  def unique_identifier
37
41
  @metadata.unique_identifier
38
42
  end
@@ -9,10 +9,12 @@ module EPUB
9
9
  attr_accessor :package,
10
10
  :id
11
11
 
12
+ # @return self
12
13
  def <<(item)
13
14
  @items ||= {}
14
15
  item.manifest = self
15
16
  @items[item.id] = item
17
+ self
16
18
  end
17
19
 
18
20
  def navs
@@ -90,7 +92,13 @@ module EPUB
90
92
 
91
93
  def content_document
92
94
  return nil unless %w[application/xhtml+xml image/svg+xml].include? media_type
93
- @content_document ||= Parser::ContentDocument.parse(self)
95
+ @content_document ||= Parser::ContentDocument.new(self).parse
96
+ end
97
+
98
+ # @return [Package::Spine::Itemref]
99
+ # @return nil when no Itemref refers this Item
100
+ def itemref
101
+ manifest.package.spine.itemrefs.find {|itemref| itemref.idref == id}
94
102
  end
95
103
 
96
104
  protected
@@ -6,10 +6,12 @@ module EPUB
6
6
  :id, :toc, :page_progression_direction
7
7
  attr_reader :itemrefs
8
8
 
9
+ # @return self
9
10
  def <<(itemref)
10
11
  @itemrefs ||= []
11
12
  itemref.spine = self
12
13
  @itemrefs << itemref
14
+ self
13
15
  end
14
16
 
15
17
  # @yield [itemref]
@@ -32,20 +34,54 @@ module EPUB
32
34
  end
33
35
 
34
36
  class Itemref
37
+ PAGE_SPREAD_PROPERTIES = ['left'.freeze, 'right'.freeze].freeze
38
+ PAGE_SPREAD_PREFIX = 'page-spread-'.freeze
39
+
35
40
  attr_accessor :spine,
36
41
  :idref, :linear, :id, :properties
37
42
  alias linear? linear
38
43
 
44
+ def initialize
45
+ @properties = []
46
+ end
47
+
39
48
  # @return [Package::Manifest::Item] item referred by this object
40
49
  def item
41
50
  @item ||= @spine.package.manifest[idref]
42
51
  end
43
52
 
53
+ def item=(item)
54
+ self.idref = item.id
55
+ item
56
+ end
57
+
44
58
  def ==(other)
45
59
  [:spine, :idref, :linear, :id].all? {|meth|
46
60
  self.__send__(meth) == other.__send__(meth)
47
61
  } and (other.properties - properties).empty?
48
62
  end
63
+
64
+ # @return ["left", "right", nil]
65
+ def page_spread
66
+ property = properties.find {|prop| prop.start_with? PAGE_SPREAD_PREFIX}
67
+ property ? property.gsub(/\A#{Regexp.escape(PAGE_SPREAD_PREFIX)}/, '') : nil
68
+ end
69
+
70
+ # @param new_value ["left", "right", nil]
71
+ def page_spread=(new_value)
72
+ if new_value.nil?
73
+ properties.delete_if {|prop| prop.start_with? PAGE_SPREAD_PREFIX}
74
+ return new_value
75
+ end
76
+
77
+ raise "Unsupported page-spread property: #{new_value}" unless PAGE_SPREAD_PROPERTIES.include? new_value
78
+
79
+ props_to_be_deleted = (PAGE_SPREAD_PROPERTIES - [new_value]).map {|prop| "#{PAGE_SPREAD_PREFIX}#{prop}"}
80
+ properties.delete_if {|prop| props_to_be_deleted.include? prop}
81
+ new_property = "#{PAGE_SPREAD_PREFIX}#{new_value}"
82
+ properties << new_property unless properties.include? new_property
83
+ new_value
84
+ end
49
85
  end
50
86
  end
51
87
  end
@@ -0,0 +1,32 @@
1
+ require_relative 'helper'
2
+
3
+ class TestContentDocument < Test::Unit::TestCase
4
+ include EPUB::ContentDocument
5
+
6
+ def test_top_level?
7
+ manifest = EPUB::Publication::Package::Manifest.new
8
+ spine = EPUB::Publication::Package::Spine.new
9
+
10
+ item1 = EPUB::Publication::Package::Manifest::Item.new
11
+ item1.id = 'item1'
12
+ item2 = EPUB::Publication::Package::Manifest::Item.new
13
+ item2.id = 'item2'
14
+ manifest << item1 << item2
15
+
16
+ itemref = EPUB::Publication::Package::Spine::Itemref.new
17
+ itemref.idref = 'item1'
18
+ spine << itemref
19
+
20
+ package = EPUB::Publication::Package.new
21
+ package.manifest = manifest
22
+ package.spine = spine
23
+
24
+ doc = XHTML.new
25
+ doc.item = item1
26
+ assert_true doc.top_level?
27
+
28
+ doc = XHTML.new
29
+ doc.item = item2
30
+ assert_false doc.top_level?
31
+ end
32
+ end
@@ -0,0 +1,258 @@
1
+ require_relative 'helper'
2
+ require 'epub/book'
3
+ require 'epub/publication'
4
+ require 'epub/publication/fixed_layout'
5
+
6
+ class EPUB::Publication::Package
7
+ include EPUB::Publication::FixedLayout
8
+ end
9
+
10
+ class TestFixedLayout < Test::Unit::TestCase
11
+ include EPUB::Publication
12
+
13
+ class TestPackage < TestFixedLayout
14
+ def test_package_dont_use_fixed_layout_by_default
15
+ assert_false Package.new.using_fixed_layout?
16
+ end
17
+
18
+ def test_make_package_use_fixed_layout
19
+ package = Package.new
20
+ package.using_fixed_layout = true
21
+ assert_true package.using_fixed_layout?
22
+
23
+ package.using_fixed_layout = false
24
+ assert_false package.using_fixed_layout?
25
+ end
26
+ end
27
+
28
+ class TestMetadata < TestFixedLayout
29
+ def setup
30
+ @metadata = Package::Metadata.new
31
+ end
32
+
33
+ def test_default_layout_is_reflowable
34
+ assert_equal 'reflowable', @metadata.rendition_layout
35
+ assert_true @metadata.reflowable?
36
+ end
37
+
38
+ def test_deafult_layout_is_not_pre_paginated
39
+ assert_false @metadata.pre_paginated?
40
+ end
41
+
42
+ def test_layout_is_pre_paginated_when_has_meta_with_rendition_layout
43
+ meta = Package::Metadata::Meta.new
44
+ meta.property = 'rendition:layout'
45
+ meta.content = 'pre-paginated'
46
+ @metadata.metas << meta
47
+ assert_equal 'pre-paginated', @metadata.rendition_layout
48
+ assert_true @metadata.pre_paginated?
49
+ end
50
+
51
+ def test_layout_is_reflowable_when_has_meta_with_rendition_layout
52
+ meta = Package::Metadata::Meta.new
53
+ meta.property = 'rendition:layout'
54
+ meta.content = 'reflowable'
55
+ @metadata.metas << meta
56
+ assert_equal 'reflowable', @metadata.rendition_layout
57
+ assert_true @metadata.reflowable?
58
+ end
59
+
60
+ def test_can_set_rendition_layout_by_method_of_metadata
61
+ @metadata.pre_paginated = true
62
+ assert_equal 'pre-paginated', @metadata.rendition_layout
63
+ assert_false @metadata.reflowable?
64
+ assert_true @metadata.pre_paginated?
65
+
66
+ @metadata.reflowable = true
67
+ assert_equal 'reflowable', @metadata.rendition_layout
68
+ assert_true @metadata.reflowable?
69
+ assert_false @metadata.pre_paginated?
70
+ end
71
+
72
+ def test_remove_meta_for_pre_paginated_when_making_reflowable
73
+ meta = Package::Metadata::Meta.new
74
+ meta.property = 'rendition:layout'
75
+ meta.content = 'pre-paginated'
76
+ @metadata.metas << meta
77
+
78
+ @metadata.reflowable = true
79
+ assert_false @metadata.metas.any? {|m| m.property == 'rendition:layout' && m.content == 'pre-paginated'}
80
+ end
81
+
82
+ def test_remove_meta_for_reflowable_when_making_pre_paginated
83
+ meta = Package::Metadata::Meta.new
84
+ meta.property = 'rendition:layout'
85
+ meta.content = 'pre-paginated'
86
+ @metadata.metas << meta
87
+ meta = Package::Metadata::Meta.new
88
+ meta.property = 'rendition:layout'
89
+ meta.content = 'reflowable'
90
+ @metadata.metas << meta
91
+
92
+ @metadata.pre_paginated = true
93
+ assert_false @metadata.metas.any? {|m| m.property == 'rendition:layout' && m.content == 'reflowable'}
94
+ end
95
+
96
+ def test_layout_setter
97
+ @metadata.rendition_layout = 'reflowable'
98
+ assert_equal 'reflowable', @metadata.rendition_layout
99
+
100
+ @metadata.rendition_layout = 'pre-paginated'
101
+ assert_equal 'pre-paginated', @metadata.rendition_layout
102
+
103
+ assert_raise FixedLayout::UnsupportedRenditionValue do
104
+ @metadata.rendition_layout = 'undefined'
105
+ end
106
+ end
107
+
108
+ def test_utility_methods_for_rendition_layout_setter
109
+ @metadata.make_pre_paginated
110
+ assert_equal 'pre-paginated', @metadata.rendition_layout
111
+
112
+ @metadata.make_reflowable
113
+ assert_equal 'reflowable', @metadata.rendition_layout
114
+
115
+ @metadata.pre_paginated!
116
+ assert_equal 'pre-paginated', @metadata.rendition_layout
117
+
118
+ @metadata.reflowable!
119
+ assert_equal 'reflowable', @metadata.rendition_layout
120
+ end
121
+
122
+ def test_default_orientation_is_auto
123
+ assert_equal 'auto', @metadata.rendition_orientation
124
+ end
125
+ end
126
+
127
+ class TestItemref < TestFixedLayout
128
+ def setup
129
+ @itemref = Package::Spine::Itemref.new
130
+ @package = Package.new
131
+ @package.metadata = Package::Metadata.new
132
+ @package.spine = Package::Spine.new
133
+ @package.spine << @itemref
134
+ end
135
+
136
+ def test_inherits_metadatas_rendition_layout_by_default
137
+ assert_equal 'reflowable', @itemref.rendition_layout
138
+
139
+ @package.metadata.rendition_layout = 'pre-paginated'
140
+ assert_equal 'pre-paginated', @itemref.rendition_layout
141
+ end
142
+
143
+ def test_overwrite_rendition_layout_of_metadata_when_set_explicitly
144
+ @package.metadata.rendition_layout = 'pre-paginated'
145
+ @itemref.properties << 'rendition:layout-reflowable'
146
+ assert_equal 'reflowable', @itemref.rendition_layout
147
+ end
148
+
149
+ def test_can_set_explicitly
150
+ @itemref.rendition_layout = 'pre-paginated'
151
+ assert_equal 'pre-paginated', @itemref.rendition_layout
152
+ end
153
+
154
+ def test_can_unset_explicitly
155
+ @itemref.rendition_layout = 'pre-paginated'
156
+ @itemref.rendition_layout = nil
157
+ assert_equal 'reflowable', @itemref.rendition_layout
158
+ assert_not_include @itemref.properties, 'rendition:layout-reflowable'
159
+ end
160
+
161
+ def test_property_added_when_rendition_layout_set
162
+ @itemref.rendition_layout = 'pre-paginated'
163
+ assert_include @itemref.properties, 'rendition:layout-pre-paginated'
164
+ end
165
+
166
+ def test_opposite_property_removed_if_exists_when_rendition_layout_set
167
+ @itemref.rendition_layout = 'reflowable'
168
+ @itemref.rendition_layout = 'pre-paginated'
169
+ assert_not_include @itemref.properties, 'rendition:layout-reflowable'
170
+ end
171
+
172
+ def test_utility_methods
173
+ assert_true @itemref.reflowable?
174
+
175
+ @itemref.make_pre_paginated
176
+ assert_false @itemref.reflowable?
177
+ assert_true @itemref.pre_paginated?
178
+ assert_not_include @itemref.properties, 'rendition:layout-reflowbale'
179
+ assert_include @itemref.properties, 'rendition:layout-pre-paginated'
180
+ end
181
+
182
+ def test_inherits_metadatas_rendition_spread_by_default
183
+ assert_equal 'auto', @itemref.rendition_spread
184
+
185
+ @package.metadata.rendition_spread = 'portrait'
186
+ assert_equal 'portrait', @itemref.rendition_spread
187
+ end
188
+
189
+ def test_rendition_property_reader_has_alias
190
+ assert_equal 'auto', @itemref.orientation
191
+
192
+ @itemref.orientation = 'landscape'
193
+ assert_equal 'landscape', @itemref.rendition_orientation
194
+ end
195
+
196
+ def test_page_spread_center_defined
197
+ @itemref.properties << 'rendition:page-spread-center'
198
+ assert_equal 'center', @itemref.page_spread
199
+ end
200
+
201
+ def test_can_make_page_spread_center_explicitly
202
+ @itemref.page_spread = 'center'
203
+ assert_include @itemref.properties, 'rendition:page-spread-center'
204
+ end
205
+
206
+ def test_page_spread_is_exclusive
207
+ @itemref.page_spread = 'right'
208
+ @itemref.page_spread = 'center'
209
+ assert_not_include @itemref.properties, 'page-spread-right'
210
+ end
211
+ end
212
+
213
+ class TestItem < TestFixedLayout
214
+ def setup
215
+ package = Package.new
216
+ package.manifest = Package::Manifest.new
217
+ @item = Package::Manifest::Item.new
218
+ @item.id = 'item'
219
+ package.manifest << @item
220
+ package.spine = Package::Spine.new
221
+ @itemref = Package::Spine::Itemref.new
222
+ @itemref.idref = 'item'
223
+ package.spine << @itemref
224
+ end
225
+
226
+ def test_can_access_rendition_attributes
227
+ @itemref.rendition_layout = 'pre-paginated'
228
+ assert_true @item.pre_paginated?
229
+
230
+ @item.rendition_orientation = 'portrait'
231
+ assert_equal 'portrait', @itemref.rendition_orientation
232
+ end
233
+ end
234
+
235
+ class TestContentDocument < TestFixedLayout
236
+ def setup
237
+ package = Package.new
238
+ package.manifest = Package::Manifest.new
239
+ item = Package::Manifest::Item.new
240
+ item.id = 'item'
241
+ package.manifest << item
242
+ package.spine = Package::Spine.new
243
+ @itemref = Package::Spine::Itemref.new
244
+ @itemref.idref = 'item'
245
+ package.spine << @itemref
246
+ @doc = EPUB::ContentDocument::XHTML.new
247
+ @doc.item = item
248
+ end
249
+
250
+ def test_can_access_rendition_attributes
251
+ @itemref.rendition_layout = 'pre-paginated'
252
+ assert_true @doc.pre_paginated?
253
+
254
+ @doc.rendition_spread = 'none'
255
+ assert_equal 'none', @itemref.rendition_spread
256
+ end
257
+ end
258
+ end