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 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}/sekr1t", "rdoc").upload
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
- === Example Property List
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
- result = Plist::parse_xml("path/to/example.plist")
49
+ == Generation
39
50
 
40
- result.class
41
- => Hash
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
- "#{result["FirstName"]} #{result["LastName"]}"
44
- => "John Public"
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
- result["ZipPostal"]
47
- => "12345"
48
-
49
- === Generation
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
- coming.soon {|i| i.promise }
104
+ If for whatever reason you can't add this method, your object will be serialized with <tt>Marshal.dump</tt> instead.
@@ -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 = '2.1.2'
21
+ VERSION = '3.0.0'
19
22
  end
@@ -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
- # === Save a plist
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(self.to_plist)
54
+ f.write(obj.to_plist)
47
55
  end
48
56
  end
49
57
 
50
- # Only the expected classes can be emitted as a plist:
51
- # String, Float, DateTime, Integer, TrueClass, FalseClass, Array, Hash
52
- #
53
- # Write me if you think another class can be coerced safely into one of the
54
- # expected plist classes (plist@hexane.org)
55
- def to_plist( header = true )
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
- self.to_plist_node
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
- class String
66
- include Plist::Emit
67
- def to_plist_node
68
- "<string>#{CGI::escapeHTML(self)}</string>"
69
- end
70
- end
119
+ def self.comment(content)
120
+ return "<!-- #{content} -->\n"
121
+ end
71
122
 
72
- class Symbol
73
- include Plist::Emit
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
- class Float
80
- include Plist::Emit
81
- def to_plist_node
82
- "<real>#{self}</real>"
83
- end
84
- end
126
+ if block_given?
127
+ out = IndentedString.new
128
+ out << "<#{type}>"
129
+ out.raise_indent
85
130
 
86
- class Time
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
- class Date
94
- include Plist::Emit
95
- def to_plist_node
96
- "<date>#{self.strftime('%Y-%m-%dT%H:%M:%SZ')}</date>"
97
- end
98
- end
133
+ out.lower_indent
134
+ out << "</#{type}>"
135
+ else
136
+ out = "<#{type}>#{contents.to_s}</#{type}>\n"
137
+ end
99
138
 
100
- class Integer
101
- include Plist::Emit
102
- def to_plist_node
103
- "<integer>#{self}</integer>"
104
- end
105
- end
139
+ return out.to_s
140
+ end
106
141
 
107
- class FalseClass
108
- include Plist::Emit
109
- def to_plist_node
110
- "<false/>"
111
- end
112
- end
142
+ def self.wrap(contents)
143
+ output = ''
113
144
 
114
- class TrueClass
115
- include Plist::Emit
116
- def to_plist_node
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
- class Array
122
- include Plist::Emit
123
- def to_plist_node
124
- fragment = "<array>\n"
125
- self.each do |e|
126
- element_plist = e.to_plist_node
127
- element_plist.each do |l|
128
- fragment += "\t#{l.chomp}\n"
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
- fragment += "</array>"
132
- fragment
133
- end
134
- end
165
+ private
166
+ class IndentedString #:nodoc:
167
+ attr_accessor :indent_string
135
168
 
136
- class Hash
137
- include Plist::Emit
138
- def to_plist_node
139
- fragment = "<dict>\n"
140
- self.keys.sort.each do |k|
141
- fragment += "\t<key>#{CGI::escapeHTML(k)}</key>\n"
142
- element_plist = self[k].to_plist_node
143
- element_plist.each do |l|
144
- fragment += "\t#{l.chomp}\n"
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
- require 'stringio'
153
- [ IO, StringIO ].each do |io_class|
154
- io_class.module_eval do
155
- include Plist::Emit
156
- def to_plist_node
157
- self.rewind
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
- output = "<data>\n"
161
- Base64::encode64(data).gsub(/\s+/, '').scan(/.{1,68}/o) { output << $& << "\n" }
162
- output << "</data>"
218
+ class Array #:nodoc:
219
+ include Plist::Emit
220
+ end
163
221
 
164
- output
165
- end
166
- end
222
+ class Hash #:nodoc:
223
+ include Plist::Emit
167
224
  end
@@ -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
- @scanner = StringScanner.new( if (File.exists? @filename_or_xml)
90
- File.open(@filename_or_xml, "r") {|f| f.read}
91
- else
92
- @filename_or_xml
93
- end )
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(XMLDECL_PATTERN)
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
- # replacing Tempfile with StringIO
216
- # think it might be a bit nicer
217
- #require 'tempfile'
218
- #tf = Tempfile.new("plist.tmp")
219
- #tf.write Base64.decode64(text.gsub(/\s+/,''))
220
- #tf.close
221
- # is this a good idea?
222
- #tf.open
223
- #tf
224
- io = StringIO.new
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>
@@ -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>
@@ -10,4 +10,4 @@
10
10
  <string>2</string>
11
11
  </dict>
12
12
  </dict>
13
- </plist>
13
+ </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 TestBasicTypes < Test::Unit::TestCase
11
+ class TestGeneratorBasicTypes < Test::Unit::TestCase
12
12
  def wrap(tag, content)
13
- return "<array>\n\t<#{tag}>#{content}</#{tag}>\n</array>"
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, ['testdata'].to_plist(false)
20
- assert_equal expected, [:testdata].to_plist(false)
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', "&lt;Fish &amp; Chips&gt;")
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), [i].to_plist(false)
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), [i].to_plist(false)
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 "<array>\n\t<true/>\n</array>", [true].to_plist(false)
37
- assert_equal "<array>\n\t<false/>\n</array>", [false].to_plist(false)
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')), [test_time].to_plist(false)
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')), [test_date].to_plist(false)
50
- assert_equal wrap('date', test_datetime.strftime('%Y-%m-%dT%H:%M:%SZ')), [test_datetime].to_plist(false)
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>&lt;plist&gt;</string>"), "<plist>".to_plist )
83
- assert_equal( Plist::_xml("<string>Fish &amp; 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
@@ -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 TestPlist < Test::Unit::TestCase
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(Plist::_xml('<string>Fish &amp; Chips</string>'))
79
+ data = Plist::parse_xml('<string>Fish &amp; 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: 2.1.2
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