gepub 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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