CFPropertyList 2.0.7

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,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