gepub 0.5.0 → 0.6.0

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/gepub/item.rb CHANGED
@@ -1,21 +1,76 @@
1
1
  module GEPUB
2
2
  class Item
3
- attr_accessor :itemid, :href, :mediatype, :content
3
+ attr_accessor :content
4
+ def self.create(parent, attributes = {})
5
+ Item.new(attributes['id'], attributes['href'], attributes['media-type'], parent,
6
+ attributes.reject { |k,v| ['id','href','media-type'].member?(k) })
7
+ end
8
+
9
+ def initialize(itemid, itemhref, itemmediatype = nil, parent = nil, attributes = {})
10
+ if attributes['properties'].class == String
11
+ attributes['properties'] = attributes['properties'].split(' ')
12
+ end
13
+ @attributes = {'id' => itemid, 'href' => itemhref, 'media-type' => itemmediatype}.merge(attributes)
14
+ @attributes['media-type'] = guess_mediatype if media_type.nil?
15
+ @parent = parent
16
+ @parent.register_item(self) unless @parent.nil?
17
+ self
18
+ end
19
+
20
+ ['id', 'href', 'media-type', 'fallback', 'properties', 'media-overlay'].each { |name|
21
+ methodbase = name.sub('-','_')
22
+ define_method(methodbase + '=') { |val| @attributes[name] = val }
23
+ define_method('set_' + methodbase) { |val| @attributes[name] = val }
24
+ define_method(methodbase) { @attributes[name] }
25
+ }
26
+
27
+ def itemid
28
+ id
29
+ end
30
+
31
+ def mediatype
32
+ media_type
33
+ end
34
+
35
+ def [](x)
36
+ @attributes[x]
37
+ end
4
38
 
5
- def initialize(itemid, href, mediatype = nil)
6
- @itemid = itemid
7
- @href = href
8
- @mediatype = mediatype || guess_mediatype
39
+ def []=(x,y)
40
+ @attributes[x] = y
41
+ end
42
+
43
+ def add_property(property)
44
+ (@attributes['properties'] ||=[]) << property
45
+ self
9
46
  end
10
47
 
48
+ def cover_image
49
+ add_property('cover-image')
50
+ end
51
+
52
+ def add_raw_content(data)
53
+ @content = data
54
+ end
11
55
  def add_content(io)
12
56
  io.binmode
13
57
  @content = io.read
14
58
  self
15
59
  end
16
-
60
+
61
+ def to_xml(builder, opf_version = '3.0')
62
+ attr = @attributes.dup
63
+ if opf_version.to_f < 3.0
64
+ attr.reject!{ |k,v| k == 'properties' }
65
+ end
66
+ if !attr['properties'].nil?
67
+ attr['properties'] = attr['properties'].join(' ')
68
+ end
69
+ builder.item(attr)
70
+ end
71
+
17
72
  def guess_mediatype
18
- case File.extname(@href)
73
+ case File.extname(href)
19
74
  when /.(html|xhtml)/i
20
75
  'application/xhtml+xml'
21
76
  when /.css/i
@@ -34,6 +89,10 @@ module GEPUB
34
89
  'application/oebps-package+xml'
35
90
  when /.ncx/i
36
91
  'application/x-dtbncx+xml'
92
+ when /.(otf|ttf|ttc)/i
93
+ 'application/vnd.ms-opentype'
94
+ when /.woff/i
95
+ 'application/font-woff'
37
96
  end
38
97
  end
39
98
  end
@@ -0,0 +1,78 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+ module GEPUB
4
+ class Manifest
5
+ include XMLUtil
6
+ attr_accessor :opf_version
7
+ def self.parse(manifest_xml, opf_version = '3.0', id_pool = Package::IDPool.new)
8
+ Manifest.new(opf_version, id_pool) {
9
+ |manifest|
10
+ manifest.instance_eval {
11
+ @xml = manifest_xml
12
+ @namespaces = @xml.namespaces
13
+ @attributes = attr_to_hash(@xml.attributes)
14
+ @items = {}
15
+ @items_by_href = {}
16
+ @xml.xpath("//#{ns_prefix(OPF_NS)}:manifest/#{ns_prefix(OPF_NS)}:item", @namespaces).map {
17
+ |item|
18
+ i = Item.create(self, attr_to_hash(item.attributes))
19
+ @items[i.id] = i
20
+ @items_by_href[i.href] = i
21
+ }
22
+ }
23
+ }
24
+ end
25
+
26
+ def id=(val)
27
+ @attributes['id'] = val
28
+ end
29
+
30
+ def id
31
+ @attributes['id']
32
+ end
33
+
34
+ def initialize(opf_version = '3.0', id_pool = Package::IDPool.new)
35
+ @id_pool = id_pool
36
+ @attributes = {}
37
+ @items = {}
38
+ @items_by_href = {}
39
+ @opf_version = opf_version
40
+ yield self if block_given?
41
+ end
42
+
43
+ def item_list
44
+ @items.dup
45
+ end
46
+
47
+ def item_by_href(href)
48
+ @items_by_href[href]
49
+ end
50
+
51
+ def add_item(id,href,media_type, attributes = {})
52
+ @items[id] = item = Item.new(id,href,media_type,self, attributes)
53
+ @items_by_href[href] = item
54
+ item
55
+ end
56
+
57
+ def to_xml(builder)
58
+ builder.manifest(@attributes) {
59
+ @items.each {
60
+ |itemid, item|
61
+ item.to_xml(builder, @opf_version)
62
+ }
63
+ }
64
+ end
65
+
66
+ def register_item(item)
67
+ raise "id '#{item.id}' is already in use." if @id_pool[item.id]
68
+ @id_pool[item.id] = true
69
+ end
70
+
71
+ def unregister_item(item)
72
+ @items[item.id] = nil
73
+ @items_by_href[item.href] = nil
74
+ @id_pool[item.id] = nil
75
+ end
76
+
77
+ end
78
+ end
data/lib/gepub/meta.rb ADDED
@@ -0,0 +1,119 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+
4
+ module GEPUB
5
+ # Holds one metadata with refine meta elements.
6
+ class Meta
7
+ attr_accessor :content
8
+ attr_reader :name
9
+ def initialize(name, content, parent, attributes= {}, refiners = {})
10
+ @parent = parent
11
+ @name = name
12
+ @content = content
13
+ @attributes = attributes
14
+ @refiners = refiners
15
+ @parent.register_meta(self) unless @parent.nil?
16
+ end
17
+
18
+ def [](x)
19
+ @attributes[x]
20
+ end
21
+
22
+ def []=(x,y)
23
+ @attributes[x] = y
24
+ end
25
+
26
+ def refiner_list(name)
27
+ return @refiners[name].dup
28
+ end
29
+
30
+ def refiner_clear(name)
31
+ if !@refiners[name].nil?
32
+ @refiners[name].each {
33
+ |refiner|
34
+ @parent.unregister_meta(refiner)
35
+ }
36
+ end
37
+ @refiners[name]= []
38
+ end
39
+
40
+ def refiner(name)
41
+ refiner = @refiners[name]
42
+ if refiner.nil? || refiner.size == 0
43
+ nil
44
+ else
45
+ refiner[0]
46
+ end
47
+ end
48
+
49
+ # add a refiner.
50
+ def add_refiner(property, content, attributes = {})
51
+ (@refiners[property] ||= []) << refiner = Meta.new('meta', content, @parent, { 'property' => property }.merge(attributes)) unless content.nil?
52
+ self
53
+ end
54
+
55
+ # set a 'unique' refiner. all other refiners with same property will be removed.
56
+ def refine(property, content, attributes = {})
57
+ if !content.nil?
58
+ refiner_clear(property)
59
+ add_refiner(property, content, attributes)
60
+ end
61
+ self
62
+ end
63
+
64
+ ['title-type', 'identifier-type', 'display-seq', 'file-as', 'group-position'].each {
65
+ |name|
66
+ methodbase = name.sub('-','_')
67
+ define_method(methodbase + '=') { |val| refine(name, val); }
68
+ define_method('set_' + methodbase) { |val| refine(name, val); }
69
+ define_method(methodbase) { refiner(name) }
70
+ }
71
+
72
+ # add alternate script refiner.
73
+ def add_alternates(alternates = {})
74
+ alternates.each {
75
+ |locale, content|
76
+ add_refiner('alternate-script', content, { 'xml:lang' => locale })
77
+ }
78
+ self
79
+ end
80
+
81
+ def to_xml(builder, id_pool, ns = nil, additional_attr = {}, opf_version = '3.0')
82
+ additional_attr ||= {}
83
+ if @refiners.size > 0 && opf_version.to_f >= 3.0
84
+ @attributes['id'] = id_pool.generate_key(:prefix => name) if @attributes['id'].nil?
85
+ end
86
+
87
+ # using eval to parametarize Namespace and content.
88
+ eval "builder#{ ns.nil? || @name == 'meta' ? '' : '[ns]'}.#{@name}(@attributes.reject{|k,v| v.nil?}.merge(additional_attr)#{@content.nil? ? '' : ', @content'})"
89
+
90
+ if @refiners.size > 0 && opf_version.to_f >= 3.0
91
+ additional_attr['refines'] = "##{@attributes['id']}"
92
+ @refiners.each {
93
+ |k, ref_list|
94
+ ref_list.each {
95
+ |ref|
96
+ ref.to_xml(builder, id_pool, nil, additional_attr)
97
+ }
98
+ }
99
+ end
100
+ end
101
+
102
+ def to_s(locale=nil)
103
+ localized = nil
104
+ if !locale.nil?
105
+ prefix = locale.sub(/^(.+?)-.*/, '\1')
106
+ regex = Regexp.new("^((" + locale.split('-').join(')?-?(') + ")?)")
107
+ candidates = @refiners['alternate-script'].select {
108
+ |refiner|
109
+ refiner['xml:lang'] =~ /^#{prefix}-?.*/
110
+ }.sort_by {
111
+ |x|
112
+ x['xml:lang'] =~ regex; $1.size
113
+ }.reverse
114
+ localized = candidates[0].content if candidates.size > 0
115
+ end
116
+ (localized || @content || super).to_s
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,230 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+ require 'time'
4
+
5
+ module GEPUB
6
+ # metadata constants
7
+ module TITLE_TYPE
8
+ ['main','subtitle', 'short', 'collection', 'edition','expanded'].each {
9
+ |type|
10
+ const_set(type.upcase, type)
11
+ }
12
+ end
13
+ # Holds data in /package/metadata
14
+ class Metadata
15
+ include XMLUtil
16
+ attr_accessor :opf_version
17
+ # parse metadata element. metadata_xml should be Nokogiri::XML::Node object.
18
+ def self.parse(metadata_xml, opf_version = '3.0', id_pool = Package::IDPool.new)
19
+ Metadata.new(opf_version, id_pool) {
20
+ |metadata|
21
+ metadata.instance_eval {
22
+ @xml = metadata_xml
23
+ @namespaces = @xml.namespaces
24
+ CONTENT_NODE_LIST.each {
25
+ |node|
26
+ i = 0
27
+ @content_nodes[node] = parse_node(DC_NS, node).sort_by {
28
+ |v|
29
+ [v.refiner('display-seq').to_s.to_i || 2 ** (0.size * 8 - 2) - 1, i += 1]
30
+ }
31
+ }
32
+ @xml.xpath("#{ns_prefix(OPF_NS)}:meta[not(@refines) and @property]", @namespaces).each {
33
+ |node|
34
+ @meta[node['property']] = create_meta(node)
35
+ }
36
+
37
+ @oldstyle_meta = parse_opf2_meta
38
+ }
39
+ }
40
+ end
41
+
42
+ def initialize(opf_version = '3.0',id_pool = Package::IDPool.new)
43
+ @id_pool = id_pool
44
+ @metalist = {}
45
+ @content_nodes = {}
46
+ @meta = {}
47
+ @oldstyle_meta = []
48
+ @opf_version = opf_version
49
+ @namespaces = { 'xmlns:dc' => DC_NS }
50
+ @namespaces['xmlns:opf'] = OPF_NS if @opf_version.to_f < 3.0
51
+ yield self if block_given?
52
+ end
53
+
54
+ def to_xml(builder)
55
+ builder.metadata(@namespaces) {
56
+ @content_nodes.each {
57
+ |name, list|
58
+ list.each {
59
+ |meta|
60
+ meta.to_xml(builder, @id_pool, ns_prefix(DC_NS), nil, @opf_version)
61
+ }
62
+ }
63
+ @oldstyle_meta.each {
64
+ |node|
65
+ node.to_xml(builder, @id_pool, nil)
66
+ }
67
+ }
68
+ @xml
69
+ end
70
+
71
+ def main_title # should make it obsolete?
72
+ @content_nodes['title'][0].content
73
+ end
74
+
75
+ def oldstyle_meta
76
+ @oldstyle_meta.dup
77
+ end
78
+
79
+ def oldstyle_meta_clear
80
+ @oldstyle_meta.each {
81
+ |meta|
82
+ unregister_meta(meta)
83
+ }
84
+ @oldstyle_meta = []
85
+ end
86
+
87
+ CONTENT_NODE_LIST = ['identifier','title', 'language', 'contributor', 'creator', 'coverage', 'date','description','format ','publisher','relation','rights','source','subject','type'].each {
88
+ |node|
89
+ define_method(node + '_list') { @content_nodes[node].dup }
90
+ define_method(node + '_clear') {
91
+ if !@content_nodes[node].nil?
92
+ @content_nodes[node].each { |x| unregister_meta(x) };
93
+ @content_nodes[node] = []
94
+ end
95
+ }
96
+
97
+ #TODO: should override for 'title'. // for 'main title' not always comes first.
98
+ define_method(node) {
99
+ if !@content_nodes[node].nil? && @content_nodes[node].size > 0
100
+ @content_nodes[node][0]
101
+ end
102
+ }
103
+
104
+ define_method('add_' + node) {
105
+ |content, id|
106
+ add_metadata(node, content, id)
107
+ }
108
+
109
+ define_method('set_' + node) {
110
+ |content, id|
111
+ send(node + "_clear")
112
+ add_metadata(node, content, id)
113
+ }
114
+
115
+ define_method(node+'=') {
116
+ |content|
117
+ send(node + "_clear")
118
+ add_metadata(node, content)
119
+ }
120
+ }
121
+
122
+ def add_identifier(string, id, type=nil)
123
+ raise 'id #{id} is already in use' if @id_pool[id]
124
+ identifier = add_metadata('identifier', string, id)
125
+ identifier.refine('identifier-type', type) unless type.nil?
126
+ identifier
127
+ end
128
+
129
+ def identifier_by_id(id)
130
+ @content_nodes['identifier'].each {
131
+ |x|
132
+ return x.content if x['id'] == id
133
+ }
134
+ end
135
+
136
+ def add_metadata(name, content, id = nil)
137
+ meta = Meta.new(name, content, self, { 'id' => id })
138
+ (@content_nodes[name] ||= []) << meta
139
+ yield self if block_given?
140
+ meta
141
+ end
142
+
143
+ def add_title(content, id = nil, title_type = nil)
144
+ meta = add_metadata('title', content, id).refine('title-type', title_type)
145
+ yield meta if block_given?
146
+ meta
147
+ end
148
+
149
+ def add_person(name, content, id = nil, role = 'aut')
150
+ meta = add_metadata(name, content, id).refine('role', role)
151
+ yield meta if block_given?
152
+ meta
153
+ end
154
+
155
+ def add_creator(content, id = nil, role = 'aut')
156
+ meta = add_person('creator', content, id, role)
157
+ yield meta if block_given?
158
+ meta
159
+ end
160
+
161
+ def add_contributor(content, id=nil, role=nil)
162
+ meta = add_person('contributor', content, id, role)
163
+ yield meta if block_given?
164
+ meta
165
+ end
166
+
167
+ def set_lastmodified(date=nil)
168
+ date ||= Time.now
169
+ (@content_nodes['meta'] ||= []).each {
170
+ |meta|
171
+ if (meta['property'] == 'dcterms:modified')
172
+ @content_nodes['meta'].delete meta
173
+ end
174
+ }
175
+ add_metadata('meta', date.utc.strftime('%Y-%m-%dT%H:%M:%SZ'))['property'] = 'dcterms:modified'
176
+ end
177
+
178
+ def add_oldstyle_meta(content, attributes = {})
179
+ meta = Meta.new('meta', content, self, attributes)
180
+ (@oldstyle_meta ||= []) << meta
181
+ meta
182
+ end
183
+
184
+
185
+ def register_meta(meta)
186
+ if !meta['id'].nil?
187
+ raise "id '#{meta['id']}' is already in use." if @id_pool[meta['id']]
188
+ @metalist[meta['id']] = meta
189
+ @id_pool[meta['id']] = true
190
+ end
191
+ end
192
+
193
+ def unregister_meta(meta)
194
+ if meta['id'].nil?
195
+ @metalist[meta['id']] = nil
196
+ @id_pool[meta['id']] = nil
197
+ end
198
+ end
199
+
200
+ private
201
+ def parse_node(ns, node)
202
+ @xml.xpath("#{ns_prefix(ns)}:#{node}", @namespaces).map {
203
+ |node|
204
+ create_meta(node)
205
+ }
206
+ end
207
+
208
+ def create_meta(node)
209
+ Meta.new(node.name, node.content, self, attr_to_hash(node.attributes), collect_refiners(node['id']))
210
+ end
211
+
212
+ def collect_refiners(id)
213
+ r = {}
214
+ if !id.nil?
215
+ @xml.xpath("//#{ns_prefix(OPF_NS)}:meta[@refines='##{id}']", @namespaces).each {
216
+ |node|
217
+ (r[node['property']] ||= []) << create_meta(node)
218
+ }
219
+ end
220
+ r
221
+ end
222
+
223
+ def parse_opf2_meta
224
+ @xml.xpath("#{ns_prefix(OPF_NS)}:meta[not(@refines) and not(@property)]").map {
225
+ |node|
226
+ create_meta(node)
227
+ }
228
+ end
229
+ end
230
+ end