sj-plist 3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +21 -0
- data/README +40 -0
- data/Rakefile +135 -0
- data/docs/USAGE +104 -0
- data/lib/plist.rb +22 -0
- data/lib/plist/ascii.rb +251 -0
- data/lib/plist/binary.rb +510 -0
- data/lib/plist/generator.rb +240 -0
- data/lib/plist/parser.rb +224 -0
- data/test/assets/AlbumData.xml +203 -0
- data/test/assets/Cookies.plist +104 -0
- data/test/assets/commented.plist +9 -0
- data/test/assets/example_data.bin +0 -0
- data/test/assets/example_data.jpg +0 -0
- data/test/assets/example_data.plist +259 -0
- data/test/assets/example_data_ascii.plist +51 -0
- data/test/assets/ruby.plist +1696 -0
- data/test/assets/test_data_elements.plist +24 -0
- data/test/assets/test_empty_key.plist +13 -0
- data/test/test_ascii.rb +143 -0
- data/test/test_binary.rb +116 -0
- data/test/test_data_elements.rb +115 -0
- data/test/test_generator.rb +59 -0
- data/test/test_generator_basic_types.rb +58 -0
- data/test/test_generator_collections.rb +82 -0
- data/test/test_parser.rb +100 -0
- metadata +80 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2006, Ben Bleything <ben@bleything.net>
|
2
|
+
and Patrick May <patrick@hexane.org>
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included
|
13
|
+
in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
16
|
+
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
17
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
= All-purpose Property List manipulation library (w/ binary support)
|
2
|
+
|
3
|
+
Plist is a library to manipulate Property List files, also known as plists. It can parse plist files into native Ruby data structures as well as generating new plist files from your Ruby objects.
|
4
|
+
|
5
|
+
This fork includes support for binary plists.
|
6
|
+
|
7
|
+
== Usage
|
8
|
+
|
9
|
+
gem install 'sj-plist'
|
10
|
+
|
11
|
+
See USAGE[link:files/docs/USAGE.html].
|
12
|
+
|
13
|
+
== Links
|
14
|
+
|
15
|
+
[<b>Project Page</b>] http://plist.rubyforge.org
|
16
|
+
[<b>Subversion repository</b>] svn://rubyforge.org//var/svn/plist
|
17
|
+
[<b>RDoc (on RubyForge)</b>] http://plist.rubyforge.org
|
18
|
+
|
19
|
+
== Credits
|
20
|
+
|
21
|
+
plist is maintained by Ben Bleything <mailto:ben@bleything.net> and Patrick May <mailto:patrick@hexane.org>. Patrick wrote most of the code; Ben is a recent addition to the project, having merged in his plist generation library.
|
22
|
+
|
23
|
+
Other folks who have helped along the way:
|
24
|
+
|
25
|
+
[<b>Martin Dittus</b>] who pointed out that +Time+ wasn't enough for plist <tt>Dates</tt>, especially those in <tt>~/Library/Cookies/Cookies.plist</tt>
|
26
|
+
[<b>Chuck Remes</b>] who pushed Patrick towards implementing <tt>#to_plist</tt>
|
27
|
+
[<b>Mat Schaffer</b>] who supplied code and test cases for <tt><data></tt> elements
|
28
|
+
[<b>Michael Granger</b>] for encouragement and help
|
29
|
+
|
30
|
+
== License and Copyright
|
31
|
+
|
32
|
+
plist is released under the MIT License.
|
33
|
+
|
34
|
+
Portions of the code (notably the Rakefile) contain code pulled and/or adapted from other projects. These files contain a comment at the top describing what was used.
|
35
|
+
|
36
|
+
=== MIT License
|
37
|
+
|
38
|
+
:include: MIT-LICENSE
|
39
|
+
|
40
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
##############################################################
|
2
|
+
# Copyright 2006, Ben Bleything <ben@bleything.net> and #
|
3
|
+
# Patrick May <patrick@hexane.org> #
|
4
|
+
# #
|
5
|
+
# Based heavily on Geoffrey Grosenbach's Rakefile for gruff. #
|
6
|
+
# Includes whitespace-fixing task based on code from Typo. #
|
7
|
+
# #
|
8
|
+
# Distributed under the MIT license. #
|
9
|
+
##############################################################
|
10
|
+
|
11
|
+
require 'fileutils'
|
12
|
+
require 'rubygems'
|
13
|
+
require 'rake'
|
14
|
+
require 'rake/testtask'
|
15
|
+
require 'rake/packagetask'
|
16
|
+
require 'rdoc/task'
|
17
|
+
require 'rubygems/package_task'
|
18
|
+
|
19
|
+
$:.unshift(File.dirname(__FILE__) + "/lib")
|
20
|
+
require 'plist'
|
21
|
+
|
22
|
+
PKG_NAME = 'sj-plist'
|
23
|
+
PKG_VERSION = Plist::VERSION
|
24
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
25
|
+
|
26
|
+
RELEASE_NAME = "REL #{PKG_VERSION}"
|
27
|
+
|
28
|
+
TEST_FILES = Dir.glob('test/test_*').delete_if { |item| item.include?( "\.svn" ) }
|
29
|
+
TEST_ASSETS = Dir.glob('test/assets/*').delete_if { |item| item.include?( "\.svn" ) }
|
30
|
+
LIB_FILES = Dir.glob('lib/**/*').delete_if { |item| item.include?( "\.svn" ) }
|
31
|
+
RELEASE_FILES = [ "Rakefile", "README", "MIT-LICENSE", "docs/USAGE" ] + LIB_FILES + TEST_FILES + TEST_ASSETS
|
32
|
+
|
33
|
+
task :default => [ :test ]
|
34
|
+
# Run the unit tests
|
35
|
+
Rake::TestTask.new { |t|
|
36
|
+
t.libs << "test"
|
37
|
+
t.pattern = 'test/test_*.rb'
|
38
|
+
t.verbose = true
|
39
|
+
}
|
40
|
+
|
41
|
+
desc "Clean pkg, coverage, and rdoc; remove .bak files"
|
42
|
+
task :clean => [ :clobber_rdoc, :clobber_package, :clobber_coverage ] do
|
43
|
+
puts cmd = "find . -type f -name *.bak -delete"
|
44
|
+
`#{cmd}`
|
45
|
+
end
|
46
|
+
|
47
|
+
task :clobber_coverage do
|
48
|
+
puts cmd = "rm -rf coverage"
|
49
|
+
`#{cmd}`
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Generate coverage analysis with rcov (requires rcov to be installed)"
|
53
|
+
task :rcov => [ :clobber_coverage ] do
|
54
|
+
puts cmd = "rcov -Ilib --xrefs -T test/*.rb"
|
55
|
+
puts `#{cmd}`
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Strip trailing whitespace and fix newlines for all release files"
|
59
|
+
task :fix_whitespace => [ :clean ] do
|
60
|
+
RELEASE_FILES.reject {|i| i =~ /assets/}.each do |filename|
|
61
|
+
next if File.directory? filename
|
62
|
+
|
63
|
+
File.open(filename) do |file|
|
64
|
+
newfile = ''
|
65
|
+
needs_love = false
|
66
|
+
|
67
|
+
file.readlines.each_with_index do |line, lineno|
|
68
|
+
if line =~ /[ \t]+$/
|
69
|
+
needs_love = true
|
70
|
+
puts "#{filename}: trailing whitespace on line #{lineno}"
|
71
|
+
line.gsub!(/[ \t]*$/, '')
|
72
|
+
end
|
73
|
+
|
74
|
+
if line.chomp == line
|
75
|
+
needs_love = true
|
76
|
+
puts "#{filename}: no newline on line #{lineno}"
|
77
|
+
line << "\n"
|
78
|
+
end
|
79
|
+
|
80
|
+
newfile << line
|
81
|
+
end
|
82
|
+
|
83
|
+
if needs_love
|
84
|
+
tempname = "#{filename}.new"
|
85
|
+
|
86
|
+
File.open(tempname, 'w').write(newfile)
|
87
|
+
File.chmod(File.stat(filename).mode, tempname)
|
88
|
+
|
89
|
+
FileUtils.ln filename, "#{filename}.bak"
|
90
|
+
FileUtils.ln tempname, filename, :force => true
|
91
|
+
File.unlink(tempname)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Genereate the RDoc documentation
|
98
|
+
RDoc::Task.new { |rdoc|
|
99
|
+
rdoc.rdoc_dir = 'rdoc'
|
100
|
+
rdoc.title = "All-purpose Property List manipulation library"
|
101
|
+
rdoc.options << '-SNmREADME'
|
102
|
+
rdoc.template = "docs/jamis-template.rb"
|
103
|
+
rdoc.rdoc_files.include('README', 'MIT-LICENSE', 'CHANGELOG')
|
104
|
+
rdoc.rdoc_files.include Dir.glob('docs/**').delete_if {|f| f.include? 'jamis' }
|
105
|
+
rdoc.rdoc_files.include('lib/**')
|
106
|
+
}
|
107
|
+
|
108
|
+
# Create compressed packages
|
109
|
+
spec = Gem::Specification.new do |s|
|
110
|
+
s.name = PKG_NAME
|
111
|
+
s.version = PKG_VERSION
|
112
|
+
|
113
|
+
s.summary = "All-purpose Property List manipulation library (w/ binary support)."
|
114
|
+
s.description = <<-EOD
|
115
|
+
Plist is a library to manipulate Property List files, also known as plists. It can parse plist files into native Ruby data structures as well as generating new plist files from your Ruby objects.
|
116
|
+
|
117
|
+
This fork includes support for binary plists.
|
118
|
+
EOD
|
119
|
+
|
120
|
+
s.authors = "Ben Bleything and Patrick May. Binary support merged by Sijmen Mulder."
|
121
|
+
s.homepage = "http://github.com/sjmulder/plist"
|
122
|
+
|
123
|
+
s.has_rdoc = true
|
124
|
+
|
125
|
+
s.files = RELEASE_FILES
|
126
|
+
s.test_files = TEST_FILES
|
127
|
+
|
128
|
+
s.autorequire = 'plist'
|
129
|
+
end
|
130
|
+
|
131
|
+
Gem::PackageTask.new(spec) do |p|
|
132
|
+
p.gem_spec = spec
|
133
|
+
p.need_tar = true
|
134
|
+
p.need_zip = true
|
135
|
+
end
|
data/docs/USAGE
ADDED
@@ -0,0 +1,104 @@
|
|
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
|
15
|
+
|
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
|
+
<dict>
|
20
|
+
<key>FirstName</key>
|
21
|
+
<string>John</string>
|
22
|
+
|
23
|
+
<key>LastName</key>
|
24
|
+
<string>Public</string>
|
25
|
+
|
26
|
+
<key>StreetAddr1</key>
|
27
|
+
<string>123 Anywhere St.</string>
|
28
|
+
|
29
|
+
<key>StateProv</key>
|
30
|
+
<string>CA</string>
|
31
|
+
|
32
|
+
<key>City</key>
|
33
|
+
<string>Some Town</string>
|
34
|
+
|
35
|
+
<key>CountryName</key>
|
36
|
+
<string>United States</string>
|
37
|
+
|
38
|
+
<key>AreaCode</key>
|
39
|
+
<string>555</string>
|
40
|
+
|
41
|
+
<key>LocalPhoneNumber</key>
|
42
|
+
<string>5551212</string>
|
43
|
+
|
44
|
+
<key>ZipPostal</key>
|
45
|
+
<string>12345</string>
|
46
|
+
</dict>
|
47
|
+
</plist>
|
48
|
+
|
49
|
+
== Generation
|
50
|
+
|
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
|
53
|
+
|
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.
|
57
|
+
|
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.
|
103
|
+
|
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
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#--
|
2
|
+
##############################################################
|
3
|
+
# Copyright 2006, Ben Bleything <ben@bleything.net> and #
|
4
|
+
# Patrick May <patrick@hexane.org> #
|
5
|
+
# #
|
6
|
+
# Distributed under the MIT license. #
|
7
|
+
##############################################################
|
8
|
+
#++
|
9
|
+
# = Plist
|
10
|
+
#
|
11
|
+
# This is the main file for plist. Everything interesting happens in Plist and Plist::Emit.
|
12
|
+
|
13
|
+
require 'base64'
|
14
|
+
require 'cgi'
|
15
|
+
require 'stringio'
|
16
|
+
|
17
|
+
require 'plist/generator'
|
18
|
+
require 'plist/parser'
|
19
|
+
|
20
|
+
module Plist
|
21
|
+
VERSION = '3.2'
|
22
|
+
end
|
data/lib/plist/ascii.rb
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
require "time"
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
module Plist
|
6
|
+
|
7
|
+
#
|
8
|
+
# string - the plist string to parse
|
9
|
+
# opts - options (see +AsciiParser.new+)
|
10
|
+
#
|
11
|
+
|
12
|
+
def self.parse_ascii(string, opts = {})
|
13
|
+
AsciiParser.new(string, opts).parse
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Plist::AsciiParser
|
18
|
+
#
|
19
|
+
# Parser for the old style ASCII/NextSTEP property lists.
|
20
|
+
#
|
21
|
+
# Created by Jari Bakken on 2008-08-13.
|
22
|
+
#
|
23
|
+
class AsciiParser < StringScanner
|
24
|
+
|
25
|
+
class ParseError < StandardError; end
|
26
|
+
|
27
|
+
SPACE_REGEXP = %r{ ( /\*.*?\*/ | # block comments
|
28
|
+
//.*?$\n? | # single-line comments
|
29
|
+
\s* )+ # space
|
30
|
+
}mx
|
31
|
+
|
32
|
+
CONTROL_CHAR = {
|
33
|
+
"a" => "\a",
|
34
|
+
"b" => "\b",
|
35
|
+
"n" => "\n",
|
36
|
+
"f" => "\f",
|
37
|
+
"t" => "\t",
|
38
|
+
"r" => "\r",
|
39
|
+
"v" => "\v",
|
40
|
+
}
|
41
|
+
|
42
|
+
BOOLS = {true => "1", false => "0"}
|
43
|
+
|
44
|
+
#
|
45
|
+
# string - the plist string to parse
|
46
|
+
#
|
47
|
+
# options hash:
|
48
|
+
#
|
49
|
+
# :parse_numbers => true/false : Set this to true if you numeric values (float/ints) as the correct Ruby type
|
50
|
+
# :parse_booleans => true/false : Set this to true if you want "true" and "false" to return the boolean Ruby types
|
51
|
+
#
|
52
|
+
# Note: Apple's parsers return strings for all old-style plist types.
|
53
|
+
#
|
54
|
+
|
55
|
+
def initialize(string, opts = {})
|
56
|
+
string = case string
|
57
|
+
when StringIO
|
58
|
+
string.string
|
59
|
+
when IO
|
60
|
+
string.read
|
61
|
+
else
|
62
|
+
string
|
63
|
+
end
|
64
|
+
|
65
|
+
@parse_numbers = opts.delete(:parse_numbers)
|
66
|
+
@parse_bools = opts.delete(:parse_booleans)
|
67
|
+
@debug = $VERBOSE == true # ruby -W3
|
68
|
+
|
69
|
+
raise ArgumentError, "unknown option #{opts.inspect}" unless opts.empty?
|
70
|
+
|
71
|
+
super(string)
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse
|
75
|
+
res = object
|
76
|
+
|
77
|
+
skip_space
|
78
|
+
error "junk after plist" unless eos?
|
79
|
+
|
80
|
+
res
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def object
|
86
|
+
skip_space
|
87
|
+
|
88
|
+
if scan(/\{/) then dictionary
|
89
|
+
elsif scan(/\(/) then array
|
90
|
+
elsif scan(/</) then data
|
91
|
+
elsif scan(/"/) then quoted_string
|
92
|
+
else unquoted_string
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def quoted_string
|
97
|
+
puts "creating quoted string #{inspect}" if @debug
|
98
|
+
|
99
|
+
result = ''
|
100
|
+
|
101
|
+
loop do
|
102
|
+
if scan(/\\/)
|
103
|
+
result << escaped
|
104
|
+
elsif scan(/"/)
|
105
|
+
break
|
106
|
+
elsif eos?
|
107
|
+
error("unterminated quoted string")
|
108
|
+
else scan(/./)
|
109
|
+
error("unterminated quoted string") unless matched
|
110
|
+
result << matched
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
def escaped
|
118
|
+
if scan(/"|\\|\\\//)
|
119
|
+
matched
|
120
|
+
elsif scan(/a|b|f|n|v|r|t/)
|
121
|
+
CONTROL_CHAR[matched]
|
122
|
+
elsif scan(/u[0-9a-f]{4}/i)
|
123
|
+
[ matched[1..-1].to_i(16) ].pack("U")
|
124
|
+
elsif scan(/\d{1,3}/)
|
125
|
+
[ matched.to_i(8) ].pack("C")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def unquoted_string
|
130
|
+
puts "creating unquoted string #{inspect}" if @debug
|
131
|
+
|
132
|
+
if scan(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ((\+|-)\d{4})?/)
|
133
|
+
puts "returning time" if @debug
|
134
|
+
require 'date'
|
135
|
+
DateTime.parse(matched)
|
136
|
+
elsif scan(/-?\d+?\.\d+\b/)
|
137
|
+
puts "returning float" if @debug
|
138
|
+
@parse_numbers ? matched.to_f : matched
|
139
|
+
elsif scan(/-?\d+\b/)
|
140
|
+
puts "returning int" if @debug
|
141
|
+
@parse_numbers ? matched.to_i : matched
|
142
|
+
elsif scan(/\b(true|false)\b/)
|
143
|
+
val = matched == 'true'
|
144
|
+
if @parse_bools
|
145
|
+
val
|
146
|
+
else
|
147
|
+
@parse_numbers ? BOOLS[val].to_i : BOOLS[val]
|
148
|
+
end
|
149
|
+
elsif eos?
|
150
|
+
error("unexpected end-of-string")
|
151
|
+
else
|
152
|
+
puts "returning string" if @debug
|
153
|
+
scan(/\w+/)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def data
|
158
|
+
puts "creating data #{inspect}" if @debug
|
159
|
+
|
160
|
+
scan(/(.+?)>/)
|
161
|
+
|
162
|
+
hex = self[1].delete(" ")
|
163
|
+
[hex].pack("H*")
|
164
|
+
end
|
165
|
+
|
166
|
+
def array
|
167
|
+
puts "creating array #{inspect}" if @debug
|
168
|
+
|
169
|
+
skip_space
|
170
|
+
|
171
|
+
arr = []
|
172
|
+
until scan(/\)/)
|
173
|
+
val = object()
|
174
|
+
|
175
|
+
return nil unless val
|
176
|
+
skip_space
|
177
|
+
|
178
|
+
unless skip(/,\s*/)
|
179
|
+
skip_space
|
180
|
+
if scan(/\)/)
|
181
|
+
return arr << val
|
182
|
+
else
|
183
|
+
error "missing ',' or ')' for array"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
arr << val
|
188
|
+
error "unexpected end-of-string when parsing array" if eos?
|
189
|
+
end
|
190
|
+
|
191
|
+
arr
|
192
|
+
end
|
193
|
+
|
194
|
+
def dictionary
|
195
|
+
puts "creating dict #{inspect}" if @debug
|
196
|
+
|
197
|
+
skip_space
|
198
|
+
|
199
|
+
dict = {}
|
200
|
+
until scan(/\}/)
|
201
|
+
key = object()
|
202
|
+
p :key => key if @debug
|
203
|
+
|
204
|
+
error "expected terminating '}' for dictionary" unless key
|
205
|
+
|
206
|
+
# dictionary keys must be strings, even if represented as 12345 in the plist
|
207
|
+
if key.is_a?(Integer) && key >= 0
|
208
|
+
key = key.to_s
|
209
|
+
end
|
210
|
+
error "dictionary key must be string (\"quoted\" or alphanumeric)" unless key.is_a? String
|
211
|
+
|
212
|
+
skip_space
|
213
|
+
error "missing '=' in dictionary" unless scan(/=/)
|
214
|
+
skip_space
|
215
|
+
|
216
|
+
val = object()
|
217
|
+
p :val => val if @debug
|
218
|
+
|
219
|
+
skip_space
|
220
|
+
error "missing ';' in dictionary" unless skip(/;/)
|
221
|
+
skip_space
|
222
|
+
|
223
|
+
dict[key] = val
|
224
|
+
error "unexpected end-of-string when parsing dictionary" if eos?
|
225
|
+
end
|
226
|
+
|
227
|
+
dict
|
228
|
+
end
|
229
|
+
|
230
|
+
def error(msg)
|
231
|
+
line = 1
|
232
|
+
string.split(//).each_with_index do |e, i|
|
233
|
+
line += 1 if e == "\n"
|
234
|
+
break if i == pos
|
235
|
+
end
|
236
|
+
|
237
|
+
context = (pos - 10) < 0 ? 0 : pos - 10
|
238
|
+
err = "#{msg} at line #{line}\n"
|
239
|
+
err << "#{string[context..pos+10]}".inspect << "\n"
|
240
|
+
|
241
|
+
err << "\n#{inspect}" if @debug
|
242
|
+
raise ParseError, err
|
243
|
+
end
|
244
|
+
|
245
|
+
def skip_space
|
246
|
+
puts "skipping whitespace" if @debug
|
247
|
+
skip SPACE_REGEXP
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
end
|