epub-parser 0.1.3 → 0.1.4

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