plist 2.1.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +2 -2
- data/docs/USAGE +66 -13
- data/lib/plist.rb +4 -1
- data/lib/plist/generator.rb +189 -132
- data/lib/plist/parser.rb +28 -33
- data/test/assets/commented.plist +9 -0
- data/test/assets/example_data.bin +0 -0
- data/test/assets/test_data_elements.plist +24 -0
- data/test/assets/test_empty_key.plist +1 -1
- data/test/test_data_elements.rb +115 -0
- data/test/test_generator.rb +59 -0
- data/test/test_generator_basic_types.rb +17 -45
- data/test/test_generator_collections.rb +82 -0
- data/test/test_parser.rb +8 -20
- metadata +10 -1
data/Rakefile
CHANGED
@@ -61,7 +61,7 @@ end
|
|
61
61
|
|
62
62
|
desc "Strip trailing whitespace and fix newlines for all release files"
|
63
63
|
task :fix_whitespace => [ :clean ] do
|
64
|
-
RELEASE_FILES.each do |filename|
|
64
|
+
RELEASE_FILES.reject {|i| i =~ /assets/}.each do |filename|
|
65
65
|
next if File.directory? filename
|
66
66
|
|
67
67
|
File.open(filename) do |file|
|
@@ -100,7 +100,7 @@ end
|
|
100
100
|
|
101
101
|
desc "Copy documentation to rubyforge"
|
102
102
|
task :update_rdoc => [ :rdoc ] do
|
103
|
-
Rake::SshDirPublisher.new("#{RUBYFORGE_USER}@rubyforge.org", "/var/www/gforge-projects/#{RUBYFORGE_PROJECT}
|
103
|
+
Rake::SshDirPublisher.new("#{RUBYFORGE_USER}@rubyforge.org", "/var/www/gforge-projects/#{RUBYFORGE_PROJECT}", "rdoc").upload
|
104
104
|
end
|
105
105
|
|
106
106
|
# Genereate the RDoc documentation
|
data/docs/USAGE
CHANGED
@@ -1,4 +1,17 @@
|
|
1
|
-
|
1
|
+
== Parsing
|
2
|
+
|
3
|
+
result = Plist::parse_xml('path/to/example.plist')
|
4
|
+
|
5
|
+
result.class
|
6
|
+
=> Hash
|
7
|
+
|
8
|
+
"#{result['FirstName']} #{result['LastName']}"
|
9
|
+
=> "John Public"
|
10
|
+
|
11
|
+
result['ZipPostal']
|
12
|
+
=> "12345"
|
13
|
+
|
14
|
+
==== Example Property List
|
2
15
|
|
3
16
|
<?xml version="1.0" encoding="UTF-8"?>
|
4
17
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
@@ -32,20 +45,60 @@
|
|
32
45
|
<string>12345</string>
|
33
46
|
</dict>
|
34
47
|
</plist>
|
35
|
-
|
36
|
-
=== Parsing
|
37
48
|
|
38
|
-
|
49
|
+
== Generation
|
39
50
|
|
40
|
-
|
41
|
-
|
51
|
+
plist also provides the ability to generate plists from Ruby objects. The following Ruby classes are converted into native plist types:
|
52
|
+
Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false
|
42
53
|
|
43
|
-
|
44
|
-
|
54
|
+
* +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the <array> and <dict> containers (respectively).
|
55
|
+
* +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a <data> element.
|
56
|
+
* User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to <tt>Marshal.dump</tt> and the result placed in a <data> element. See below for more details.
|
45
57
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
58
|
+
==== Creating a plist
|
59
|
+
|
60
|
+
There are two ways to generate complete plists. Given an object:
|
61
|
+
|
62
|
+
obj = [1, :two, {'c' => 0xd}]
|
63
|
+
|
64
|
+
If you've mixed in <tt>Plist::Emit</tt> (which is already done for +Array+ and +Hash+), you can simply call +to_plist+:
|
65
|
+
|
66
|
+
obj.to_plist
|
67
|
+
|
68
|
+
This is equivalent to calling <tt>Plist::Emit.dump(obj)</tt>. Either one will yield:
|
69
|
+
|
70
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
71
|
+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
72
|
+
<plist version="1.0">
|
73
|
+
<array>
|
74
|
+
<integer>1</integer>
|
75
|
+
<string>two</string>
|
76
|
+
<dict>
|
77
|
+
<key>c</key>
|
78
|
+
<integer>13</integer>
|
79
|
+
</dict>
|
80
|
+
</array>
|
81
|
+
</plist>
|
82
|
+
|
83
|
+
You can also dump plist fragments by passing +false+ as the second parameter:
|
84
|
+
|
85
|
+
Plist::Emit.dump('holy cow!', false)
|
86
|
+
=> "<string>holy cow!</string>"
|
87
|
+
|
88
|
+
==== Custom serialization
|
89
|
+
|
90
|
+
If your class can be safely coerced into a native plist datatype, you can implement +to_plist_node+. Upon encountering an object of a class it doesn't recognize, the plist library will check to see if it responds to +to_plist_node+, and if so, insert the result of that call into the plist output.
|
91
|
+
|
92
|
+
An example:
|
93
|
+
|
94
|
+
class MyFancyString
|
95
|
+
...
|
96
|
+
|
97
|
+
def to_plist_node
|
98
|
+
return "<string>#{self.defancify}</string>"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
When you attempt to serialize a +MyFancyString+ object, the +to_plist_node+ method will be called and the object's contents will be defancified and placed in the plist.
|
50
103
|
|
51
|
-
|
104
|
+
If for whatever reason you can't add this method, your object will be serialized with <tt>Marshal.dump</tt> instead.
|
data/lib/plist.rb
CHANGED
@@ -10,10 +10,13 @@
|
|
10
10
|
#
|
11
11
|
# This is the main file for plist. Everything interesting happens in Plist and Plist::Emit.
|
12
12
|
|
13
|
+
require 'base64'
|
13
14
|
require 'cgi'
|
15
|
+
require 'stringio'
|
16
|
+
|
14
17
|
require 'plist/generator'
|
15
18
|
require 'plist/parser'
|
16
19
|
|
17
20
|
module Plist
|
18
|
-
VERSION = '
|
21
|
+
VERSION = '3.0.0'
|
19
22
|
end
|
data/lib/plist/generator.rb
CHANGED
@@ -1,167 +1,224 @@
|
|
1
|
-
|
1
|
+
#--###########################################################
|
2
2
|
# Copyright 2006, Ben Bleything <ben@bleything.net> and #
|
3
3
|
# Patrick May <patrick@hexane.org> #
|
4
4
|
# #
|
5
5
|
# Distributed under the MIT license. #
|
6
6
|
##############################################################
|
7
|
-
|
8
|
-
#
|
9
|
-
# You can turn the variables back into a plist string:
|
10
|
-
#
|
11
|
-
# r.to_plist
|
12
|
-
#
|
13
|
-
# There is a convenience method for saving a variable to a file:
|
14
|
-
#
|
15
|
-
# r.save_plist(filename)
|
16
|
-
#
|
17
|
-
# Only these ruby types can be converted into a plist:
|
18
|
-
#
|
19
|
-
# String
|
20
|
-
# Float
|
21
|
-
# DateTime
|
22
|
-
# Integer
|
23
|
-
# FalseClass
|
24
|
-
# TrueClass
|
25
|
-
# Array
|
26
|
-
# Hash
|
27
|
-
#
|
28
|
-
# Notes:
|
29
|
-
#
|
30
|
-
# + Array and Hash are recursive -- the elements of an Array and the values of a Hash
|
31
|
-
# must convert to a plist.
|
32
|
-
# + The keys of the Hash must be strings.
|
33
|
-
# + The contents of data elements are returned as a Tempfile.
|
34
|
-
# + Data elements can be set with to an open IO or a StringIO
|
35
|
-
#
|
36
|
-
# If you have suggestions for mapping other Ruby types to the plist types, send a note to:
|
37
|
-
#
|
38
|
-
# mailto:plist@hexane.org
|
39
|
-
#
|
40
|
-
# I'll take a look and probably add it, I'm just reticent to create too many
|
41
|
-
# "convenience" methods without at least agreeing with someone :-)
|
7
|
+
#++
|
8
|
+
# See Plist::Emit.
|
42
9
|
module Plist
|
10
|
+
# === Create a plist
|
11
|
+
# You can dump an object to a plist in one of two ways:
|
12
|
+
#
|
13
|
+
# * <tt>Plist::Emit.dump(obj)</tt>
|
14
|
+
# * <tt>obj.to_plist</tt>
|
15
|
+
# * This requires that you mixin the <tt>Plist::Emit</tt> module, which is already done for +Array+ and +Hash+.
|
16
|
+
#
|
17
|
+
# The following Ruby classes are converted into native plist types:
|
18
|
+
# Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false
|
19
|
+
# * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the <array> and <dict> containers (respectively).
|
20
|
+
# * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a <data> element.
|
21
|
+
# * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to <tt>Marshal.dump</tt> and the result placed in a <data> element.
|
22
|
+
#
|
23
|
+
# For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below.
|
43
24
|
module Emit
|
25
|
+
# Helper method for injecting into classes. Calls <tt>Plist::Emit.dump</tt> with +self+.
|
26
|
+
def to_plist(envelope = true)
|
27
|
+
return Plist::Emit.dump(self, envelope)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Helper method for injecting into classes. Calls <tt>Plist::Emit.save_plist</tt> with +self+.
|
44
31
|
def save_plist(filename)
|
32
|
+
Plist::Emit.save_plist(self, filename)
|
33
|
+
end
|
34
|
+
|
35
|
+
# The following Ruby classes are converted into native plist types:
|
36
|
+
# Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time
|
37
|
+
#
|
38
|
+
# Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes.
|
39
|
+
#
|
40
|
+
# +IO+ and +StringIO+ objects are encoded and placed in <data> elements; other objects are <tt>Marshal.dump</tt>'ed unless they implement +to_plist_node+.
|
41
|
+
#
|
42
|
+
# The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment.
|
43
|
+
def self.dump(obj, envelope = true)
|
44
|
+
output = plist_node(obj)
|
45
|
+
|
46
|
+
output = wrap(output) if envelope
|
47
|
+
|
48
|
+
return output
|
49
|
+
end
|
50
|
+
|
51
|
+
# Writes the serialized object's plist to the specified filename.
|
52
|
+
def self.save_plist(obj, filename)
|
45
53
|
File.open(filename, 'wb') do |f|
|
46
|
-
f.write(
|
54
|
+
f.write(obj.to_plist)
|
47
55
|
end
|
48
56
|
end
|
49
57
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if (header)
|
57
|
-
Plist::_xml(self.to_plist_node)
|
58
|
+
private
|
59
|
+
def self.plist_node(element)
|
60
|
+
output = ''
|
61
|
+
|
62
|
+
if element.respond_to? :to_plist_node
|
63
|
+
output << element.to_plist_node
|
58
64
|
else
|
59
|
-
|
65
|
+
case element
|
66
|
+
when Array
|
67
|
+
if element.empty?
|
68
|
+
output << "<array/>\n"
|
69
|
+
else
|
70
|
+
output << tag('array') {
|
71
|
+
element.collect {|e| plist_node(e)}
|
72
|
+
}
|
73
|
+
end
|
74
|
+
when Hash
|
75
|
+
if element.empty?
|
76
|
+
output << "<dict/>\n"
|
77
|
+
else
|
78
|
+
inner_tags = []
|
79
|
+
|
80
|
+
element.keys.sort.each do |k|
|
81
|
+
v = element[k]
|
82
|
+
inner_tags << tag('key', CGI::escapeHTML(k.to_s))
|
83
|
+
inner_tags << plist_node(v)
|
84
|
+
end
|
85
|
+
|
86
|
+
output << tag('dict') {
|
87
|
+
inner_tags
|
88
|
+
}
|
89
|
+
end
|
90
|
+
when true, false
|
91
|
+
output << "<#{element}/>\n"
|
92
|
+
when Time
|
93
|
+
output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ'))
|
94
|
+
when Date # also catches DateTime
|
95
|
+
output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ'))
|
96
|
+
when String, Symbol, Fixnum, Bignum, Integer, Float
|
97
|
+
output << tag(element_type(element), CGI::escapeHTML(element.to_s))
|
98
|
+
when IO, StringIO
|
99
|
+
element.rewind
|
100
|
+
contents = element.read
|
101
|
+
# note that apple plists are wrapped at a different length then
|
102
|
+
# what ruby's base64 wraps by default.
|
103
|
+
# I used #encode64 instead of #b64encode (which allows a length arg)
|
104
|
+
# because b64encode is b0rked and ignores the length arg.
|
105
|
+
data = "\n"
|
106
|
+
Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" }
|
107
|
+
output << tag('data', data)
|
108
|
+
else
|
109
|
+
output << comment( 'The <data> element below contains a Ruby object which has been serialized with Marshal.dump.' )
|
110
|
+
data = "\n"
|
111
|
+
Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" }
|
112
|
+
output << tag('data', data )
|
113
|
+
end
|
60
114
|
end
|
115
|
+
|
116
|
+
return output
|
61
117
|
end
|
62
|
-
end
|
63
|
-
end
|
64
118
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
"<string>#{CGI::escapeHTML(self)}</string>"
|
69
|
-
end
|
70
|
-
end
|
119
|
+
def self.comment(content)
|
120
|
+
return "<!-- #{content} -->\n"
|
121
|
+
end
|
71
122
|
|
72
|
-
|
73
|
-
|
74
|
-
def to_plist_node
|
75
|
-
"<string>#{CGI::escapeHTML(self.to_s)}</string>"
|
76
|
-
end
|
77
|
-
end
|
123
|
+
def self.tag(type, contents = '', &block)
|
124
|
+
out = nil
|
78
125
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
126
|
+
if block_given?
|
127
|
+
out = IndentedString.new
|
128
|
+
out << "<#{type}>"
|
129
|
+
out.raise_indent
|
85
130
|
|
86
|
-
|
87
|
-
include Plist::Emit
|
88
|
-
def to_plist_node
|
89
|
-
"<date>#{self.utc.strftime('%Y-%m-%dT%H:%M:%SZ')}</date>"
|
90
|
-
end
|
91
|
-
end
|
131
|
+
out << block.call
|
92
132
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
133
|
+
out.lower_indent
|
134
|
+
out << "</#{type}>"
|
135
|
+
else
|
136
|
+
out = "<#{type}>#{contents.to_s}</#{type}>\n"
|
137
|
+
end
|
99
138
|
|
100
|
-
|
101
|
-
|
102
|
-
def to_plist_node
|
103
|
-
"<integer>#{self}</integer>"
|
104
|
-
end
|
105
|
-
end
|
139
|
+
return out.to_s
|
140
|
+
end
|
106
141
|
|
107
|
-
|
108
|
-
|
109
|
-
def to_plist_node
|
110
|
-
"<false/>"
|
111
|
-
end
|
112
|
-
end
|
142
|
+
def self.wrap(contents)
|
143
|
+
output = ''
|
113
144
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
"<true/>"
|
118
|
-
end
|
119
|
-
end
|
145
|
+
output << '<?xml version="1.0" encoding="UTF-8"?>' + "\n"
|
146
|
+
output << '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' + "\n"
|
147
|
+
output << '<plist version="1.0">' + "\n"
|
120
148
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
149
|
+
output << contents
|
150
|
+
|
151
|
+
output << '</plist>' + "\n"
|
152
|
+
|
153
|
+
return output
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.element_type(item)
|
157
|
+
return case item
|
158
|
+
when String, Symbol: 'string'
|
159
|
+
when Fixnum, Bignum, Integer: 'integer'
|
160
|
+
when Float: 'real'
|
161
|
+
else
|
162
|
+
raise "Don't know about this data type... something must be wrong!"
|
129
163
|
end
|
130
164
|
end
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
end
|
165
|
+
private
|
166
|
+
class IndentedString #:nodoc:
|
167
|
+
attr_accessor :indent_string
|
135
168
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
169
|
+
@@indent_level = 0
|
170
|
+
|
171
|
+
def initialize(str = "\t")
|
172
|
+
@indent_string = str
|
173
|
+
@contents = ''
|
174
|
+
end
|
175
|
+
|
176
|
+
def to_s
|
177
|
+
return @contents
|
178
|
+
end
|
179
|
+
|
180
|
+
def raise_indent
|
181
|
+
@@indent_level += 1
|
182
|
+
end
|
183
|
+
|
184
|
+
def lower_indent
|
185
|
+
@@indent_level -= 1 if @@indent_level > 0
|
186
|
+
end
|
187
|
+
|
188
|
+
def <<(val)
|
189
|
+
if val.is_a? Array
|
190
|
+
val.each do |f|
|
191
|
+
self << f
|
192
|
+
end
|
193
|
+
else
|
194
|
+
# if it's already indented, don't bother indenting further
|
195
|
+
unless val =~ /\A#{@indent_string}/
|
196
|
+
indent = @indent_string * @@indent_level
|
197
|
+
|
198
|
+
@contents << val.gsub(/^/, indent)
|
199
|
+
else
|
200
|
+
@contents << val
|
201
|
+
end
|
202
|
+
|
203
|
+
# it already has a newline, don't add another
|
204
|
+
@contents << "\n" unless val =~ /\n$/
|
205
|
+
end
|
145
206
|
end
|
146
207
|
end
|
147
|
-
fragment += "</dict>"
|
148
|
-
fragment
|
149
208
|
end
|
150
209
|
end
|
151
210
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
data = self.read
|
211
|
+
# we need to add this so sorting hash keys works properly
|
212
|
+
class Symbol #:nodoc:
|
213
|
+
def <=> (other)
|
214
|
+
self.to_s <=> other.to_s
|
215
|
+
end
|
216
|
+
end
|
159
217
|
|
160
|
-
|
161
|
-
|
162
|
-
|
218
|
+
class Array #:nodoc:
|
219
|
+
include Plist::Emit
|
220
|
+
end
|
163
221
|
|
164
|
-
|
165
|
-
|
166
|
-
end
|
222
|
+
class Hash #:nodoc:
|
223
|
+
include Plist::Emit
|
167
224
|
end
|
data/lib/plist/parser.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
|
1
|
+
#--###########################################################
|
2
2
|
# Copyright 2006, Ben Bleything <ben@bleything.net> and #
|
3
3
|
# Patrick May <patrick@hexane.org> #
|
4
4
|
# #
|
5
5
|
# Distributed under the MIT license. #
|
6
6
|
##############################################################
|
7
|
-
|
7
|
+
#++
|
8
8
|
# Plist parses Mac OS X xml property list files into ruby data structures.
|
9
9
|
#
|
10
10
|
# === Load a plist file
|
@@ -12,17 +12,6 @@
|
|
12
12
|
#
|
13
13
|
# r = Plist::parse_xml( filename_or_xml )
|
14
14
|
module Plist
|
15
|
-
TEMPLATE = <<-XML
|
16
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
17
|
-
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
18
|
-
<plist version="1.0">
|
19
|
-
%plist%
|
20
|
-
</plist>
|
21
|
-
XML
|
22
|
-
def Plist::_xml( xml )
|
23
|
-
TEMPLATE.sub( /%plist%/, xml )
|
24
|
-
end
|
25
|
-
|
26
15
|
# Note that I don't use these two elements much:
|
27
16
|
#
|
28
17
|
# + Date elements are returned as DateTime objects.
|
@@ -78,6 +67,8 @@ XML
|
|
78
67
|
TEXT = /([^<]+)/
|
79
68
|
XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um
|
80
69
|
DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um
|
70
|
+
COMMENT_START = /\A<!--/u
|
71
|
+
COMMENT_END = /.*?-->/um
|
81
72
|
|
82
73
|
|
83
74
|
def parse
|
@@ -86,13 +77,20 @@ XML
|
|
86
77
|
end_tag = /<\/(#{plist_tags})[^>]*>/i
|
87
78
|
|
88
79
|
require 'strscan'
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
80
|
+
|
81
|
+
contents = (
|
82
|
+
if (File.exists? @filename_or_xml)
|
83
|
+
File.open(@filename_or_xml) {|f| f.read}
|
84
|
+
else
|
85
|
+
@filename_or_xml
|
86
|
+
end
|
87
|
+
)
|
88
|
+
|
89
|
+
@scanner = StringScanner.new( contents )
|
94
90
|
until @scanner.eos?
|
95
|
-
if @scanner.scan(
|
91
|
+
if @scanner.scan(COMMENT_START)
|
92
|
+
@scanner.scan(COMMENT_END)
|
93
|
+
elsif @scanner.scan(XMLDECL_PATTERN)
|
96
94
|
elsif @scanner.scan(DOCTYPE_PATTERN)
|
97
95
|
elsif @scanner.scan(start_tag)
|
98
96
|
@listener.tag_start(@scanner[1], nil)
|
@@ -136,7 +134,7 @@ XML
|
|
136
134
|
|
137
135
|
class PList < PTag
|
138
136
|
def to_ruby
|
139
|
-
children.first.to_ruby
|
137
|
+
children.first.to_ruby if children.first
|
140
138
|
end
|
141
139
|
end
|
142
140
|
|
@@ -212,19 +210,16 @@ XML
|
|
212
210
|
require 'base64'
|
213
211
|
class PData < PTag
|
214
212
|
def to_ruby
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
io.write Base64.decode64(text.gsub(/\s+/,''))
|
226
|
-
io.rewind
|
227
|
-
io
|
213
|
+
data = Base64.decode64(text.gsub(/\s+/, ''))
|
214
|
+
|
215
|
+
begin
|
216
|
+
return Marshal.load(data)
|
217
|
+
rescue Exception => e
|
218
|
+
io = StringIO.new
|
219
|
+
io.write data
|
220
|
+
io.rewind
|
221
|
+
return io
|
222
|
+
end
|
228
223
|
end
|
229
224
|
end
|
230
225
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<!-- I am a comment! -->
|
5
|
+
<!--
|
6
|
+
I am a multi-line comment!
|
7
|
+
hooray!
|
8
|
+
-->
|
9
|
+
</plist>
|
Binary file
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>stringio</key>
|
6
|
+
<data>dGhpcyBpcyBhIHN0cmluZ2lvIG9iamVjdA==
|
7
|
+
</data>
|
8
|
+
<key>file</key>
|
9
|
+
<data>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
10
|
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
11
|
+
AAAAAAAAAAAAAA==
|
12
|
+
</data>
|
13
|
+
<key>io</key>
|
14
|
+
<data>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
15
|
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
16
|
+
AAAAAAAAAAAAAA==
|
17
|
+
</data>
|
18
|
+
<key>marshal</key>
|
19
|
+
<!-- The <data> element below contains a Ruby object which has been serialized with Marshal.dump. -->
|
20
|
+
<data>BAhvOhZNYXJzaGFsYWJsZU9iamVjdAY6CUBmb28iHnRoaXMgb2JqZWN0IHdh
|
21
|
+
cyBtYXJzaGFsZWQ=
|
22
|
+
</data>
|
23
|
+
</dict>
|
24
|
+
</plist>
|
@@ -0,0 +1,115 @@
|
|
1
|
+
##############################################################
|
2
|
+
# Copyright 2006, Ben Bleything <ben@bleything.net> and #
|
3
|
+
# Patrick May <patrick@hexane.org> #
|
4
|
+
# #
|
5
|
+
# Distributed under the MIT license. #
|
6
|
+
##############################################################
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
require 'plist'
|
10
|
+
require 'stringio'
|
11
|
+
|
12
|
+
class MarshalableObject
|
13
|
+
attr_accessor :foo
|
14
|
+
|
15
|
+
def initialize(str)
|
16
|
+
@foo = str
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class TestDataElements < Test::Unit::TestCase
|
21
|
+
@@result = Plist::parse_xml('test/assets/test_data_elements.plist')
|
22
|
+
|
23
|
+
def test_marshal
|
24
|
+
expected = <<END
|
25
|
+
<!-- The <data> element below contains a Ruby object which has been serialized with Marshal.dump. -->
|
26
|
+
<data>
|
27
|
+
BAhvOhZNYXJzaGFsYWJsZU9iamVjdAY6CUBmb28iHnRoaXMgb2JqZWN0IHdhcyBtYXJz
|
28
|
+
aGFsZWQ=
|
29
|
+
</data>
|
30
|
+
END
|
31
|
+
|
32
|
+
mo = MarshalableObject.new('this object was marshaled')
|
33
|
+
|
34
|
+
assert_equal expected.chomp, Plist::Emit.dump(mo, false).chomp
|
35
|
+
|
36
|
+
assert_instance_of MarshalableObject, @@result['marshal']
|
37
|
+
|
38
|
+
assert_equal mo.foo, @@result['marshal'].foo
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_generator_io_and_file
|
42
|
+
expected = <<END
|
43
|
+
<data>
|
44
|
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
45
|
+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
46
|
+
</data>
|
47
|
+
END
|
48
|
+
|
49
|
+
expected.chomp!
|
50
|
+
|
51
|
+
fd = IO.sysopen('test/assets/example_data.bin')
|
52
|
+
io = IO.open(fd, 'r')
|
53
|
+
|
54
|
+
# File is a subclass of IO, so catching IO in the dispatcher should work for File as well...
|
55
|
+
f = File.open('test/assets/example_data.bin')
|
56
|
+
|
57
|
+
assert_equal expected, Plist::Emit.dump(io, false).chomp
|
58
|
+
assert_equal expected, Plist::Emit.dump(f, false).chomp
|
59
|
+
|
60
|
+
assert_instance_of StringIO, @@result['io']
|
61
|
+
assert_instance_of StringIO, @@result['file']
|
62
|
+
|
63
|
+
io.rewind
|
64
|
+
f.rewind
|
65
|
+
|
66
|
+
assert_equal io.read, @@result['io'].read
|
67
|
+
assert_equal f.read, @@result['file'].read
|
68
|
+
|
69
|
+
io.close
|
70
|
+
f.close
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_generator_string_io
|
74
|
+
expected = <<END
|
75
|
+
<data>
|
76
|
+
dGhpcyBpcyBhIHN0cmluZ2lvIG9iamVjdA==
|
77
|
+
</data>
|
78
|
+
END
|
79
|
+
|
80
|
+
sio = StringIO.new('this is a stringio object')
|
81
|
+
|
82
|
+
assert_equal expected.chomp, Plist::Emit.dump(sio, false).chomp
|
83
|
+
|
84
|
+
assert_instance_of StringIO, @@result['stringio']
|
85
|
+
|
86
|
+
sio.rewind
|
87
|
+
assert_equal sio.read, @@result['stringio'].read
|
88
|
+
end
|
89
|
+
|
90
|
+
# this functionality is credited to Mat Schaffer,
|
91
|
+
# who discovered the plist with the data tag
|
92
|
+
# supplied the test data, and provided the parsing code.
|
93
|
+
def test_data
|
94
|
+
# test reading plist <data> elements
|
95
|
+
data = Plist::parse_xml("test/assets/example_data.plist");
|
96
|
+
assert_equal( File.open("test/assets/example_data.jpg"){|f| f.read }, data['image'].read )
|
97
|
+
|
98
|
+
# test writing data elements
|
99
|
+
expected = File.read("test/assets/example_data.plist")
|
100
|
+
result = data.to_plist
|
101
|
+
#File.open('result.plist', 'w') {|f|f.write(result)} # debug
|
102
|
+
assert_equal( expected, result )
|
103
|
+
|
104
|
+
# Test changing the <data> object in the plist to a StringIO and writing.
|
105
|
+
# This appears extraneous given that plist currently returns a StringIO,
|
106
|
+
# so the above writing test also flexes StringIO#to_plist_node.
|
107
|
+
# However, the interface promise is to return an IO, not a particular class.
|
108
|
+
# plist used to return Tempfiles, which was changed solely for performance reasons.
|
109
|
+
data['image'] = StringIO.new( File.read("test/assets/example_data.jpg"))
|
110
|
+
|
111
|
+
assert_equal(expected, data.to_plist )
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
##############################################################
|
2
|
+
# Copyright 2006, Ben Bleything <ben@bleything.net> and #
|
3
|
+
# Patrick May <patrick@hexane.org> #
|
4
|
+
# #
|
5
|
+
# Distributed under the MIT license. #
|
6
|
+
##############################################################
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
require 'plist'
|
10
|
+
|
11
|
+
class SerializableObject
|
12
|
+
attr_accessor :foo
|
13
|
+
|
14
|
+
def initialize(str)
|
15
|
+
@foo = str
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_plist_node
|
19
|
+
return "<string>#{CGI::escapeHTML @foo}</string>"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class TestGenerator < Test::Unit::TestCase
|
24
|
+
def test_to_plist_vs_plist_emit_dump_no_envelope
|
25
|
+
source = [1, :b, true]
|
26
|
+
|
27
|
+
to_plist = source.to_plist(false)
|
28
|
+
plist_emit_dump = Plist::Emit.dump(source, false)
|
29
|
+
|
30
|
+
assert_equal to_plist, plist_emit_dump
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_to_plist_vs_plist_emit_dump_with_envelope
|
34
|
+
source = [1, :b, true]
|
35
|
+
|
36
|
+
to_plist = source.to_plist
|
37
|
+
plist_emit_dump = Plist::Emit.dump(source)
|
38
|
+
|
39
|
+
assert_equal to_plist, plist_emit_dump
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_dumping_serializable_object
|
43
|
+
str = 'this object implements #to_plist_node'
|
44
|
+
so = SerializableObject.new(str)
|
45
|
+
|
46
|
+
assert_equal "<string>#{str}</string>", Plist::Emit.dump(so, false)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_write_plist
|
50
|
+
data = [1, :two, {:c => 'dee'}]
|
51
|
+
|
52
|
+
data.save_plist('test.plist')
|
53
|
+
file = File.open('test.plist') {|f| f.read}
|
54
|
+
|
55
|
+
assert_equal file, data.to_plist
|
56
|
+
|
57
|
+
File.unlink('test.plist')
|
58
|
+
end
|
59
|
+
end
|
@@ -8,79 +8,51 @@
|
|
8
8
|
require 'test/unit'
|
9
9
|
require 'plist'
|
10
10
|
|
11
|
-
class
|
11
|
+
class TestGeneratorBasicTypes < Test::Unit::TestCase
|
12
12
|
def wrap(tag, content)
|
13
|
-
return "
|
13
|
+
return "<#{tag}>#{content}</#{tag}>"
|
14
14
|
end
|
15
15
|
|
16
16
|
def test_strings
|
17
17
|
expected = wrap('string', 'testdata')
|
18
18
|
|
19
|
-
assert_equal expected,
|
20
|
-
assert_equal expected,
|
19
|
+
assert_equal expected, Plist::Emit.dump('testdata', false).chomp
|
20
|
+
assert_equal expected, Plist::Emit.dump(:testdata, false).chomp
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_strings_with_escaping
|
24
|
+
expected = wrap('string', "<Fish & Chips>")
|
25
|
+
|
26
|
+
assert_equal expected, Plist::Emit.dump('<Fish & Chips>', false).chomp
|
21
27
|
end
|
22
28
|
|
23
29
|
def test_integers
|
24
30
|
[42, 2376239847623987623, -8192].each do |i|
|
25
|
-
assert_equal wrap('integer', i),
|
31
|
+
assert_equal wrap('integer', i), Plist::Emit.dump(i, false).chomp
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
29
35
|
def test_floats
|
30
36
|
[3.14159, -38.3897, 2398476293847.9823749872349980].each do |i|
|
31
|
-
assert_equal wrap('real', i),
|
37
|
+
assert_equal wrap('real', i), Plist::Emit.dump(i, false).chomp
|
32
38
|
end
|
33
39
|
end
|
34
40
|
|
35
41
|
def test_booleans
|
36
|
-
assert_equal "<
|
37
|
-
assert_equal "<
|
42
|
+
assert_equal "<true/>", Plist::Emit.dump(true, false).chomp
|
43
|
+
assert_equal "<false/>", Plist::Emit.dump(false, false).chomp
|
38
44
|
end
|
39
45
|
|
40
46
|
def test_time
|
41
47
|
test_time = Time.now
|
42
|
-
assert_equal wrap('date', test_time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')),
|
48
|
+
assert_equal wrap('date', test_time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')), Plist::Emit.dump(test_time, false).chomp
|
43
49
|
end
|
44
50
|
|
45
51
|
def test_dates
|
46
52
|
test_date = Date.today
|
47
53
|
test_datetime = DateTime.now
|
48
54
|
|
49
|
-
assert_equal wrap('date', test_date.strftime('%Y-%m-%dT%H:%M:%SZ')),
|
50
|
-
assert_equal wrap('date', test_datetime.strftime('%Y-%m-%dT%H:%M:%SZ')),
|
51
|
-
end
|
52
|
-
|
53
|
-
# generater tests from patrick's plist.rb code
|
54
|
-
def test_to_plist
|
55
|
-
assert_equal( Plist::_xml("<string>Hello, World</string>"), "Hello, World".to_plist )
|
56
|
-
assert_equal( Plist::_xml("<real>151936595.697543</real>"), 151936595.697543.to_plist )
|
57
|
-
assert_equal( Plist::_xml("<date>2006-04-21T16:47:58Z</date>"), DateTime.parse("2006-04-21T16:47:58Z").to_plist )
|
58
|
-
assert_equal( Plist::_xml("<integer>999000</integer>"), 999000.to_plist )
|
59
|
-
assert_equal( Plist::_xml("<false/>"), false.to_plist )
|
60
|
-
assert_equal( Plist::_xml("<true/>"), true.to_plist )
|
61
|
-
|
62
|
-
assert_equal( Plist::_xml("<array>\n\t<true/>\n\t<false/>\n</array>"),
|
63
|
-
[ true, false ].to_plist )
|
64
|
-
|
65
|
-
assert_equal( Plist::_xml("<dict>\n\t<key>False</key>\n\t<false/>\n\t<key>True</key>\n\t<true/>\n</dict>"),
|
66
|
-
{ 'True' => true, 'False' => false }.to_plist )
|
67
|
-
|
68
|
-
source = File.open("test/assets/AlbumData.xml") { |f| f.read }
|
69
|
-
|
70
|
-
result = Plist::parse_xml(source)
|
71
|
-
|
72
|
-
assert_equal( result, Plist::parse_xml(result.to_plist) )
|
73
|
-
|
74
|
-
File.delete('hello.plist') if File.exists?('hello.plist')
|
75
|
-
"Hello, World".save_plist('hello.plist')
|
76
|
-
assert_equal( Plist::_xml("<string>Hello, World</string>"),
|
77
|
-
File.open('hello.plist') {|f| f.read } )
|
78
|
-
File.delete('hello.plist') if File.exists?('hello.plist')
|
55
|
+
assert_equal wrap('date', test_date.strftime('%Y-%m-%dT%H:%M:%SZ')), Plist::Emit.dump(test_date, false).chomp
|
56
|
+
assert_equal wrap('date', test_datetime.strftime('%Y-%m-%dT%H:%M:%SZ')), Plist::Emit.dump(test_datetime, false).chomp
|
79
57
|
end
|
80
|
-
|
81
|
-
def test_escape_string_values
|
82
|
-
assert_equal( Plist::_xml("<string><plist></string>"), "<plist>".to_plist )
|
83
|
-
assert_equal( Plist::_xml("<string>Fish & Chips</string>"), "Fish & Chips".to_plist )
|
84
|
-
end
|
85
|
-
|
86
58
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
##############################################################
|
2
|
+
# Copyright 2006, Ben Bleything <ben@bleything.net> and #
|
3
|
+
# Patrick May <patrick@hexane.org> #
|
4
|
+
# #
|
5
|
+
# Distributed under the MIT license. #
|
6
|
+
##############################################################
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
require 'plist'
|
10
|
+
|
11
|
+
class TestGeneratorCollections < Test::Unit::TestCase
|
12
|
+
def test_array
|
13
|
+
expected = <<END
|
14
|
+
<array>
|
15
|
+
<integer>1</integer>
|
16
|
+
<integer>2</integer>
|
17
|
+
<integer>3</integer>
|
18
|
+
</array>
|
19
|
+
END
|
20
|
+
|
21
|
+
assert_equal expected, [1,2,3].to_plist(false)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_empty_array
|
25
|
+
expected = <<END
|
26
|
+
<array/>
|
27
|
+
END
|
28
|
+
|
29
|
+
assert_equal expected, [].to_plist(false)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_hash
|
33
|
+
expected = <<END
|
34
|
+
<dict>
|
35
|
+
<key>abc</key>
|
36
|
+
<integer>123</integer>
|
37
|
+
<key>foo</key>
|
38
|
+
<string>bar</string>
|
39
|
+
</dict>
|
40
|
+
END
|
41
|
+
# thanks to recent changes in the generator code, hash keys are sorted before emission,
|
42
|
+
# so multi-element hash tests should be reliable. We're testing that here too.
|
43
|
+
assert_equal expected, {:foo => :bar, :abc => 123}.to_plist(false)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_empty_hash
|
47
|
+
expected = <<END
|
48
|
+
<dict/>
|
49
|
+
END
|
50
|
+
|
51
|
+
assert_equal expected, {}.to_plist(false)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_hash_with_array_element
|
55
|
+
expected = <<END
|
56
|
+
<dict>
|
57
|
+
<key>ary</key>
|
58
|
+
<array>
|
59
|
+
<integer>1</integer>
|
60
|
+
<string>b</string>
|
61
|
+
<string>3</string>
|
62
|
+
</array>
|
63
|
+
</dict>
|
64
|
+
END
|
65
|
+
assert_equal expected, {:ary => [1,:b,'3']}.to_plist(false)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_array_with_hash_element
|
69
|
+
expected = <<END
|
70
|
+
<array>
|
71
|
+
<dict>
|
72
|
+
<key>foo</key>
|
73
|
+
<string>bar</string>
|
74
|
+
</dict>
|
75
|
+
<string>b</string>
|
76
|
+
<integer>3</integer>
|
77
|
+
</array>
|
78
|
+
END
|
79
|
+
|
80
|
+
assert_equal expected, [{:foo => 'bar'}, :b, 3].to_plist(false)
|
81
|
+
end
|
82
|
+
end
|
data/test/test_parser.rb
CHANGED
@@ -6,11 +6,10 @@
|
|
6
6
|
##############################################################
|
7
7
|
|
8
8
|
require 'test/unit'
|
9
|
-
require 'pp'
|
10
9
|
|
11
10
|
require 'plist'
|
12
11
|
|
13
|
-
class
|
12
|
+
class TestParser < Test::Unit::TestCase
|
14
13
|
def test_Plist_parse_xml
|
15
14
|
result = Plist::parse_xml("test/assets/AlbumData.xml")
|
16
15
|
|
@@ -59,7 +58,6 @@ class TestPlist < Test::Unit::TestCase
|
|
59
58
|
# plist = Plist::parse_xml( "~/Pictures/iPhoto Library/AlbumData.xml" )
|
60
59
|
#end
|
61
60
|
|
62
|
-
|
63
61
|
# date fields are credited to
|
64
62
|
def test_date_fields
|
65
63
|
result = Plist::parse_xml("test/assets/Cookies.plist")
|
@@ -67,22 +65,6 @@ class TestPlist < Test::Unit::TestCase
|
|
67
65
|
assert_equal( "2007-10-25T12:36:35Z", result.first['Expires'].to_s )
|
68
66
|
end
|
69
67
|
|
70
|
-
# this functionality is credited to Mat Schaffer,
|
71
|
-
# who discovered the plist with the data tag
|
72
|
-
# supplied the test data, and provided the parsing code.
|
73
|
-
def test_data
|
74
|
-
data = Plist::parse_xml("test/assets/example_data.plist");
|
75
|
-
assert_equal( File.open("test/assets/example_data.jpg"){|f| f.read }, data['image'].read )
|
76
|
-
assert_equal( File.open("test/assets/example_data.plist"){|f| f.read }, data.to_plist )
|
77
|
-
|
78
|
-
data['image'] = StringIO.new( File.open("test/assets/example_data.jpg"){ |f| f.read } )
|
79
|
-
File.open('temp.plist', 'w'){|f| f.write data.to_plist }
|
80
|
-
assert_equal( File.open("test/assets/example_data.plist"){|f| f.read }, data.to_plist )
|
81
|
-
|
82
|
-
File.delete('temp.plist') if File.exists?('temp.plist')
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
68
|
# bug fix for empty <key>
|
87
69
|
# reported by Matthias Peick <matthias@peick.de>
|
88
70
|
# reported and fixed by Frederik Seiffert <ego@frederikseiffert.de>
|
@@ -94,9 +76,15 @@ class TestPlist < Test::Unit::TestCase
|
|
94
76
|
# bug fix for decoding entities
|
95
77
|
# reported by Matthias Peick <matthias@peick.de>
|
96
78
|
def test_decode_entities
|
97
|
-
data = Plist::parse_xml(
|
79
|
+
data = Plist::parse_xml('<string>Fish & Chips</string>')
|
98
80
|
assert_equal('Fish & Chips', data)
|
99
81
|
end
|
82
|
+
|
83
|
+
def test_comment_handling_and_empty_plist
|
84
|
+
assert_nothing_raised do
|
85
|
+
assert_nil( Plist::parse_xml( File.read('test/assets/commented.plist') ) )
|
86
|
+
end
|
87
|
+
end
|
100
88
|
end
|
101
89
|
|
102
90
|
__END__
|
metadata
CHANGED
@@ -3,7 +3,7 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: plist
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version:
|
6
|
+
version: 3.0.0
|
7
7
|
date: 2006-09-20 00:00:00 -07:00
|
8
8
|
summary: All-purpose Property List manipulation library.
|
9
9
|
require_paths:
|
@@ -37,15 +37,24 @@ files:
|
|
37
37
|
- lib/plist.rb
|
38
38
|
- lib/plist/generator.rb
|
39
39
|
- lib/plist/parser.rb
|
40
|
+
- test/test_data_elements.rb
|
41
|
+
- test/test_generator.rb
|
40
42
|
- test/test_generator_basic_types.rb
|
43
|
+
- test/test_generator_collections.rb
|
41
44
|
- test/test_parser.rb
|
42
45
|
- test/assets/AlbumData.xml
|
46
|
+
- test/assets/commented.plist
|
43
47
|
- test/assets/Cookies.plist
|
48
|
+
- test/assets/example_data.bin
|
44
49
|
- test/assets/example_data.jpg
|
45
50
|
- test/assets/example_data.plist
|
51
|
+
- test/assets/test_data_elements.plist
|
46
52
|
- test/assets/test_empty_key.plist
|
47
53
|
test_files:
|
54
|
+
- test/test_data_elements.rb
|
55
|
+
- test/test_generator.rb
|
48
56
|
- test/test_generator_basic_types.rb
|
57
|
+
- test/test_generator_collections.rb
|
49
58
|
- test/test_parser.rb
|
50
59
|
rdoc_options: []
|
51
60
|
|