bb-epub 0.0.1

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.
@@ -0,0 +1,87 @@
1
+ # See: http://www.idpf.org/epub/30/spec/epub30-publications.html#sec-opf-metadata-identifiers-pid
2
+ #
3
+ # "To redress this problem of identifying minor modifications and releases
4
+ # without changing the Unique Identifier, this specification defines the
5
+ # semantics for a Package Identifier, or means of distinguishing and
6
+ # sequentially ordering Publications with the same Unique Identifier. The
7
+ # Package Identifier is not an actual property in the package metadata section,
8
+ # but is a value that can be obtained from two required pieces of metadata: the
9
+ # Unique Identifier and the last modification date of the Publication.
10
+ #
11
+ # "When the taken together, the combined value represents a unique identity that
12
+ # can be used to distinguish any particular version of an EPUB Manifestation
13
+ # from another. To ensure that a Package Identifier can be constructed, the
14
+ # Publication must include exactly one `modified` property containing
15
+ # the last modification date (see meta)."
16
+ #
17
+ class BbEPUB::Transform::PackageIdentifier < Bookbinder::Transform
18
+
19
+ def dependencies
20
+ [BbEPUB::Transform::Metadata, BbEPUB::Transform::NCX]
21
+ end
22
+
23
+
24
+ def to_map(package)
25
+ opf_doc = package.file(:opf).document('r')
26
+
27
+ # Unique identifier
28
+ unless id_hashes = package.map['metadata']['identifier']
29
+ package.warn('No identifiers found in package metadata')
30
+ end
31
+ if id_hashes && uid_id = opf_doc.root['unique-identifier']
32
+ uid_hash = id_hashes.detect { |id_hash|
33
+ id_hash['id'] && id_hash['id']['@'] == uid_id
34
+ }
35
+ else
36
+ package.warn('OPF root "unique-identifier" attribute not found')
37
+ end
38
+ if uid_hash
39
+ id_hashes.delete(uid_hash)
40
+ package.map['metadata'].delete('identifier') if id_hashes.empty?
41
+ package.map['unique-identifier'] = uid_hash['@']
42
+ else
43
+ package.warn('OPF metadata for required "unique-identifier" not found')
44
+ end
45
+
46
+ # Modified
47
+ begin
48
+ mod_hash = package.map['metadata'].delete('dcterms:modified').first
49
+ timestamp = Time.parse(mod_hash['@'])
50
+ rescue
51
+ package.warn('OPF metadata for "modified" not found - using now()')
52
+ timestamp = Time.now
53
+ end
54
+ package.map['modified'] = timestamp.utc.iso8601
55
+ end
56
+
57
+
58
+ def from_map(package)
59
+ map_uid = package.map['unique-identifier']
60
+ map_mod = package.map['modified']
61
+ opf_doc = package.file(:opf).document
62
+ metadata_tag = opf_doc.find('opf|metadata')
63
+
64
+ # Unique identifier
65
+ opf_doc.root['unique-identifier'] = 'unique-identifier'
66
+ opf_doc.new_node('dc:identifier', :append => metadata_tag) { |uid_tag|
67
+ uid_tag['id'] = 'unique-identifier'
68
+ uid_tag.content = map_uid
69
+ }
70
+
71
+ # Add the unique identifier to the NCX as well, because too
72
+ # much data redundancy is never enough.
73
+ package.if_file(:ncx) { |ncx_file|
74
+ ncx_file.document.new_node('meta', :append => 'ncx|head') { |uid_meta_tag|
75
+ uid_meta_tag['name'] = 'dtb:uid'
76
+ uid_meta_tag['content'] = map_uid
77
+ }
78
+ }
79
+
80
+ # Modified
81
+ opf_doc.new_node('meta', :append => metadata_tag) { |mod_tag|
82
+ mod_tag['property'] = 'dcterms:modified'
83
+ mod_tag.content = map_mod
84
+ }
85
+ end
86
+
87
+ end
@@ -0,0 +1,273 @@
1
+ # For the original Apple extension to EPUB 2, see any version of the iBooks
2
+ # Asset Guide prior to version 5. Here's a link to version 4.7:
3
+ #
4
+ # http://www.mobileread.com/forums/attachment.php?attachmentid=74234
5
+ #
6
+ # In particular, see the section titled "Configuring Display Options"
7
+ #
8
+ # For the EPUB 3 Fixed Layout specification (actually a Mixed Layout
9
+ # specification), see:
10
+ #
11
+ # http://www.idpf.org/epub/301/spec/epub-publications.html#sec-package-metadata-fxl
12
+ #
13
+ # NOTE: a potential gotcha. The EPUB3 properties are 'rendition:...',
14
+ # with a colon, whereas the Openbook form follows the dash convention
15
+ # for property names, ie 'rendition-...'.
16
+ #
17
+ class BbEPUB::Transform::Rendition < Bookbinder::Transform
18
+
19
+ RENDITION_DEFAULT_PROPERTIES = {
20
+ 'rendition:flow' => 'paginated',
21
+ 'rendition:layout' => 'reflowable',
22
+ 'rendition:spread' => 'auto',
23
+ 'rendition:orientation' => 'auto',
24
+ 'rendition:viewport' => nil
25
+ }
26
+ APPLE_FXL_PATH = 'META-INF/com.apple.ibooks.display-options.xml'
27
+
28
+
29
+ def dependencies
30
+ [BbEPUB::Transform::Spine, BbEPUB::Transform::Metadata]
31
+ end
32
+
33
+
34
+ def to_map(package)
35
+ package.map['rendition-format'] = 'ebook'
36
+ book_properties = book_properties_from_apple_display_options(package)
37
+ book_properties.update(book_properties_from_opf(package))
38
+ component_rendition_properties_from_opf(package, book_properties)
39
+ end
40
+
41
+
42
+ def from_map(package)
43
+ first_cmpt = package.map['spine'][0]
44
+ book_properties = {
45
+ 'rendition:flow' => first_cmpt['rendition-flow'],
46
+ 'rendition:layout' => first_cmpt['rendition-layout'],
47
+ 'rendition:spread' => first_cmpt['rendition-spread'],
48
+ 'rendition:orientation' => first_cmpt['rendition-orientation'],
49
+ 'rendition:viewport' => first_cmpt['rendition-viewport']
50
+ }
51
+ opf_doc = package.file(:opf).document
52
+ opf_doc.add_prefix('rendition')
53
+ book_properties.each_pair { |prop, content|
54
+ if content && RENDITION_DEFAULT_PROPERTIES[prop] != content
55
+ book_meta_tag(opf_doc, prop, content)
56
+ end
57
+ }
58
+ opf_doc.each('opf|spine > opf|itemref') { |itemref|
59
+ # Update the itemref properties attribute.
60
+ cmpt = component_for_itemref(package, itemref)
61
+ props = (itemref['properties'] || '').split
62
+ book_properties.each_pair { |prop, book_content|
63
+ key = prop.gsub(':', '-')
64
+ if cmpt[key] != book_content
65
+ cmpt_content = cmpt[key] || RENDITION_DEFAULT_PROPERTIES[prop]
66
+ props.push("#{prop}-#{cmpt_content}")
67
+ end
68
+ }
69
+ if ['left', 'right'].include?(cmpt['rendition-position'])
70
+ props.push('page-spread-'+cmpt['rendition-position'])
71
+ elsif cmpt['rendition-position'] == 'center'
72
+ props.push('rendition:page-spread-center')
73
+ end
74
+ if cmpt['rendition-align-x-center']
75
+ props.push('rendition:align-x-center')
76
+ end
77
+ itemref['properties'] = props.join(' ') if props.any?
78
+
79
+ # Update the viewport/viewBox attribute in the component file.
80
+ add_icb_to_component(
81
+ package.file(cmpt['path']),
82
+ cmpt['rendition-viewport']
83
+ ) if cmpt['rendition-viewport']
84
+ }
85
+ end
86
+
87
+
88
+ protected
89
+
90
+ # Inspects Apple's display-options file in META-INF for some
91
+ # option values that we can convert to EPUB3 equivalents.
92
+ # Returns a hash with zero or more rendition:* properties.
93
+ #
94
+ def book_properties_from_apple_display_options(package)
95
+ {}.tap { |book_properties|
96
+ package.if_file(APPLE_FXL_PATH, false) { |apple_file|
97
+ apple_options = apple_file.document('r').search(
98
+ 'display_options > platform[name="*"] > option'
99
+ )
100
+ apple_hash = apple_options.inject({}) { |acc, apple_option|
101
+ acc.update(apple_option['name'] => apple_option.content.strip)
102
+ }
103
+
104
+ if apple_hash['fixed-layout'] == 'true'
105
+ book_properties['rendition-layout'] = 'pre-paginated'
106
+ end
107
+
108
+ if apple_hash['orientation-lock'] == 'landscape-only'
109
+ book_properties['rendition-orientation'] = 'landscape'
110
+ elsif apple_hash['orientation-lock'] == 'portrait-only'
111
+ book_properties['rendition-orientation'] = 'portrait'
112
+ end
113
+
114
+ # FIXME: not really a 1-to-1 mapping - should we ignore this option?
115
+ # UPDATE: yes, disabled for now.
116
+ # if apple_hash['open-to-spread'] == 'true'
117
+ # book_properties['rendition-spread'] = 'both'
118
+ # end
119
+ }
120
+ }
121
+ end
122
+
123
+
124
+ # Looks for book rendition properties in the OPF <metadata>
125
+ # tag, returning a hash with zero or more such properties.
126
+ #
127
+ # Book-level rendition properties are:
128
+ #
129
+ # <meta property="rendition:layout">reflowable</meta>
130
+ # <meta property="rendition:spread">auto</meta>
131
+ # <meta property="rendition:orientation">landscape</meta>
132
+ #
133
+ def book_properties_from_opf(package)
134
+ {}.tap { |book_properties|
135
+ spent = []
136
+ package.map['metadata'].each_pair { |key, value|
137
+ if RENDITION_DEFAULT_PROPERTIES.keys.include?(key)
138
+ book_properties.update(key.sub(':', '-') => value.first['@'])
139
+ spent.push(key)
140
+ end
141
+ }
142
+ spent.each { |key| package.map['metadata'].delete(key) }
143
+ if vpt_str = book_properties['rendition-viewport']
144
+ if vpt = parse_viewport_string(vpt_str)
145
+ book_properties['rendition-viewport'] = vpt
146
+ else
147
+ package.warn("Viewport string unparseable: #{vpt_str}")
148
+ end
149
+ end
150
+ }
151
+ end
152
+
153
+
154
+ # Iterates over each spine component looking for component-specific
155
+ # rendition properties, which are merged over the top of the default
156
+ # book-level properties, then applied directly to the map's component.
157
+ #
158
+ def component_rendition_properties_from_opf(package, book_properties)
159
+ opf_doc = package.file(:opf).document('r')
160
+ opf_doc.each('opf|spine opf|itemref') { |itemref|
161
+ cmpt = component_for_itemref(package, itemref)
162
+ cmpt_properties = book_properties.clone
163
+ (itemref['properties'] || '').split.each { |itemref_prop|
164
+ if match = itemref_prop.match(/^rendition:flow-(.+)/)
165
+ cmpt_properties['rendition-flow'] = match[1]
166
+ end
167
+ if match = itemref_prop.match(/^rendition:layout-(.+)/)
168
+ cmpt_properties['rendition-layout'] = match[1]
169
+ end
170
+ if match = itemref_prop.match(/^rendition:spread-(.+)/)
171
+ cmpt_properties['rendition-spread'] = match[1]
172
+ end
173
+ if match = itemref_prop.match(/^rendition:orientation-(.+)/)
174
+ cmpt_properties['rendition-spread'] = match[1]
175
+ end
176
+ if match = itemref_prop.match(/^(rendition:)?page-spread-(\w+)/)
177
+ cmpt_properties['rendition-position'] = match[2]
178
+ end
179
+ if match = itemref_prop.match(/^rendition:align-x-center$/)
180
+ cmpt_properties['rendition-align-x-center'] = true
181
+ end
182
+ }
183
+
184
+ # The rendition:viewport should ONLY be set on the component if
185
+ # it is pre-paginated, and we should prefer the values in the
186
+ # HTML file itself over anything we find in the OPF.
187
+ book_vpt = cmpt_properties.delete('rendition-viewport')
188
+ if cmpt_properties['rendition-layout'] == 'pre-paginated'
189
+ cmpt_vpt = retrieve_icb_from_component(package.file(cmpt['path']))
190
+ if cmpt_vpt || book_vpt
191
+ cmpt_properties['rendition-viewport'] = cmpt_vpt || book_vpt
192
+ else
193
+ package.warn("Pre-paginated: viewport not found: #{cmpt['path']}")
194
+ puts("Expected viewport, found nothing")
195
+ cmpt_properties.delete('rendition-layout')
196
+ end
197
+ end
198
+ cmpt.update(cmpt_properties)
199
+ }
200
+ end
201
+
202
+
203
+ # Given an itemref tag, finds the map's component hash that
204
+ # corresponds to it. (Look-up is performed using href, via
205
+ # the manifest item tag.)
206
+ #
207
+ def component_for_itemref(package, itemref)
208
+ package.map['spine'].detect { |cmpt|
209
+ cmpt['id'] == itemref['idref']
210
+ }
211
+ end
212
+
213
+
214
+ # Finds the "initial containing block" (in EPUB3 parlance) for
215
+ # the given component document. This is either the SVG viewBox
216
+ # attribute, or the HTML <meta name="viewport"> content attribute.
217
+ #
218
+ def retrieve_icb_from_component(cmpt_file)
219
+ cmpt_doc = cmpt_file.document('r')
220
+ if cmpt_file.media_type.match(/svg/)
221
+ icb = cmpt_doc.root['viewBox'].split
222
+ { 'width' => icb[0], 'height' => icb[1] }
223
+ elsif viewport_meta = cmpt_doc.find('meta[name="viewport"]')
224
+ parse_viewport_string(viewport_meta['content'])
225
+ end
226
+ end
227
+
228
+
229
+ def parse_viewport_string(icb)
230
+ {
231
+ 'width' => icb.match(/width\s*=\s*(\d+)/)[1].to_i,
232
+ 'height' => icb.match(/height\s*=\s*(\d+)/)[1].to_i
233
+ }
234
+ rescue
235
+ # package.warn("Viewport string unparseable: #{icb}")
236
+ nil
237
+ end
238
+
239
+
240
+ # Creates a book-level meta tag for the given rendition:* property,
241
+ # inserting it into the OPF document.
242
+ #
243
+ def book_meta_tag(opf_doc, property, content)
244
+ opf_doc.new_node('meta', :append => 'opf|metadata') { |meta_tag|
245
+ meta_tag['property'] = property
246
+ meta_tag.content = content
247
+ }
248
+ end
249
+
250
+
251
+ # Updates the viewBox or meta-viewport declaration in the
252
+ # component document (SVG or HTML respectively), creating
253
+ # tags/attributes as necessary.
254
+ #
255
+ def add_icb_to_component(cmpt_file, icb)
256
+ cmpt_doc = cmpt_file.document
257
+ if cmpt_file.media_type.match(/svg/)
258
+ cmpt_doc.root['viewBox'] = icb.join(' ')
259
+ elsif viewport_meta = cmpt_doc.find('meta[name="viewport"]')
260
+ vpc = viewport_meta['content']
261
+ vpc.gsub!(/width\s*=\s*\d+/, "width=#{icb['width']}")
262
+ vpc.gsub!(/height\s*=\s*\d+/, "height=#{icb['height']}")
263
+ viewport_meta['content'] = vpc
264
+ else
265
+ cmpt_doc.new_node('meta', :append => 'head') { |viewport_meta_tag|
266
+ viewport_meta_tag['name'] = 'viewport'
267
+ viewport_meta_tag['content'] =
268
+ "width=#{icb['width']},height=#{icb['height']}"
269
+ }
270
+ end
271
+ end
272
+
273
+ end
@@ -0,0 +1,38 @@
1
+ class BbEPUB::Transform::Resources < Bookbinder::Transform
2
+
3
+ def dependencies
4
+ [BbEPUB::Transform::OPF]
5
+ end
6
+
7
+
8
+ # Find all the items in the manifest -- except all spine items, the NCX,
9
+ # and the NAV.
10
+ #
11
+ def to_map(package)
12
+ opf_doc = package.file(:opf).document('r')
13
+ items = opf_doc.search('opf|manifest > opf|item')
14
+ package.map['resources'] = items.collect { |item|
15
+ path = package.make_path(item['href'])
16
+ {
17
+ 'path' => path,
18
+ 'media-type' => item['media-type'],
19
+ 'id' => item['id'] || package.make_id(path)
20
+ }
21
+ }
22
+ end
23
+
24
+
25
+ def from_map(package)
26
+ return unless package.map['resources']
27
+ opf_doc = package.file(:opf).document
28
+ manifest_tag = opf_doc.find('opf|manifest')
29
+ package.map['resources'].each { |rsrc|
30
+ opf_doc.new_node('item', :append => manifest_tag) { |manifest_item_tag|
31
+ manifest_item_tag['href'] = package.make_href(rsrc['path'])
32
+ manifest_item_tag['media-type'] = rsrc['media-type']
33
+ manifest_item_tag['id'] = rsrc['id']
34
+ }
35
+ }
36
+ end
37
+
38
+ end
@@ -0,0 +1,79 @@
1
+ class BbEPUB::Transform::Spine < Bookbinder::Transform
2
+
3
+ def dependencies
4
+ [BbEPUB::Transform::Resources]
5
+ end
6
+
7
+
8
+ def to_map(package)
9
+ opf_doc = package.file(:opf).document('r')
10
+ itemrefs = opf_doc.search('opf|spine > opf|itemref')
11
+ package.map['spine'] = itemrefs.collect { |itemref|
12
+ cmpt = package.map['resources'].detect { |r| r['id'] == itemref['idref'] }
13
+ if cmpt
14
+ package.map['resources'].delete(cmpt)
15
+ cmpt['linear'] = itemref['linear'] == 'no' ? false : true
16
+ cmpt
17
+ else
18
+ package.warn("No manifest item for spine idref: #{itemref['idref']}")
19
+ end
20
+ }.compact
21
+ end
22
+
23
+
24
+ def from_map(package)
25
+ opf_doc = package.file(:opf).document
26
+
27
+ package.map['spine'].each { |cmpt|
28
+ # Convert component to valid XHTML
29
+ cmpt_file = package.file(cmpt['path'])
30
+ cmpt_doc = cmpt_file.document
31
+ # Add the EPUB namespace:
32
+ # FIXME: only add the EPUB namespace if there are any epub:* attrs?
33
+ cmpt_doc.add_namespace('epub')
34
+
35
+ # Update the OPF manifest
36
+ opf_doc.new_node('item', :append => 'opf|manifest') { |manifest_item_tag|
37
+ manifest_item_tag['href'] = package.make_href(cmpt['path'])
38
+ manifest_item_tag['id'] = cmpt['id']
39
+ manifest_item_tag['media-type'] = cmpt['media-type']
40
+ props = component_properties(cmpt, cmpt_doc)
41
+ manifest_item_tag['properties'] = props.join(' ') if props.any?
42
+ }
43
+
44
+ # Update the OPF spine
45
+ opf_doc.new_node('itemref', :append => 'opf|spine') { |spine_item_tag|
46
+ spine_item_tag['idref'] = cmpt['id']
47
+ spine_item_tag['linear'] = 'no' unless cmpt['linear']
48
+ }
49
+ }
50
+ end
51
+
52
+
53
+ protected
54
+
55
+ # Assemble the component properties by inspecting the HTML...
56
+ # because some reading systems can't do this themselves, I guess.
57
+ #
58
+ # NB: the 'cover-image' and 'nav' properties will be set in
59
+ # other tranforms.
60
+ #
61
+ def component_properties(cmpt, cmpt_doc)
62
+ [].tap { |props|
63
+ # 'scripted': check whether there are any script tags in the component.
64
+ props.push('scripted') if cmpt_doc.find('script')
65
+
66
+ # 'mathml': look for a math element
67
+ props.push('mathml') if cmpt_doc.find('math, mathml:math')
68
+
69
+ # 'remote-resources': TODO
70
+
71
+ # 'svg': look for an svg element
72
+ props.push('svg') if cmpt_doc.find('svg, svg:svg')
73
+
74
+ # 'switch': look for an epub:switch element
75
+ props.push('switch') if cmpt_doc.find('epub|switch')
76
+ }
77
+ end
78
+
79
+ end
@@ -0,0 +1,92 @@
1
+ # EPUB2 spec:
2
+ #
3
+ # http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.2.1
4
+ #
5
+ # EPUB3 spec:
6
+ #
7
+ # http://www.idpf.org/epub/30/spec/epub30-publications.html#sec-opf-dctitle
8
+ #
9
+ # On title types:
10
+ #
11
+ # "When the title-type value is drawn from a code list or other formal
12
+ # enumeration, the scheme attribute should be attached to identify its source.
13
+ # When a scheme is not specified, Reading Systems should recognize the
14
+ # following title type values: main, subtitle, short, collection, edition
15
+ # and expanded."
16
+ #
17
+ class BbEPUB::Transform::Title < Bookbinder::Transform
18
+
19
+ TITLE_TYPES = %w[main subtitle short collection edition expanded]
20
+
21
+
22
+ def dependencies
23
+ [BbEPUB::Transform::Metadata, BbEPUB::Transform::NCX]
24
+ end
25
+
26
+
27
+ def to_map(package)
28
+ package.map['title'] = titles = {}
29
+ return unless package.map['metadata']
30
+ return unless metadata_array = package.map['metadata']['title']
31
+ title_data = metadata_array.sort { |a, b|
32
+ if a['display-seq'] && b['display-seq']
33
+ a['display-seq']['@'].to_i <=> b['display-seq']['@'].to_i
34
+ else
35
+ 0
36
+ end
37
+ }
38
+ title_data.each { |tdata|
39
+ t = tdata['@']
40
+ type = tdata['title-type'] ? tdata['title-type']['@'] : 'main'
41
+ next unless TITLE_TYPES.include?(type)
42
+ if titles[type]
43
+ package.warn("Existing title for '#{type}' - discarding '#{t}'")
44
+ else
45
+ titles[type] = t
46
+ metadata_array.delete(tdata)
47
+ end
48
+ }
49
+ # Now that we have "used" the raw metadata for titles, remove it.
50
+ package.map['metadata'].delete('title') if metadata_array.empty?
51
+ end
52
+
53
+
54
+ def from_map(package)
55
+ titles = package.map['title']
56
+ opf_doc = package.file(:opf).document
57
+ metadata_tag = opf_doc.find('opf|metadata')
58
+ seq = 0
59
+ titles.each_pair { |type, title|
60
+ title_tag = opf_doc.new_node('dc:title', :append => metadata_tag)
61
+ title_tag.content = title
62
+ if titles.length > 1
63
+ seq += 1
64
+ title_id = "dc-title-metadata-#{seq}"
65
+ title_tag['id'] = title_id
66
+ opf_doc.new_node('meta', :append => metadata_tag) { |type_meta_tag|
67
+ type_meta_tag.content = type
68
+ type_meta_tag['property'] = 'title-type'
69
+ type_meta_tag['refines'] = '#'+title_id
70
+ }
71
+ opf_doc.new_node('meta', :append => metadata_tag) { |seq_meta_tag|
72
+ seq_meta_tag.content = seq
73
+ seq_meta_tag['property'] = 'display-seq'
74
+ seq_meta_tag['refines'] = '#'+title_id
75
+ }
76
+ end
77
+ }
78
+
79
+ # Add it to the NCX if that file exists in the package
80
+ package.if_file(:ncx) { |ncx_file|
81
+ ncx_title = [titles['main'], titles['subtitle']].compact.join(': ')
82
+ ncx_doc = ncx_file.document
83
+ ncx_doc.new_node('docTitle') { |doc_title_tag|
84
+ ncx_doc.new_node('text', :append => doc_title_tag) { |text_tag|
85
+ text_tag.content = ncx_title
86
+ }
87
+ ncx_doc.find('ncx|head').add_next_sibling(doc_title_tag)
88
+ }
89
+ }
90
+ end
91
+
92
+ end
@@ -0,0 +1,39 @@
1
+ # See https://itunesconnect.apple.com/docs/iBooksAssetGuide5.1Revision2.pdf
2
+ # pages 22 and 23.
3
+ #
4
+ # "The version of your book is specified within a `meta` element
5
+ # in the Package Document. The `meta` element has a property value
6
+ # of `ibooks:version':
7
+ #
8
+ # <meta property="ibooks:version">1.1.2</meta>
9
+ #
10
+ class BbEPUB::Transform::Version < Bookbinder::Transform
11
+
12
+ def dependencies
13
+ [BbEPUB::Transform::Metadata]
14
+ end
15
+
16
+
17
+ def to_map(package)
18
+ ver_hashes = package.map['metadata'].delete('ibooks:version')
19
+ if ver_hashes && ver_hashes.any?
20
+ package.map['version'] = ver_hashes.first['@']
21
+ end
22
+ end
23
+
24
+
25
+ def from_map(package)
26
+ if package.map['version']
27
+ # Add the ibooks prefix to the package root.
28
+ opf_doc = package.file(:opf).document
29
+ opf_doc.add_prefix('ibooks')
30
+
31
+ # Create the meta node and append it to <metadata>
32
+ opf_doc.new_node('meta', :append => 'opf|metadata') { |ver_tag|
33
+ ver_tag['property'] = 'ibooks:version'
34
+ ver_tag.content = package.map['version']
35
+ }
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,5 @@
1
+ module BbEPUB
2
+
3
+ VERSION = "0.0.1"
4
+
5
+ end
data/lib/bb-epub.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'bookbinder'
2
+
3
+ module BbEPUB
4
+ end
5
+
6
+ module BbEPUB::Transform
7
+ end
8
+
9
+ require 'bb-epub/version'
10
+ require 'bb-epub/package'