plist 3.1.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2a76784a6b7e953d1cd9b6deaf390eb7556fd0df83e8970db7ac4640c71493b4
4
+ data.tar.gz: 8324c2b332839aa772eb77a4a760b24e43e24c363c99d01c2b2b4b11e311938a
5
+ SHA512:
6
+ metadata.gz: c33686b606d723821333314b4a18fe6ac39d65a3712aa1cd43896dbbd4bfb95130aa0a1f62b4a3b260c2a633d44681eec0766fef5651ae70580340a905e4f6e4
7
+ data.tar.gz: 5b63b55469235ec79f14af6f25c05cbc8e6088ce9532e9dfc5aa0887e533a7ee67424953d62e0b3dfe046c7745d9a2add1e0b8ddf07c34f46c5a551c92a9a158
File without changes
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env ruby
2
- #
1
+ # encoding: utf-8
2
+
3
3
  # = plist
4
4
  #
5
5
  # This is the main file for plist. Everything interesting happens in
@@ -13,9 +13,6 @@ require 'base64'
13
13
  require 'cgi'
14
14
  require 'stringio'
15
15
 
16
- require 'plist/generator'
17
- require 'plist/parser'
18
-
19
- module Plist
20
- VERSION = '3.1.0'
21
- end
16
+ require_relative 'plist/generator'
17
+ require_relative 'plist/parser'
18
+ require_relative 'plist/version'
@@ -1,222 +1,173 @@
1
- #!/usr/bin/env ruby
2
- #
1
+ # encoding: utf-8
2
+
3
3
  # = plist
4
4
  #
5
5
  # Copyright 2006-2010 Ben Bleything and Patrick May
6
6
  # Distributed under the MIT License
7
7
  #
8
8
 
9
- module Plist ; end
10
-
11
- # === Create a plist
12
- # You can dump an object to a plist in one of two ways:
13
- #
14
- # * <tt>Plist::Emit.dump(obj)</tt>
15
- # * <tt>obj.to_plist</tt>
16
- # * This requires that you mixin the <tt>Plist::Emit</tt> module, which is already done for +Array+ and +Hash+.
17
- #
18
- # The following Ruby classes are converted into native plist types:
19
- # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false
20
- # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the <array> and <dict> containers (respectively).
21
- # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a <data> element.
22
- # * 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.
23
- #
24
- # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below.
25
- module Plist::Emit
26
- # Helper method for injecting into classes. Calls <tt>Plist::Emit.dump</tt> with +self+.
27
- def to_plist(envelope = true)
28
- return Plist::Emit.dump(self, envelope)
29
- end
30
-
31
- # Helper method for injecting into classes. Calls <tt>Plist::Emit.save_plist</tt> with +self+.
32
- def save_plist(filename)
33
- Plist::Emit.save_plist(self, filename)
34
- end
35
-
36
- # The following Ruby classes are converted into native plist types:
37
- # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time
9
+ module Plist
10
+ # === Create a plist
11
+ # You can dump an object to a plist in one of two ways:
38
12
  #
39
- # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes.
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+.
40
16
  #
41
- # +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+.
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.
42
22
  #
43
- # 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.
44
- def self.dump(obj, envelope = true)
45
- output = plist_node(obj)
23
+ # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below.
24
+ module Emit
25
+ DEFAULT_INDENT = "\t"
46
26
 
47
- output = wrap(output) if envelope
27
+ # Helper method for injecting into classes. Calls <tt>Plist::Emit.dump</tt> with +self+.
28
+ def to_plist(envelope = true, options = {})
29
+ Plist::Emit.dump(self, envelope, options)
30
+ end
48
31
 
49
- return output
50
- end
32
+ # Helper method for injecting into classes. Calls <tt>Plist::Emit.save_plist</tt> with +self+.
33
+ def save_plist(filename, options = {})
34
+ Plist::Emit.save_plist(self, filename, options)
35
+ end
51
36
 
52
- # Writes the serialized object's plist to the specified filename.
53
- def self.save_plist(obj, filename)
54
- File.open(filename, 'wb') do |f|
55
- f.write(obj.to_plist)
37
+ # The following Ruby classes are converted into native plist types:
38
+ # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time
39
+ #
40
+ # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes.
41
+ #
42
+ # +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+.
43
+ #
44
+ # 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.
45
+ def self.dump(obj, envelope = true, options = {})
46
+ options = { :indent => DEFAULT_INDENT }.merge(options)
47
+
48
+ output = PlistBuilder.new(options[:indent]).build(obj)
49
+ output = wrap(output) if envelope
50
+
51
+ output
56
52
  end
57
- end
58
53
 
59
- private
60
- def self.plist_node(element)
61
- output = ''
62
-
63
- if element.respond_to? :to_plist_node
64
- output << element.to_plist_node
65
- else
66
- case element
67
- when Array
68
- if element.empty?
69
- output << "<array/>\n"
54
+ # Writes the serialized object's plist to the specified filename.
55
+ def self.save_plist(obj, filename, options = {})
56
+ File.open(filename, 'wb') do |f|
57
+ f.write(obj.to_plist(true, options))
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ class PlistBuilder
64
+ def initialize(indent_str)
65
+ @indent_str = indent_str.to_s
66
+ end
67
+
68
+ def build(element, level=0)
69
+ if element.respond_to? :to_plist_node
70
+ element.to_plist_node
70
71
  else
71
- output << tag('array') {
72
- element.collect {|e| plist_node(e)}
73
- }
72
+ case element
73
+ when Array
74
+ if element.empty?
75
+ tag('array', nil, level)
76
+ else
77
+ tag('array', nil, level) {
78
+ element.collect {|e| build(e, level + 1) }.join
79
+ }
80
+ end
81
+ when Hash
82
+ if element.empty?
83
+ tag('dict', nil, level)
84
+ else
85
+ tag('dict', '', level) do
86
+ element.sort_by{|k,v| k.to_s }.collect do |k,v|
87
+ tag('key', CGI.escapeHTML(k.to_s), level + 1) +
88
+ build(v, level + 1)
89
+ end.join
90
+ end
91
+ end
92
+ when true, false
93
+ tag(element, nil, level)
94
+ when Time
95
+ tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ'), level)
96
+ when Date # also catches DateTime
97
+ tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ'), level)
98
+ when String, Symbol, Integer, Float
99
+ tag(element_type(element), CGI.escapeHTML(element.to_s), level)
100
+ when IO, StringIO
101
+ data = element.tap(&:rewind).read
102
+ data_tag(data, level)
103
+ else
104
+ data = Marshal.dump(element)
105
+ comment_tag('The <data> element below contains a Ruby object which has been serialized with Marshal.dump.') +
106
+ data_tag(data, level)
107
+ end
74
108
  end
75
- when Hash
76
- if element.empty?
77
- output << "<dict/>\n"
78
- else
79
- inner_tags = []
109
+ end
80
110
 
81
- element.keys.sort.each do |k|
82
- v = element[k]
83
- inner_tags << tag('key', CGI::escapeHTML(k.to_s))
84
- inner_tags << plist_node(v)
85
- end
111
+ private
86
112
 
87
- output << tag('dict') {
88
- inner_tags
89
- }
113
+ def tag(type, contents, level, &block)
114
+ if block_given?
115
+ indent("<#{type}>\n", level) +
116
+ block.call +
117
+ indent("</#{type}>\n", level)
118
+ elsif contents.to_s.empty?
119
+ indent("<#{type}/>\n", level)
120
+ else
121
+ indent("<#{type}>#{contents.to_s}</#{type}>\n", level)
90
122
  end
91
- when true, false
92
- output << "<#{element}/>\n"
93
- when Time
94
- output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ'))
95
- when Date # also catches DateTime
96
- output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ'))
97
- when String, Symbol, Fixnum, Bignum, Integer, Float
98
- output << tag(element_type(element), CGI::escapeHTML(element.to_s))
99
- when IO, StringIO
100
- element.rewind
101
- contents = element.read
123
+ end
124
+
125
+ def data_tag(data, level)
102
126
  # note that apple plists are wrapped at a different length then
103
127
  # what ruby's base64 wraps by default.
104
128
  # I used #encode64 instead of #b64encode (which allows a length arg)
105
129
  # because b64encode is b0rked and ignores the length arg.
106
- data = "\n"
107
- Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" }
108
- output << tag('data', data)
109
- else
110
- output << comment( 'The <data> element below contains a Ruby object which has been serialized with Marshal.dump.' )
111
- data = "\n"
112
- Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" }
113
- output << tag('data', data )
130
+ tag('data', nil, level) do
131
+ Base64.encode64(data)
132
+ .gsub(/\s+/, '')
133
+ .scan(/.{1,68}/o)
134
+ .collect { |line| indent(line, level) }
135
+ .join("\n")
136
+ .concat("\n")
137
+ end
114
138
  end
115
- end
116
-
117
- return output
118
- end
119
-
120
- def self.comment(content)
121
- return "<!-- #{content} -->\n"
122
- end
123
-
124
- def self.tag(type, contents = '', &block)
125
- out = nil
126
-
127
- if block_given?
128
- out = IndentedString.new
129
- out << "<#{type}>"
130
- out.raise_indent
131
-
132
- out << block.call
133
-
134
- out.lower_indent
135
- out << "</#{type}>"
136
- else
137
- out = "<#{type}>#{contents.to_s}</#{type}>\n"
138
- end
139
-
140
- return out.to_s
141
- end
142
-
143
- def self.wrap(contents)
144
- output = ''
145
-
146
- output << '<?xml version="1.0" encoding="UTF-8"?>' + "\n"
147
- output << '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' + "\n"
148
- output << '<plist version="1.0">' + "\n"
149
-
150
- output << contents
151
-
152
- output << '</plist>' + "\n"
153
-
154
- return output
155
- end
156
-
157
- def self.element_type(item)
158
- case item
159
- when String, Symbol
160
- 'string'
161
-
162
- when Fixnum, Bignum, Integer
163
- 'integer'
164
-
165
- when Float
166
- 'real'
167
139
 
168
- else
169
- raise "Don't know about this data type... something must be wrong!"
170
- end
171
- end
172
- private
173
- class IndentedString #:nodoc:
174
- attr_accessor :indent_string
175
-
176
- def initialize(str = "\t")
177
- @indent_string = str
178
- @contents = ''
179
- @indent_level = 0
180
- end
181
-
182
- def to_s
183
- return @contents
184
- end
185
-
186
- def raise_indent
187
- @indent_level += 1
188
- end
189
-
190
- def lower_indent
191
- @indent_level -= 1 if @indent_level > 0
192
- end
193
-
194
- def <<(val)
195
- if val.is_a? Array
196
- val.each do |f|
197
- self << f
198
- end
199
- else
200
- # if it's already indented, don't bother indenting further
201
- unless val =~ /\A#{@indent_string}/
202
- indent = @indent_string * @indent_level
140
+ def indent(str, level)
141
+ @indent_str.to_s * level + str
142
+ end
203
143
 
204
- @contents << val.gsub(/^/, indent)
144
+ def element_type(item)
145
+ case item
146
+ when String, Symbol
147
+ 'string'
148
+ when Integer
149
+ 'integer'
150
+ when Float
151
+ 'real'
205
152
  else
206
- @contents << val
153
+ raise "Don't know about this data type... something must be wrong!"
207
154
  end
155
+ end
208
156
 
209
- # it already has a newline, don't add another
210
- @contents << "\n" unless val =~ /\n$/
157
+ def comment_tag(content)
158
+ return "<!-- #{content} -->\n"
211
159
  end
212
160
  end
213
- end
214
- end
215
161
 
216
- # we need to add this so sorting hash keys works properly
217
- class Symbol #:nodoc:
218
- def <=> (other)
219
- self.to_s <=> other.to_s
162
+ def self.wrap(contents)
163
+ output = '<?xml version="1.0" encoding="UTF-8"?>' + "\n"
164
+ output << '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' + "\n"
165
+ output << '<plist version="1.0">' + "\n"
166
+ output << contents
167
+ output << '</plist>' + "\n"
168
+
169
+ output
170
+ end
220
171
  end
221
172
  end
222
173
 
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env ruby
2
- #
1
+ # encoding: utf-8
2
+
3
3
  # = plist
4
4
  #
5
5
  # Copyright 2006-2010 Ben Bleything and Patrick May
@@ -11,42 +11,48 @@
11
11
  # === Load a plist file
12
12
  # This is the main point of the library:
13
13
  #
14
- # r = Plist::parse_xml( filename_or_xml )
14
+ # r = Plist.parse_xml(filename_or_xml)
15
15
  module Plist
16
- # Note that I don't use these two elements much:
17
- #
18
- # + Date elements are returned as DateTime objects.
19
- # + Data elements are implemented as Tempfiles
20
- #
21
- # Plist::parse_xml will blow up if it encounters a data element.
22
- # If you encounter such an error, or if you have a Date element which
23
- # can't be parsed into a Time object, please send your plist file to
24
- # plist@hexane.org so that I can implement the proper support.
25
- def Plist::parse_xml( filename_or_xml )
16
+ # Raised when an element is not implemented
17
+ class UnimplementedElementError < RuntimeError; end
18
+
19
+ # Note that I don't use these two elements much:
20
+ #
21
+ # + Date elements are returned as DateTime objects.
22
+ # + Data elements are implemented as Tempfiles
23
+ #
24
+ # Plist.parse_xml will blow up if it encounters a Date element.
25
+ # If you encounter such an error, or if you have a Date element which
26
+ # can't be parsed into a Time object, please create an issue
27
+ # attaching your plist file at https://github.com/patsplat/plist/issues
28
+ # so folks can implement the proper support.
29
+ def self.parse_xml(filename_or_xml)
26
30
  listener = Listener.new
27
- #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener)
31
+ # parser = REXML::Parsers::StreamParser.new(File.new(filename), listener)
28
32
  parser = StreamParser.new(filename_or_xml, listener)
29
33
  parser.parse
30
34
  listener.result
31
35
  end
32
36
 
33
37
  class Listener
34
- #include REXML::StreamListener
38
+ # include REXML::StreamListener
35
39
 
36
40
  attr_accessor :result, :open
37
41
 
38
42
  def initialize
39
43
  @result = nil
40
- @open = Array.new
44
+ @open = []
41
45
  end
42
46
 
43
-
44
47
  def tag_start(name, attributes)
45
- @open.push PTag::mappings[name].new
48
+ @open.push PTag.mappings[name].new
46
49
  end
47
50
 
48
- def text( contents )
49
- @open.last.text = contents if @open.last
51
+ def text(contents)
52
+ if @open.last
53
+ @open.last.text ||= ''
54
+ @open.last.text.concat(contents)
55
+ end
50
56
  end
51
57
 
52
58
  def tag_end(name)
@@ -60,11 +66,11 @@ module Plist
60
66
  end
61
67
 
62
68
  class StreamParser
63
- def initialize( plist_data_or_file, listener )
69
+ def initialize(plist_data_or_file, listener)
64
70
  if plist_data_or_file.respond_to? :read
65
71
  @xml = plist_data_or_file.read
66
- elsif File.exists? plist_data_or_file
67
- @xml = File.read( plist_data_or_file )
72
+ elsif File.exist? plist_data_or_file
73
+ @xml = File.read(plist_data_or_file)
68
74
  else
69
75
  @xml = plist_data_or_file
70
76
  end
@@ -72,26 +78,35 @@ module Plist
72
78
  @listener = listener
73
79
  end
74
80
 
75
- TEXT = /([^<]+)/
76
- XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um
77
- DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um
78
- COMMENT_START = /\A<!--/u
79
- COMMENT_END = /.*?-->/um
80
-
81
+ TEXT = /([^<]+)/
82
+ CDATA = /<!\[CDATA\[(.*?)\]\]>/
83
+ XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/m
84
+ DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/m
85
+ COMMENT_START = /\A<!--/
86
+ COMMENT_END = /.*?-->/m
87
+ UNIMPLEMENTED_ERROR = 'Unimplemented element. ' \
88
+ 'Consider reporting via https://github.com/patsplat/plist/issues'
81
89
 
82
90
  def parse
83
- plist_tags = PTag::mappings.keys.join('|')
91
+ plist_tags = PTag.mappings.keys.join('|')
84
92
  start_tag = /<(#{plist_tags})([^>]*)>/i
85
93
  end_tag = /<\/(#{plist_tags})[^>]*>/i
86
94
 
87
95
  require 'strscan'
88
96
 
89
- @scanner = StringScanner.new( @xml )
97
+ @scanner = StringScanner.new(@xml)
90
98
  until @scanner.eos?
91
99
  if @scanner.scan(COMMENT_START)
92
100
  @scanner.scan(COMMENT_END)
93
101
  elsif @scanner.scan(XMLDECL_PATTERN)
102
+ encoding = parse_encoding_from_xml_declaration(@scanner[1])
103
+ next if encoding.nil?
104
+
105
+ # use the specified encoding for the rest of the file
106
+ next unless String.method_defined?(:force_encoding)
107
+ @scanner.string = @scanner.rest.force_encoding(encoding)
94
108
  elsif @scanner.scan(DOCTYPE_PATTERN)
109
+ next
95
110
  elsif @scanner.scan(start_tag)
96
111
  @listener.tag_start(@scanner[1], nil)
97
112
  if (@scanner[2] =~ /\/$/)
@@ -99,32 +114,49 @@ module Plist
99
114
  end
100
115
  elsif @scanner.scan(TEXT)
101
116
  @listener.text(@scanner[1])
117
+ elsif @scanner.scan(CDATA)
118
+ @listener.text(@scanner[1])
102
119
  elsif @scanner.scan(end_tag)
103
120
  @listener.tag_end(@scanner[1])
104
121
  else
105
- raise "Unimplemented element"
122
+ raise UnimplementedElementError.new(UNIMPLEMENTED_ERROR)
106
123
  end
107
124
  end
108
125
  end
126
+
127
+ private
128
+
129
+ def parse_encoding_from_xml_declaration(xml_declaration)
130
+ return unless defined?(Encoding)
131
+
132
+ xml_encoding = xml_declaration.match(/(?:\A|\s)encoding=(?:"(.*?)"|'(.*?)')(?:\s|\Z)/)
133
+
134
+ return if xml_encoding.nil?
135
+
136
+ begin
137
+ Encoding.find(xml_encoding[1])
138
+ rescue ArgumentError
139
+ nil
140
+ end
141
+ end
109
142
  end
110
143
 
111
144
  class PTag
112
- @@mappings = { }
113
- def PTag::mappings
114
- @@mappings
145
+ def self.mappings
146
+ @mappings ||= {}
115
147
  end
116
148
 
117
- def PTag::inherited( sub_class )
149
+ def self.inherited(sub_class)
118
150
  key = sub_class.to_s.downcase
119
- key.gsub!(/^plist::/, '' )
151
+ key.gsub!(/^plist::/, '')
120
152
  key.gsub!(/^p/, '') unless key == "plist"
121
153
 
122
- @@mappings[key] = sub_class
154
+ mappings[key] = sub_class
123
155
  end
124
156
 
125
157
  attr_accessor :text, :children
126
158
  def initialize
127
- @children = Array.new
159
+ @children = []
128
160
  end
129
161
 
130
162
  def to_ruby
@@ -140,7 +172,7 @@ module Plist
140
172
 
141
173
  class PDict < PTag
142
174
  def to_ruby
143
- dict = Hash.new
175
+ dict = {}
144
176
  key = nil
145
177
 
146
178
  children.each do |c|
@@ -158,13 +190,13 @@ module Plist
158
190
 
159
191
  class PKey < PTag
160
192
  def to_ruby
161
- CGI::unescapeHTML(text || '')
193
+ CGI.unescapeHTML(text || '')
162
194
  end
163
195
  end
164
196
 
165
197
  class PString < PTag
166
198
  def to_ruby
167
- CGI::unescapeHTML(text || '')
199
+ CGI.unescapeHTML(text || '')
168
200
  end
169
201
  end
170
202
 
@@ -210,11 +242,10 @@ module Plist
210
242
  require 'base64'
211
243
  class PData < PTag
212
244
  def to_ruby
213
- data = Base64.decode64(text.gsub(/\s+/, ''))
214
-
245
+ data = Base64.decode64(text.gsub(/\s+/, '')) unless text.nil?
215
246
  begin
216
247
  return Marshal.load(data)
217
- rescue Exception => e
248
+ rescue Exception
218
249
  io = StringIO.new
219
250
  io.write data
220
251
  io.rewind