CFPropertyList 2.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # CFFormatError implementation
4
+ #
5
+ # Author:: Christian Kruse (mailto:cjk@wwwtech.de)
6
+ # Copyright:: Copyright (c) 2010
7
+ # License:: MIT License
8
+
9
+ class CFPlistError < Exception
10
+ end
11
+
12
+ # Exception thrown when format errors occur
13
+ class CFFormatError < CFPlistError
14
+ end
15
+
16
+ class CFTypeError < CFPlistError
17
+ end
18
+
19
+ # eof
@@ -0,0 +1,316 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # CFPropertyList implementation
4
+ # class to read, manipulate and write both XML and binary property list
5
+ # files (plist(5)) as defined by Apple
6
+ #
7
+ # == Example
8
+ #
9
+ # # create a arbitrary data structure of basic data types
10
+ # data = {
11
+ # 'name' => 'John Doe',
12
+ # 'missing' => true,
13
+ # 'last_seen' => Time.now,
14
+ # 'friends' => ['Jane Doe','Julian Doe'],
15
+ # 'likes' => {
16
+ # 'me' => false
17
+ # }
18
+ # }
19
+ #
20
+ # # create CFPropertyList::List object
21
+ # plist = CFPropertyList::List.new
22
+ #
23
+ # # call CFPropertyList.guess() to create corresponding CFType values
24
+ # # pass in optional :convert_unknown_to_string => true to convert things like symbols into strings.
25
+ # plist.value = CFPropertyList.guess(data)
26
+ #
27
+ # # write plist to file
28
+ # plist.save("example.plist", CFPropertyList::List::FORMAT_BINARY)
29
+ #
30
+ # # … later, read it again
31
+ # plist = CFPropertyList::List.new({:file => "example.plist"})
32
+ # data = CFPropertyList.native_types(plist.value)
33
+ #
34
+ # Author:: Christian Kruse (mailto:cjk@wwwtech.de)
35
+ # Copyright:: Copyright (c) 2010
36
+ # License:: Distributes under the same terms as Ruby
37
+
38
+ require 'libxml'
39
+ require 'kconv'
40
+ require 'date'
41
+
42
+ module CFPropertyList
43
+ # interface class for PList parsers
44
+ class ParserInterface
45
+ # load a plist
46
+ def load(opts={})
47
+ return ""
48
+ end
49
+
50
+ # convert a plist to string
51
+ def to_str(opts={})
52
+ return true
53
+ end
54
+ end
55
+ end
56
+
57
+ dirname = File.dirname(__FILE__)
58
+ require dirname + '/rbCFPlistError.rb'
59
+ require dirname + '/rbCFTypes.rb'
60
+ require dirname + '/rbXMLCFPropertyList.rb'
61
+ require dirname + '/rbBinaryCFPropertyList.rb'
62
+
63
+ require 'iconv' unless "".respond_to?("encode")
64
+
65
+ module CFPropertyList
66
+ # Create CFType hierarchy by guessing the correct CFType, e.g.
67
+ #
68
+ # x = {
69
+ # 'a' => ['b','c','d']
70
+ # }
71
+ # cftypes = CFPropertyList.guess(x)
72
+ #
73
+ # pass optional options hash. Only possible value actually:
74
+ # +convert_unknown_to_string+:: Convert unknown objects to string calling to_str()
75
+ # +converter_method+:: Convert unknown objects to known objects calling +method_name+
76
+ #
77
+ # cftypes = CFPropertyList.guess(x,:convert_unknown_to_string => true,:converter_method => :to_hash)
78
+ def guess(object, options = {})
79
+ if(object.is_a?(Fixnum) || object.is_a?(Integer)) then
80
+ return CFInteger.new(object)
81
+ elsif(object.is_a?(Float) || (Object.const_defined?('BigDecimal') and object.is_a?(BigDecimal))) then
82
+ return CFReal.new(object)
83
+ elsif(object.is_a?(TrueClass) || object.is_a?(FalseClass)) then
84
+ return CFBoolean.new(object)
85
+ elsif(object.is_a?(String)) then
86
+ return CFString.new(object)
87
+ elsif(object.is_a?(Time) || object.is_a?(DateTime)) then
88
+ return CFDate.new(object)
89
+ elsif(object.is_a?(IO)) then
90
+ return CFData.new(object.read, CFData::DATA_RAW)
91
+ elsif(object.is_a?(Array)) then
92
+ ary = Array.new
93
+ object.each do
94
+ |o|
95
+ ary.push CFPropertyList.guess(o, options)
96
+ end
97
+
98
+ return CFArray.new(ary)
99
+ elsif(object.is_a?(Hash)) then
100
+ hsh = Hash.new
101
+ object.each_pair do
102
+ |k,v|
103
+ k = k.to_s if k.is_a?(Symbol)
104
+ hsh[k] = CFPropertyList.guess(v, options)
105
+ end
106
+
107
+ return CFDictionary.new(hsh)
108
+ elsif options[:converter_method] and object.respond_to?(options[:converter_method]) then
109
+ return CFPropertyList.guess(object.send(options[:converter_method]))
110
+ elsif options[:convert_unknown_to_string] then
111
+ return CFString.new(object.to_s)
112
+ else
113
+ raise CFTypeError.new("Unknown class #{object.class.to_s}! Try using :convert_unknown_to_string if you want to use unknown object types!")
114
+ end
115
+ end
116
+
117
+ # Converts a CFType hiercharchy to native Ruby types
118
+ def native_types(object,keys_as_symbols=false)
119
+ return if object.nil?
120
+
121
+ if(object.is_a?(CFDate) || object.is_a?(CFString) || object.is_a?(CFInteger) || object.is_a?(CFReal) || object.is_a?(CFBoolean)) then
122
+ return object.value
123
+ elsif(object.is_a?(CFData)) then
124
+ return object.decoded_value
125
+ elsif(object.is_a?(CFArray)) then
126
+ ary = []
127
+ object.value.each do
128
+ |v|
129
+ ary.push CFPropertyList.native_types(v)
130
+ end
131
+
132
+ return ary
133
+ elsif(object.is_a?(CFDictionary)) then
134
+ hsh = {}
135
+ object.value.each_pair do
136
+ |k,v|
137
+ k = k.to_sym if keys_as_symbols
138
+ hsh[k] = CFPropertyList.native_types(v)
139
+ end
140
+
141
+ return hsh
142
+ end
143
+ end
144
+
145
+ module_function :guess, :native_types
146
+
147
+ class List
148
+ # Format constant for binary format
149
+ FORMAT_BINARY = 1
150
+
151
+ # Format constant for XML format
152
+ FORMAT_XML = 2
153
+
154
+ # Format constant for automatic format recognizing
155
+ FORMAT_AUTO = 0
156
+
157
+ @@parsers = [Binary,XML]
158
+
159
+ # Path of PropertyList
160
+ attr_accessor :filename
161
+ # Path of PropertyList
162
+ attr_accessor :format
163
+ # the root value in the plist file
164
+ attr_accessor :value
165
+
166
+ def initialize(opts={})
167
+ @filename = opts[:file]
168
+ @format = opts[:format] || FORMAT_AUTO
169
+ @data = opts[:data]
170
+
171
+ load(@filename) unless @filename.nil?
172
+ load_str(@data) unless @data.nil?
173
+ end
174
+
175
+ # Load an XML PropertyList
176
+ # filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+
177
+ def load_xml(filename=nil)
178
+ load(filename,List::FORMAT_XML)
179
+ end
180
+
181
+ # read a binary plist file
182
+ # filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+
183
+ def load_binary(filename=nil)
184
+ load(filename,List::FORMAT_BINARY)
185
+ end
186
+
187
+ # load a plist from a XML string
188
+ # str:: The string containing the plist
189
+ def load_xml_str(str=nil)
190
+ load_str(str,List::FORMAT_XML)
191
+ end
192
+
193
+ # load a plist from a binary string
194
+ # str:: The string containing the plist
195
+ def load_binary_str(str=nil)
196
+ load_str(str,List::FORMAT_BINARY)
197
+ end
198
+
199
+ # load a plist from a string
200
+ # str = nil:: The string containing the plist
201
+ # format = nil:: The format of the plist
202
+ def load_str(str=nil,format=nil)
203
+ str = @data if str.nil?
204
+ format = @format if format.nil?
205
+
206
+ @value = {}
207
+ case format
208
+ when List::FORMAT_BINARY, List::FORMAT_XML then
209
+ prsr = @@parsers[format-1].new
210
+ @value = prsr.load({:data => str})
211
+
212
+ when List::FORMAT_AUTO then # what we now do is ugly, but neccessary to recognize the file format
213
+ filetype = str[0..5]
214
+ version = str[6..7]
215
+
216
+ prsr = nil
217
+ if filetype == "bplist" then
218
+ raise CFFormatError.new("Wong file version #{version}") unless version == "00"
219
+ prsr = Binary.new
220
+ else
221
+ prsr = XML.new
222
+ end
223
+
224
+ @value = prsr.load({:data => str})
225
+ end
226
+ end
227
+
228
+ # Read a plist file
229
+ # file = nil:: The filename of the file to read. If nil, use +filename+ instance variable
230
+ # format = nil:: The format of the plist file. Auto-detect if nil
231
+ def load(file=nil,format=nil)
232
+ file = @filename if file.nil?
233
+ format = @format if format.nil?
234
+ @value = {}
235
+
236
+ raise IOError.new("File #{file} not readable!") unless File.readable? file
237
+
238
+ case format
239
+ when List::FORMAT_BINARY, List::FORMAT_XML then
240
+ prsr = @@parsers[format-1].new
241
+ @value = prsr.load({:file => file})
242
+
243
+ when List::FORMAT_AUTO then # what we now do is ugly, but neccessary to recognize the file format
244
+ magic_number = IO.read(file,8)
245
+ filetype = magic_number[0..5]
246
+ version = magic_number[6..7]
247
+
248
+ prsr = nil
249
+ if filetype == "bplist" then
250
+ raise CFFormatError.new("Wong file version #{version}") unless version == "00"
251
+ prsr = Binary.new
252
+ else
253
+ prsr = XML.new
254
+ end
255
+
256
+ @value = prsr.load({:file => file})
257
+ end
258
+ end
259
+
260
+ # Serialize CFPropertyList object to specified format and write it to file
261
+ # file = nil:: The filename of the file to write to. Uses +filename+ instance variable if nil
262
+ # format = nil:: The format to save in. Uses +format+ instance variable if nil
263
+ def save(file=nil,format=nil,opts={})
264
+ format = @format if format.nil?
265
+ file = @filename if file.nil?
266
+
267
+ raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML") if format != FORMAT_BINARY && format != FORMAT_XML
268
+
269
+ if(!File.exists?(file)) then
270
+ raise IOError.new("File #{file} not writable!") unless File.writable?(File.dirname(file))
271
+ elsif(!File.writable?(file)) then
272
+ raise IOError.new("File #{file} not writable!")
273
+ end
274
+
275
+ opts[:root] = @value
276
+ prsr = @@parsers[format-1].new
277
+ content = prsr.to_str(opts)
278
+
279
+ File.open(file, 'wb') {
280
+ |fd|
281
+ fd.write content
282
+ }
283
+ end
284
+
285
+ # convert plist to string
286
+ # format = List::FORMAT_BINARY:: The format to save the plist
287
+ # opts={}:: Pass parser options
288
+ def to_str(format=List::FORMAT_BINARY,opts={})
289
+ prsr = @@parsers[format-1].new
290
+ opts[:root] = @value
291
+ return prsr.to_str(opts)
292
+ end
293
+ end
294
+ end
295
+
296
+ class Array
297
+ def to_plist(options={})
298
+ options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY
299
+
300
+ plist = CFPropertyList::List.new
301
+ plist.value = CFPropertyList.guess(self, options)
302
+ plist.to_str(options[:plist_format])
303
+ end
304
+ end
305
+
306
+ class Hash
307
+ def to_plist(options={})
308
+ options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY
309
+
310
+ plist = CFPropertyList::List.new
311
+ plist.value = CFPropertyList.guess(self, options)
312
+ plist.to_str(options[:plist_format])
313
+ end
314
+ end
315
+
316
+ # eof
data/lib/rbCFTypes.rb ADDED
@@ -0,0 +1,233 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # CFTypes, e.g. CFString, CFInteger
4
+ # needed to create unambiguous plists
5
+ #
6
+ # Author:: Christian Kruse (mailto:cjk@wwwtech.de)
7
+ # Copyright:: Copyright (c) 2009
8
+ # License:: Distributes under the same terms as Ruby
9
+
10
+ require 'base64'
11
+
12
+ module CFPropertyList
13
+ # This class defines the base class for all CFType classes
14
+ #
15
+ class CFType
16
+ # value of the type
17
+ attr_accessor :value
18
+
19
+
20
+ # set internal value to parameter value by default
21
+ def initialize(value=nil)
22
+ @value = value
23
+ end
24
+
25
+ # convert type to XML
26
+ def to_xml
27
+ end
28
+
29
+ # convert type to binary
30
+ def to_binary(bplist)
31
+ end
32
+ end
33
+
34
+ # This class holds string values, both, UTF-8 and UTF-16BE
35
+ # It will convert the value to UTF-16BE if necessary (i.e. if non-ascii char contained)
36
+ class CFString < CFType
37
+ # convert to XML
38
+ def to_xml
39
+ n = LibXML::XML::Node.new('string')
40
+ n << LibXML::XML::Node.new_text(@value) unless @value.nil?
41
+ return n
42
+ end
43
+
44
+ # convert to binary
45
+ def to_binary(bplist)
46
+ return bplist.string_to_binary(@value);
47
+ end
48
+ end
49
+
50
+ # This class holds integer/fixnum values
51
+ class CFInteger < CFType
52
+ # convert to XML
53
+ def to_xml
54
+ return LibXML::XML::Node.new('integer') << LibXML::XML::Node.new_text(@value.to_s)
55
+ end
56
+
57
+ # convert to binary
58
+ def to_binary(bplist)
59
+ return bplist.num_to_binary(self)
60
+ end
61
+ end
62
+
63
+ # This class holds float values
64
+ class CFReal < CFType
65
+ # convert to XML
66
+ def to_xml
67
+ return LibXML::XML::Node.new('real') << LibXML::XML::Node.new_text(@value.to_s)
68
+ end
69
+
70
+ # convert to binary
71
+ def to_binary(bplist)
72
+ return bplist.num_to_binary(self)
73
+ end
74
+ end
75
+
76
+ # This class holds Time values. While Apple uses seconds since 2001,
77
+ # the rest of the world uses seconds since 1970. So if you access value
78
+ # directly, you get the Time class. If you access via get_value you either
79
+ # geht the timestamp or the Apple timestamp
80
+ class CFDate < CFType
81
+ TIMESTAMP_APPLE = 0
82
+ TIMESTAMP_UNIX = 1;
83
+ DATE_DIFF_APPLE_UNIX = 978307200
84
+
85
+ # create a XML date strimg from a time object
86
+ def CFDate.date_string(val)
87
+ # 2009-05-13T20:23:43Z
88
+ val.getutc.strftime("%Y-%m-%dT%H:%M:%SZ")
89
+ end
90
+
91
+ # parse a XML date string
92
+ def CFDate.parse_date(val)
93
+ # 2009-05-13T20:23:43Z
94
+ val =~ %r{^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$}
95
+ year,month,day,hour,min,sec = $1, $2, $3, $4, $5, $6
96
+ return Time.utc(year,month,day,hour,min,sec).getlocal
97
+ end
98
+
99
+ # set value to defined state
100
+ def initialize(value = nil,format=CFDate::TIMESTAMP_UNIX)
101
+ if(value.is_a?(Time) || value.nil?) then
102
+ @value = value.nil? ? Time.now : value
103
+ else
104
+ set_value(value,format)
105
+ end
106
+ end
107
+
108
+ # set value with timestamp, either Apple or UNIX
109
+ def set_value(value,format=CFDate::TIMESTAMP_UNIX)
110
+ if(format == CFDate::TIMESTAMP_UNIX) then
111
+ @value = Time.at(value)
112
+ else
113
+ @value = Time.at(value + CFDate::DATE_DIFF_APPLE_UNIX)
114
+ end
115
+ end
116
+
117
+ # get timestamp, either UNIX or Apple timestamp
118
+ def get_value(format=CFDate::TIMESTAMP_UNIX)
119
+ if(format == CFDate::TIMESTAMP_UNIX) then
120
+ return @value.to_i
121
+ else
122
+ return @value.to_f - CFDate::DATE_DIFF_APPLE_UNIX
123
+ end
124
+ end
125
+
126
+ # convert to XML
127
+ def to_xml
128
+ return LibXML::XML::Node.new('date') << LibXML::XML::Node.new_text(CFDate::date_string(@value))
129
+ end
130
+
131
+ # convert to binary
132
+ def to_binary(bplist)
133
+ return bplist.date_to_binary(@value)
134
+ end
135
+ end
136
+
137
+ # This class contains a boolean value
138
+ class CFBoolean < CFType
139
+ # convert to XML
140
+ def to_xml
141
+ return LibXML::XML::Node.new(@value ? 'true' : 'false')
142
+ end
143
+
144
+ # convert to binary
145
+ def to_binary(bplist)
146
+ return bplist.bool_to_binary(@value);
147
+ end
148
+ end
149
+
150
+ # This class contains binary data values
151
+ class CFData < CFType
152
+ # Base64 encoded data
153
+ DATA_BASE64 = 0
154
+ # Raw data
155
+ DATA_RAW = 1
156
+
157
+ # set value to defined state, either base64 encoded or raw
158
+ def initialize(value=nil,format=DATA_BASE64)
159
+ if(format == DATA_RAW) then
160
+ @value = Base64.encode64(value)
161
+ else
162
+ @value = value
163
+ end
164
+ end
165
+
166
+ # get base64 decoded value
167
+ def decoded_value
168
+ return Base64.decode64(@value)
169
+ end
170
+
171
+ # convert to XML
172
+ def to_xml
173
+ return LibXML::XML::Node.new('data') << LibXML::XML::Node.new_text(@value)
174
+ end
175
+
176
+ # convert to binary
177
+ def to_binary(bplist)
178
+ return bplist.data_to_binary(decoded_value())
179
+ end
180
+ end
181
+
182
+ # This class contains an array of values
183
+ class CFArray < CFType
184
+ # create a new array CFType
185
+ def initialize(val=[])
186
+ @value = val
187
+ end
188
+
189
+ # convert to XML
190
+ def to_xml
191
+ n = LibXML::XML::Node.new('array')
192
+ @value.each do
193
+ |v|
194
+ n << v.to_xml
195
+ end
196
+
197
+ return n
198
+ end
199
+
200
+ # convert to binary
201
+ def to_binary(bplist)
202
+ return bplist.array_to_binary(self)
203
+ end
204
+ end
205
+
206
+ # this class contains a hash of values
207
+ class CFDictionary < CFType
208
+ # Create new CFDictonary type.
209
+ def initialize(value={})
210
+ @value = value
211
+ end
212
+
213
+ # convert to XML
214
+ def to_xml
215
+ n = LibXML::XML::Node.new('dict')
216
+ @value.each_pair do
217
+ |key,value|
218
+ k = LibXML::XML::Node.new('key') << LibXML::XML::Node.new_text(key)
219
+ n << k
220
+ n << value.to_xml
221
+ end
222
+
223
+ return n
224
+ end
225
+
226
+ # convert to binary
227
+ def to_binary(bplist)
228
+ return bplist.dict_to_binary(self)
229
+ end
230
+ end
231
+ end
232
+
233
+ # eof