CFPropertyList 2.0.14 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,7 +12,7 @@
12
12
  # License:: MIT License
13
13
 
14
14
  # general plist error. All exceptions thrown are derived from this class.
15
- class CFPlistError < Exception
15
+ class CFPlistError < StandardError
16
16
  end
17
17
 
18
18
  # Exception thrown when format errors occur
@@ -1,6 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- require 'libxml'
4
3
  require 'kconv'
5
4
  require 'date'
6
5
  require 'time'
@@ -44,6 +43,10 @@ require 'time'
44
43
  # Copyright:: Copyright (c) 2010
45
44
  # License:: MIT License
46
45
  module CFPropertyList
46
+ class << self
47
+ attr_accessor :xml_parser_interface
48
+ end
49
+
47
50
  # interface class for PList parsers
48
51
  class ParserInterface
49
52
  # load a plist
@@ -56,25 +59,15 @@ module CFPropertyList
56
59
  return true
57
60
  end
58
61
  end
59
- end
60
62
 
61
- class String
62
- unless("".respond_to?(:blob) && "".respond_to?(:blob=)) then
63
- # The blob status of this string (to set to true if a binary string)
64
- attr_accessor :blob
65
- end
63
+ class XMLParserInterface < ParserInterface
64
+ def new_node(name)
65
+ end
66
66
 
67
- unless("".respond_to?(:blob?)) then
68
- # Returns whether or not +str+ is a blob.
69
- # @return [true,false] If true, this string contains binary data. If false, its a regular string
70
- def blob?
71
- @blob
67
+ def new_text(val)
72
68
  end
73
- end
74
69
 
75
- unless("".respond_to?(:bytesize)) then
76
- def bytesize
77
- self.length
70
+ def append_node(parent, child)
78
71
  end
79
72
  end
80
73
  end
@@ -82,10 +75,29 @@ end
82
75
  dirname = File.dirname(__FILE__)
83
76
  require dirname + '/rbCFPlistError.rb'
84
77
  require dirname + '/rbCFTypes.rb'
85
- require dirname + '/rbXMLCFPropertyList.rb'
86
78
  require dirname + '/rbBinaryCFPropertyList.rb'
79
+ require dirname + '/rbPlainCFPropertyList.rb'
80
+
81
+ begin
82
+ require dirname + '/rbLibXMLParser.rb'
83
+ temp = LibXML::XML::Parser::Options::NOBLANKS # check if we have a version with parser options
84
+ temp = false # avoid a warning
85
+ try_nokogiri = false
86
+ CFPropertyList.xml_parser_interface = CFPropertyList::LibXMLParser
87
+ rescue LoadError, NameError
88
+ try_nokogiri = true
89
+ end
90
+
91
+ if try_nokogiri then
92
+ begin
93
+ require dirname + '/rbNokogiriParser.rb'
94
+ CFPropertyList.xml_parser_interface = CFPropertyList::NokogiriXMLParser
95
+ rescue LoadError
96
+ require dirname + '/rbREXMLParser.rb'
97
+ CFPropertyList.xml_parser_interface = CFPropertyList::ReXMLParser
98
+ end
99
+ end
87
100
 
88
- require 'iconv' unless "".respond_to?("encode")
89
101
 
90
102
  module CFPropertyList
91
103
  # Create CFType hierarchy by guessing the correct CFType, e.g.
@@ -99,43 +111,59 @@ module CFPropertyList
99
111
  # +convert_unknown_to_string+:: Convert unknown objects to string calling to_str()
100
112
  # +converter_method+:: Convert unknown objects to known objects calling +method_name+
101
113
  #
102
- # cftypes = CFPropertyList.guess(x,:convert_unknown_to_string => true,:converter_method => :to_hash)
114
+ # cftypes = CFPropertyList.guess(x,:convert_unknown_to_string => true,:converter_method => :to_hash, :converter_with_opts => true)
103
115
  def guess(object, options = {})
104
- if(object.is_a?(Fixnum) || object.is_a?(Integer)) then
105
- return CFInteger.new(object)
106
- elsif(object.is_a?(Float) || (Object.const_defined?('BigDecimal') and object.is_a?(BigDecimal))) then
107
- return CFReal.new(object)
108
- elsif(object.is_a?(TrueClass) || object.is_a?(FalseClass)) then
109
- return CFBoolean.new(object)
110
- elsif(object.is_a?(String)) then
111
- return object.blob? ? CFData.new(object, CFData::DATA_RAW) : CFString.new(object)
112
- elsif(object.respond_to?(:read)) then
113
- return CFData.new(object.read(), CFData::DATA_RAW)
114
- elsif(object.is_a?(Time) || object.is_a?(DateTime) || object.is_a?(Date)) then
115
- return CFDate.new(object)
116
- elsif(object.is_a?(Array)) then
116
+ case object
117
+ when Integer then CFInteger.new(object)
118
+ when UidFixnum then CFUid.new(object)
119
+ when Float then CFReal.new(object)
120
+ when TrueClass, FalseClass then CFBoolean.new(object)
121
+
122
+ when Blob
123
+ CFData.new(object, CFData::DATA_RAW)
124
+
125
+ when String, Symbol
126
+ CFString.new(object.to_s)
127
+
128
+ when Time, DateTime, Date
129
+ CFDate.new(object)
130
+
131
+ when Array, Enumerator
117
132
  ary = Array.new
118
- object.each do
119
- |o|
133
+ object.each do |o|
120
134
  ary.push CFPropertyList.guess(o, options)
121
135
  end
136
+ CFArray.new(ary)
122
137
 
123
- return CFArray.new(ary)
124
- elsif(object.is_a?(Hash)) then
138
+ when Hash
125
139
  hsh = Hash.new
126
- object.each_pair do
127
- |k,v|
140
+ object.each_pair do |k,v|
128
141
  k = k.to_s if k.is_a?(Symbol)
129
142
  hsh[k] = CFPropertyList.guess(v, options)
130
143
  end
131
-
132
- return CFDictionary.new(hsh)
133
- elsif options[:converter_method] and object.respond_to?(options[:converter_method]) then
134
- return CFPropertyList.guess(object.send(options[:converter_method]))
135
- elsif options[:convert_unknown_to_string] then
136
- return CFString.new(object.to_s)
144
+ CFDictionary.new(hsh)
137
145
  else
138
- raise CFTypeError.new("Unknown class #{object.class.to_s}! Try using :convert_unknown_to_string if you want to use unknown object types!")
146
+ case
147
+ when Object.const_defined?('BigDecimal') && object.is_a?(BigDecimal)
148
+ CFReal.new(object)
149
+ when object.respond_to?(:read)
150
+ raw_data = object.read
151
+ # treat the data as a bytestring (ASCII-8BIT) if Ruby supports it. Do this by forcing
152
+ # the encoding, on the assumption that the bytes were read correctly, and just tagged with
153
+ # an inappropriate encoding, rather than transcoding.
154
+ raw_data.force_encoding(Encoding::ASCII_8BIT) if raw_data.respond_to?(:force_encoding)
155
+ CFData.new(raw_data, CFData::DATA_RAW)
156
+ when options[:converter_method] && object.respond_to?(options[:converter_method])
157
+ if options[:converter_with_opts]
158
+ CFPropertyList.guess(object.send(options[:converter_method],options),options)
159
+ else
160
+ CFPropertyList.guess(object.send(options[:converter_method]),options)
161
+ end
162
+ when options[:convert_unknown_to_string]
163
+ CFString.new(object.to_s)
164
+ else
165
+ raise CFTypeError.new("Unknown class #{object.class.to_s}. Try using :convert_unknown_to_string if you want to use unknown object types!")
166
+ end
139
167
  end
140
168
  end
141
169
 
@@ -143,10 +171,10 @@ module CFPropertyList
143
171
  def native_types(object,keys_as_symbols=false)
144
172
  return if object.nil?
145
173
 
146
- if(object.is_a?(CFDate) || object.is_a?(CFString) || object.is_a?(CFInteger) || object.is_a?(CFReal) || object.is_a?(CFBoolean)) then
174
+ if(object.is_a?(CFDate) || object.is_a?(CFString) || object.is_a?(CFInteger) || object.is_a?(CFReal) || object.is_a?(CFBoolean)) || object.is_a?(CFUid) then
147
175
  return object.value
148
176
  elsif(object.is_a?(CFData)) then
149
- return object.decoded_value
177
+ return CFPropertyList::Blob.new(object.decoded_value)
150
178
  elsif(object.is_a?(CFArray)) then
151
179
  ary = []
152
180
  object.value.each do
@@ -177,17 +205,22 @@ module CFPropertyList
177
205
  # Format constant for XML format
178
206
  FORMAT_XML = 2
179
207
 
208
+ # Format constant for the old plain format
209
+ FORMAT_PLAIN = 3
210
+
180
211
  # Format constant for automatic format recognizing
181
212
  FORMAT_AUTO = 0
182
213
 
183
- @@parsers = [Binary,XML]
214
+ @@parsers = [Binary, CFPropertyList.xml_parser_interface, PlainParser]
184
215
 
185
216
  # Path of PropertyList
186
217
  attr_accessor :filename
187
- # Path of PropertyList
218
+ # the original format of the PropertyList
188
219
  attr_accessor :format
189
220
  # the root value in the plist file
190
221
  attr_accessor :value
222
+ # default value for XML generation; if true generate formatted XML
223
+ attr_accessor :formatted
191
224
 
192
225
  # initialize a new CFPropertyList, arguments are:
193
226
  #
@@ -200,11 +233,22 @@ module CFPropertyList
200
233
  @filename = opts[:file]
201
234
  @format = opts[:format] || FORMAT_AUTO
202
235
  @data = opts[:data]
236
+ @formatted = opts[:formatted]
203
237
 
204
238
  load(@filename) unless @filename.nil?
205
239
  load_str(@data) unless @data.nil?
206
240
  end
207
241
 
242
+ # returns a list of registered parsers
243
+ def self.parsers
244
+ @@parsers
245
+ end
246
+
247
+ # set a list of parsers
248
+ def self.parsers=(val)
249
+ @@parsers = val
250
+ end
251
+
208
252
  # Load an XML PropertyList
209
253
  # filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+
210
254
  def load_xml(filename=nil)
@@ -217,6 +261,12 @@ module CFPropertyList
217
261
  load(filename,List::FORMAT_BINARY)
218
262
  end
219
263
 
264
+ # read a plain plist file
265
+ # filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+
266
+ def load_plain(filename=nil)
267
+ load(filename,List::FORMAT_PLAIN)
268
+ end
269
+
220
270
  # load a plist from a XML string
221
271
  # str:: The string containing the plist
222
272
  def load_xml_str(str=nil)
@@ -229,6 +279,12 @@ module CFPropertyList
229
279
  load_str(str,List::FORMAT_BINARY)
230
280
  end
231
281
 
282
+ # load a plist from a plain string
283
+ # str:: The string containing the plist
284
+ def load_plain_str(str=nil)
285
+ load_str(str,List::FORMAT_PLAIN)
286
+ end
287
+
232
288
  # load a plist from a string
233
289
  # str = nil:: The string containing the plist
234
290
  # format = nil:: The format of the plist
@@ -238,7 +294,7 @@ module CFPropertyList
238
294
 
239
295
  @value = {}
240
296
  case format
241
- when List::FORMAT_BINARY, List::FORMAT_XML then
297
+ when List::FORMAT_BINARY, List::FORMAT_XML, List::FORMAT_PLAIN then
242
298
  prsr = @@parsers[format-1].new
243
299
  @value = prsr.load({:data => str})
244
300
 
@@ -247,11 +303,19 @@ module CFPropertyList
247
303
  version = str[6..7]
248
304
 
249
305
  prsr = nil
306
+
250
307
  if filetype == "bplist" then
251
- raise CFFormatError.new("Wong file version #{version}") unless version == "00"
308
+ raise CFFormatError.new("Wrong file version #{version}") unless version == "00"
252
309
  prsr = Binary.new
310
+ @format = List::FORMAT_BINARY
253
311
  else
254
- prsr = XML.new
312
+ if str =~ /^<(\?xml|!DOCTYPE|plist)/
313
+ prsr = CFPropertyList.xml_parser_interface.new
314
+ @format = List::FORMAT_XML
315
+ else
316
+ prsr = PlainParser.new
317
+ @format = List::FORMAT_PLAIN
318
+ end
255
319
  end
256
320
 
257
321
  @value = prsr.load({:data => str})
@@ -269,25 +333,35 @@ module CFPropertyList
269
333
  raise IOError.new("File #{file} not readable!") unless File.readable? file
270
334
 
271
335
  case format
272
- when List::FORMAT_BINARY, List::FORMAT_XML then
336
+ when List::FORMAT_BINARY, List::FORMAT_XML, List::FORMAT_PLAIN then
273
337
  prsr = @@parsers[format-1].new
274
338
  @value = prsr.load({:file => file})
275
339
 
276
340
  when List::FORMAT_AUTO then # what we now do is ugly, but neccessary to recognize the file format
277
- magic_number = IO.read(file,8)
341
+ magic_number = IO.read(file,12)
342
+ raise IOError.new("File #{file} is empty.") unless magic_number
278
343
  filetype = magic_number[0..5]
279
344
  version = magic_number[6..7]
280
345
 
281
346
  prsr = nil
282
347
  if filetype == "bplist" then
283
- raise CFFormatError.new("Wong file version #{version}") unless version == "00"
348
+ raise CFFormatError.new("Wrong file version #{version}") unless version == "00"
284
349
  prsr = Binary.new
350
+ @format = List::FORMAT_BINARY
285
351
  else
286
- prsr = XML.new
352
+ if magic_number =~ /^<(\?xml|!DOCTYPE|plist)/
353
+ prsr = CFPropertyList.xml_parser_interface.new
354
+ @format = List::FORMAT_XML
355
+ else
356
+ prsr = PlainParser.new
357
+ @format = List::FORMAT_PLAIN
358
+ end
287
359
  end
288
360
 
289
361
  @value = prsr.load({:file => file})
290
362
  end
363
+
364
+ raise CFFormatError.new("Invalid format or parser error!") if @value.nil?
291
365
  end
292
366
 
293
367
  # Serialize CFPropertyList object to specified format and write it to file
@@ -297,7 +371,9 @@ module CFPropertyList
297
371
  format = @format if format.nil?
298
372
  file = @filename if file.nil?
299
373
 
300
- raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML") if format != FORMAT_BINARY && format != FORMAT_XML
374
+ if format != FORMAT_BINARY && format != FORMAT_XML && format != FORMAT_PLAIN
375
+ raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML")
376
+ end
301
377
 
302
378
  if(!File.exists?(file)) then
303
379
  raise IOError.new("File #{file} not writable!") unless File.writable?(File.dirname(file))
@@ -306,7 +382,10 @@ module CFPropertyList
306
382
  end
307
383
 
308
384
  opts[:root] = @value
385
+ opts[:formatted] = @formatted unless opts.has_key?(:formatted)
386
+
309
387
  prsr = @@parsers[format-1].new
388
+
310
389
  content = prsr.to_str(opts)
311
390
 
312
391
  File.open(file, 'wb') {
@@ -319,13 +398,21 @@ module CFPropertyList
319
398
  # format = List::FORMAT_BINARY:: The format to save the plist
320
399
  # opts={}:: Pass parser options
321
400
  def to_str(format=List::FORMAT_BINARY,opts={})
401
+ if format != FORMAT_BINARY && format != FORMAT_XML && format != FORMAT_PLAIN
402
+ raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML")
403
+ end
404
+
322
405
  prsr = @@parsers[format-1].new
406
+
323
407
  opts[:root] = @value
408
+ opts[:formatted] = @formatted unless opts.has_key?(:formatted)
409
+
324
410
  return prsr.to_str(opts)
325
411
  end
326
412
  end
327
413
  end
328
414
 
415
+
329
416
  class Array
330
417
  # convert an array to plist format
331
418
  def to_plist(options={})
@@ -333,7 +420,18 @@ class Array
333
420
 
334
421
  plist = CFPropertyList::List.new
335
422
  plist.value = CFPropertyList.guess(self, options)
336
- plist.to_str(options[:plist_format])
423
+ plist.to_str(options[:plist_format], options)
424
+ end
425
+ end
426
+
427
+ class Enumerator
428
+ # convert an array to plist format
429
+ def to_plist(options={})
430
+ options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY
431
+
432
+ plist = CFPropertyList::List.new
433
+ plist.value = CFPropertyList.guess(self, options)
434
+ plist.to_str(options[:plist_format], options)
337
435
  end
338
436
  end
339
437
 
@@ -344,7 +442,7 @@ class Hash
344
442
 
345
443
  plist = CFPropertyList::List.new
346
444
  plist.value = CFPropertyList.guess(self, options)
347
- plist.to_str(options[:plist_format])
445
+ plist.to_str(options[:plist_format], options)
348
446
  end
349
447
  end
350
448