CFPropertyList 2.0.14 → 3.0.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.
@@ -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