plist 1.1.1 → 2.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.
Files changed (3) hide show
  1. data/plist.rb +156 -12
  2. data/test_plist.rb +26 -0
  3. metadata +2 -2
data/plist.rb CHANGED
@@ -3,23 +3,74 @@
3
3
 
4
4
  # Plist parses Mac OS X xml property list files into ruby data structures.
5
5
  #
6
+ # === Load a plist file
7
+ # This is the main point of the library:
8
+ #
9
+ # r = Plist::parse_xml( filename_or_xml )
10
+ #
11
+ # === Save a plist
12
+ # You can turn the variables back into a plist string:
13
+ #
14
+ # r.to_plist
15
+ #
16
+ # There is a convenience method for saving a variable to a file:
17
+ #
18
+ # r.save_plist(filename)
19
+ #
20
+ # Only these ruby types can be converted into a plist:
21
+ #
22
+ # String
23
+ # Float
24
+ # DateTime
25
+ # Integer
26
+ # FalseClass
27
+ # TrueClass
28
+ # Array
29
+ # Hash
30
+ #
31
+ # Note that Array and Hash are recursive -- the elements of an Array and the values of a Hash
32
+ # must convert to a plist. Also note that the keys of the Hash must be strings.
33
+ #
34
+ # If you have suggestions for mapping other Ruby types to the plist types, send a note to:
35
+ #
36
+ # mailto:plist@hexane.org
37
+ #
38
+ # I'll take a look and probably add it, I'm just reticent to create too many
39
+ # "convenience" methods without at least agreeing with someone :-)
40
+ #
41
+ # === Credits
42
+ # plist.rb has been implemented by Patrick May. A few other folks have been helpful in developing plist.rb:
43
+ #
44
+ # + Martin Dittus, who pointed out that Time wasn't enough for plist Dates,
45
+ # especially those in "~/Library/Cookies/Cookies.plist"
46
+ #
47
+ # + Chuck Remes, who pushed me towards implementing #to_plist
48
+ class Plist
49
+
50
+ TEMPLATE = <<-XML
51
+ <?xml version="1.0" encoding="UTF-8"?>
52
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
53
+ <plist version="1.0">
54
+ %plist%
55
+ </plist>
56
+ XML
57
+ def Plist::_xml( xml )
58
+ TEMPLATE.sub( /%plist%/, xml )
59
+ end
60
+
6
61
  # Note that I don't use these two elements much:
7
62
  #
8
- # + Date elements are returned as Time objects.
63
+ # + Date elements are returned as DateTime objects.
9
64
  # + Data elements are not yet implemented.
10
65
  #
11
66
  # Plist::parse_xml will blow up if it encounters a data element.
12
67
  # If you encounter such an error, or if you have a Date element which
13
68
  # can't be parsed into a Time object, please send your plist file to
14
- # patrick@hexane.org so that I can implement the proper support.
15
- #
16
- # The main point of this api is one method: Plist::parse_xml( filename )
17
- class Plist
18
-
19
- def Plist::parse_xml( filename )
69
+ # plist@hexane.org so that I can implement the proper support.
70
+ def Plist::parse_xml( filename_or_xml )
20
71
  listener = Listener.new
21
72
  #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener)
22
- parser = StreamParser.new(filename, listener)
73
+ parser = StreamParser.new(filename_or_xml, listener)
23
74
  parser.parse
24
75
  listener.result
25
76
  end
@@ -54,8 +105,8 @@ class Plist
54
105
  end
55
106
 
56
107
  class StreamParser
57
- def initialize( filename, listener )
58
- @filename = filename
108
+ def initialize( filename_or_xml, listener )
109
+ @filename_or_xml = filename_or_xml
59
110
  @listener = listener
60
111
  end
61
112
 
@@ -70,7 +121,11 @@ class Plist
70
121
  end_tag = /<\/(#{plist_tags})[^>]*>/i
71
122
 
72
123
  require 'strscan'
73
- @scanner = StringScanner.new( File.open(@filename, "r") {|f| f.read} )
124
+ @scanner = StringScanner.new( if (File.exists? @filename_or_xml)
125
+ File.open(@filename_or_xml, "r") {|f| f.read}
126
+ else
127
+ @filename_or_xml
128
+ end )
74
129
  until @scanner.eos?
75
130
  if @scanner.scan(XMLDECL_PATTERN)
76
131
  elsif @scanner.scan(DOCTYPE_PATTERN)
@@ -146,7 +201,7 @@ class Plist
146
201
 
147
202
  class PString < PTag
148
203
  def to_ruby
149
- text
204
+ text || ''
150
205
  end
151
206
  end
152
207
 
@@ -189,4 +244,93 @@ class Plist
189
244
  end
190
245
  end
191
246
 
247
+ module Emit
248
+ def save_plist(filename)
249
+ File.open(filename, 'wb') do |f|
250
+ f.write(self.to_plist)
251
+ end
252
+ end
253
+
254
+ # Only the expected classes can be emitted as a plist:
255
+ # String, Float, DateTime, Integer, TrueClass, FalseClass, Array, Hash
256
+ #
257
+ # Write me if you think another class can be coerced safely into one of the
258
+ # expected plist classes (plist@hexane.org)
259
+ def to_plist
260
+ Plist::_xml(self.to_plist_fragment)
261
+ end
262
+ end
263
+ end
264
+
265
+ class String
266
+ include Plist::Emit
267
+ def to_plist_fragment
268
+ "<string>#{self}</string>"
269
+ end
270
+ end
271
+
272
+ class Float
273
+ include Plist::Emit
274
+ def to_plist_fragment
275
+ "<real>#{self}</real>"
276
+ end
277
+ end
278
+
279
+ class DateTime
280
+ include Plist::Emit
281
+ def to_plist_fragment
282
+ "<date>#{self}</date>"
283
+ end
284
+ end
285
+
286
+ class Integer
287
+ include Plist::Emit
288
+ def to_plist_fragment
289
+ "<integer>#{self}</integer>"
290
+ end
291
+ end
292
+
293
+ class FalseClass
294
+ include Plist::Emit
295
+ def to_plist_fragment
296
+ "<false/>"
297
+ end
298
+ end
299
+
300
+ class TrueClass
301
+ include Plist::Emit
302
+ def to_plist_fragment
303
+ "<true/>"
304
+ end
305
+ end
306
+
307
+ class Array
308
+ include Plist::Emit
309
+ def to_plist_fragment
310
+ fragment = "<array>\n"
311
+ self.each do |e|
312
+ element_plist = e.to_plist_fragment
313
+ element_plist.each do |l|
314
+ fragment += " #{l}\n"
315
+ end
316
+ end
317
+ fragment += "</array>"
318
+ fragment
319
+ end
320
+ end
321
+
322
+ class Hash
323
+ include Plist::Emit
324
+ def to_plist_fragment
325
+ fragment = "<dict>\n"
326
+ self.keys.sort.each do |k|
327
+ fragment += " <key>#{k}</key>\n"
328
+ element_plist = self[k].to_plist_fragment
329
+ element_plist.each do |l|
330
+ fragment += " #{l}\n"
331
+ end
332
+ end
333
+ fragment += "</dict>"
334
+ fragment
335
+ end
192
336
  end
@@ -57,6 +57,32 @@ class TestPlist < Test::Unit::TestCase
57
57
  assert_equal( "2007-10-25T12:36:35Z", result.first['Expires'].to_s )
58
58
  end
59
59
 
60
+ def test_to_plist
61
+ assert_equal( Plist::_xml("<string>Hello, World</string>"), "Hello, World".to_plist )
62
+ assert_equal( Plist::_xml("<real>151936595.697543</real>"), 151936595.697543.to_plist )
63
+ assert_equal( Plist::_xml("<date>2006-04-21T16:47:58Z</date>"), DateTime.parse("2006-04-21T16:47:58Z").to_plist )
64
+ assert_equal( Plist::_xml("<integer>999000</integer>"), 999000.to_plist )
65
+ assert_equal( Plist::_xml("<false/>"), false.to_plist )
66
+ assert_equal( Plist::_xml("<true/>"), true.to_plist )
67
+
68
+ assert_equal( Plist::_xml("<array>\n <true/>\n <false/>\n</array>"),
69
+ [ true, false ].to_plist )
70
+
71
+ assert_equal( Plist::_xml("<dict>\n <key>False</key>\n <false/>\n <key>True</key>\n <true/>\n</dict>"),
72
+ { 'True' => true, 'False' => false }.to_plist )
73
+
74
+ source = File.open("AlbumData.xml") { |f| f.read }
75
+
76
+ result = Plist::parse_xml(source)
77
+
78
+ assert_equal( result, Plist::parse_xml(result.to_plist) )
79
+
80
+ File.delete('hello.plist') if File.exists?('hello.plist')
81
+ "Hello, World".save_plist('hello.plist')
82
+ assert_equal( Plist::_xml("<string>Hello, World</string>"),
83
+ File.open('hello.plist') {|f| f.read } )
84
+ end
85
+
60
86
  end
61
87
 
62
88
  __END__
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11.3
3
3
  specification_version: 1
4
4
  name: plist
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.1.1
7
- date: 2006-05-03 00:00:00 -04:00
6
+ version: 2.0.0
7
+ date: 2006-07-04 00:00:00 -04:00
8
8
  summary: plist parses Mac OS X plist files into ruby data types.
9
9
  require_paths:
10
10
  - "."