property-list 1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/LICENSE +29 -0
- data/README.md +52 -0
- data/Rakefile +7 -0
- data/gems.locked +41 -0
- data/gems.rb +3 -0
- data/lib/property-list.rb +83 -0
- data/lib/property-list/ascii_generator.rb +148 -0
- data/lib/property-list/ascii_parser.rb +217 -0
- data/lib/property-list/binary_generator.rb +306 -0
- data/lib/property-list/binary_markers.rb +34 -0
- data/lib/property-list/binary_parser.rb +169 -0
- data/lib/property-list/version.rb +3 -0
- data/lib/property-list/xml_generator.rb +147 -0
- data/lib/property-list/xml_parser.rb +121 -0
- data/property-list.gemspec +22 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cccb65a32fea2d625572d56e271741ad40ed0e57
|
4
|
+
data.tar.gz: 321a139923120337ab64a16c9836386e3ab5feaa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e7fe9ed9590b7a55622509048b7d51f44a011ca8dc223acd717e3cdcbcad0802627bb9075be536f511a27cf7245a2bdc5962a71d9e82dd1f87a165025f2a5777
|
7
|
+
data.tar.gz: 4ef25397d9e969d28bc97943f11c9e075c00b12b36479e5cad80c47c6eec9a2fbdc94ad5c7c454c65ae1fab4a13550e8e747a425a9406c7f7ce21dfd80bb06aa
|
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
# Gemfile.lock
|
46
|
+
# .ruby-version
|
47
|
+
# .ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
data/LICENSE
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
BSD 3-Clause License
|
2
|
+
|
3
|
+
Copyright (c) 2017, luikore
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
10
|
+
list of conditions and the following disclaimer.
|
11
|
+
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
14
|
+
and/or other materials provided with the distribution.
|
15
|
+
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
17
|
+
contributors may be used to endorse or promote products derived from
|
18
|
+
this software without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
This is a full-featured property list library which supports XML/Binary/ASCII format plists, and performance is tuned for large property list files.
|
2
|
+
|
3
|
+
The code is as clean and complete as possible. There is no runtime dependency to any other gems or libplist or other libraries.
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
Requires Ruby 1.9+
|
8
|
+
|
9
|
+
gem ins property-list
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Load a plist file
|
14
|
+
|
15
|
+
PropertyList.load_xml File.read "some_plist.xml"
|
16
|
+
PropertyList.load_binary File.binread "some_binary.plist"
|
17
|
+
PropertyList.load_ascii File.read "some_ascii.strings"
|
18
|
+
PropertyList.load File.binread "unknown_format.plist"
|
19
|
+
|
20
|
+
Generate a plist file data
|
21
|
+
|
22
|
+
PropertyList.dump_xml obj
|
23
|
+
PropertyList.dump_binary obj
|
24
|
+
PropertyList.dump_ascii obj
|
25
|
+
|
26
|
+
## Data type mapping
|
27
|
+
|
28
|
+
Data type mapping in `PropertyList.load`:
|
29
|
+
|
30
|
+
real: Float
|
31
|
+
string: String
|
32
|
+
integer: Integer
|
33
|
+
data: StringIO
|
34
|
+
date: DateTime
|
35
|
+
true: true
|
36
|
+
false: false
|
37
|
+
uid: PropertyList::Uid # obj.uid is the integer index
|
38
|
+
|
39
|
+
Reverse mapping in `PropertyList.dump_*`:
|
40
|
+
|
41
|
+
Float: real
|
42
|
+
String, Symbol: string
|
43
|
+
Integer: integer
|
44
|
+
StringIO, IO: data
|
45
|
+
Time, DateTime, Date: date
|
46
|
+
true: true
|
47
|
+
false: false
|
48
|
+
PropertyList::Uid: uid
|
49
|
+
|
50
|
+
## Credits
|
51
|
+
|
52
|
+
The binary generating code is modified from https://github.com/jarib/plist with bug fixes and performance tuning.
|
data/Rakefile
ADDED
data/gems.locked
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
property-list (1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.1)
|
10
|
+
docile (1.1.5)
|
11
|
+
hoe (3.16.1)
|
12
|
+
rake (>= 0.8, < 13.0)
|
13
|
+
json (2.1.0)
|
14
|
+
method_source (0.8.2)
|
15
|
+
pry (0.10.4)
|
16
|
+
coderay (~> 1.1.0)
|
17
|
+
method_source (~> 0.8.1)
|
18
|
+
slop (~> 3.4)
|
19
|
+
rake (12.0.0)
|
20
|
+
simplecov (0.14.1)
|
21
|
+
docile (~> 1.1.0)
|
22
|
+
json (>= 1.8, < 3)
|
23
|
+
simplecov-html (~> 0.10.0)
|
24
|
+
simplecov-html (0.10.2)
|
25
|
+
slop (3.6.0)
|
26
|
+
test-unit (1.2.3)
|
27
|
+
hoe (>= 1.5.1)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
bundler (~> 1.15.4)
|
34
|
+
property-list!
|
35
|
+
pry (~> 0.10.4)
|
36
|
+
rake (~> 12.0.0)
|
37
|
+
simplecov (~> 0.14.1)
|
38
|
+
test-unit (~> 1.2.3)
|
39
|
+
|
40
|
+
BUNDLED WITH
|
41
|
+
1.15.4
|
data/gems.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'set'
|
3
|
+
require 'base64'
|
4
|
+
require 'cgi'
|
5
|
+
require 'stringio'
|
6
|
+
require 'strscan'
|
7
|
+
|
8
|
+
require_relative 'property-list/xml_generator'
|
9
|
+
require_relative 'property-list/xml_parser'
|
10
|
+
require_relative 'property-list/ascii_generator'
|
11
|
+
require_relative 'property-list/ascii_parser'
|
12
|
+
require_relative 'property-list/binary_markers'
|
13
|
+
require_relative 'property-list/binary_generator'
|
14
|
+
require_relative 'property-list/binary_parser'
|
15
|
+
require_relative 'property-list/version'
|
16
|
+
|
17
|
+
# === Load a plist file
|
18
|
+
#
|
19
|
+
# PropertyList.load_xml File.read "some_plist.xml"
|
20
|
+
# PropertyList.load_binary File.binread "some_binary.plist"
|
21
|
+
# PropertyList.load_ascii File.read "some_ascii.strings"
|
22
|
+
# PropertyList.load File.binread "unknown_format.plist"
|
23
|
+
#
|
24
|
+
# === Generate a plist file data
|
25
|
+
#
|
26
|
+
# PropertyList.dump_xml obj
|
27
|
+
# PropertyList.dump_binary obj
|
28
|
+
# PropertyList.dump_ascii obj
|
29
|
+
#
|
30
|
+
module PropertyList
|
31
|
+
# load plist (binary or xml or ascii) into a ruby object
|
32
|
+
# auto detect the format
|
33
|
+
def self.load data
|
34
|
+
case data.byteslice(0, 8)
|
35
|
+
when /\Abplist\d\d/n
|
36
|
+
load_binary data.force_encoding('binary')
|
37
|
+
when /\A<\?xml\ /n
|
38
|
+
load_xml data.force_encoding('utf-8')
|
39
|
+
else
|
40
|
+
load_ascii data.force_encoding('utf-8')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class SyntaxError < RuntimeError
|
45
|
+
end
|
46
|
+
|
47
|
+
# binary plist v0x elements:
|
48
|
+
|
49
|
+
class UID
|
50
|
+
def initialize uid
|
51
|
+
@uid = uid
|
52
|
+
end
|
53
|
+
attr_reader :uid
|
54
|
+
end
|
55
|
+
|
56
|
+
# binary plist v1x elements:
|
57
|
+
|
58
|
+
class URL
|
59
|
+
def initialize url
|
60
|
+
@url = url
|
61
|
+
end
|
62
|
+
attr_reader :url
|
63
|
+
end
|
64
|
+
|
65
|
+
class UUID
|
66
|
+
def initialize uuid
|
67
|
+
@uuid = uuid
|
68
|
+
end
|
69
|
+
attr_reader :uuid
|
70
|
+
end
|
71
|
+
|
72
|
+
class OrderedSet
|
73
|
+
def initialize elements
|
74
|
+
@elements = elements.uniq
|
75
|
+
end
|
76
|
+
|
77
|
+
def each
|
78
|
+
@elements.each do |e|
|
79
|
+
yield e
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module PropertyList
|
2
|
+
# options can be:
|
3
|
+
#
|
4
|
+
# [:indent_unit] the indent unit, default value is <code>"\t"</code>, set to <code>''</code> if you don't need indent
|
5
|
+
#
|
6
|
+
# [:initial_indent] initial indent space, default is <code>''</code>, the indentation per line equals to <code>initial_indent + indent * current_indent_level</code>
|
7
|
+
#
|
8
|
+
# [:wrap] wrap the top level output with '{}' when obj is a Hash, default is true.
|
9
|
+
#
|
10
|
+
# [:encoding_comment] add encoding comment '!$*UTF8*$!' on top of file, default is false
|
11
|
+
#
|
12
|
+
# [:sort_keys] sort dict keys, default is true
|
13
|
+
#
|
14
|
+
def self.dump_ascii obj, indent_unit: "\t", initial_indent: '', wrap: true, encoding_comment: false, sort_keys: true
|
15
|
+
generator = AsciiGenerator.new indent_unit: indent_unit, initial_indent: initial_indent, sort_keys: sort_keys
|
16
|
+
generator.output << "// !$*UTF8*$!\n" if encoding_comment
|
17
|
+
generator.generate obj, wrap
|
18
|
+
generator.output << "\n" if wrap and obj.is_a?(Hash)
|
19
|
+
generator.output.join
|
20
|
+
end
|
21
|
+
|
22
|
+
class AsciiGenerator #:nodoc:
|
23
|
+
def initialize indent_unit: "\t", initial_indent: '', sort_keys: true
|
24
|
+
@indent_unit = indent_unit
|
25
|
+
@indent_level = 0
|
26
|
+
@initial_indent = initial_indent
|
27
|
+
@indent = @initial_indent + @indent_unit * @indent_level
|
28
|
+
@sort_keys = sort_keys
|
29
|
+
@output = []
|
30
|
+
end
|
31
|
+
attr_reader :output
|
32
|
+
|
33
|
+
def generate object, wrap=true
|
34
|
+
# See also
|
35
|
+
# http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html
|
36
|
+
# The <...> extensions are from GNUStep
|
37
|
+
|
38
|
+
case object
|
39
|
+
when Array
|
40
|
+
ascii_collection '(', ')' do
|
41
|
+
object.each do |e|
|
42
|
+
generate e
|
43
|
+
@output << ",\n"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
when Hash
|
47
|
+
if wrap
|
48
|
+
ascii_collection '{', '}' do
|
49
|
+
ascii_hash_content object
|
50
|
+
end
|
51
|
+
else
|
52
|
+
ascii_hash_content object
|
53
|
+
end
|
54
|
+
when true
|
55
|
+
ascii_value "<*BY>"
|
56
|
+
when false
|
57
|
+
ascii_value "<*BN>"
|
58
|
+
when Float
|
59
|
+
if object.to_i == object
|
60
|
+
object = object.to_i
|
61
|
+
end
|
62
|
+
ascii_value "<*R#{object}>"
|
63
|
+
when Integer
|
64
|
+
ascii_value "<*I#{object}>"
|
65
|
+
when Time, Date # also covers DateTime
|
66
|
+
ascii_value "<*D#{object.strftime '%Y-%m-%d %H:%M:%S %z'}>"
|
67
|
+
when String
|
68
|
+
ascii_string object
|
69
|
+
when Symbol
|
70
|
+
ascii_string object.to_s
|
71
|
+
when IO, StringIO
|
72
|
+
object.rewind
|
73
|
+
contents = object.read
|
74
|
+
ascii_data contents
|
75
|
+
else
|
76
|
+
raise "Generating of this class is not supported"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def ascii_value v
|
81
|
+
@output << @indent
|
82
|
+
@output << v
|
83
|
+
end
|
84
|
+
|
85
|
+
TABLE_FOR_ASCII_STRING_ESCAPE = {
|
86
|
+
"\\".ord => "\\\\",
|
87
|
+
'"'.ord => '\\"',
|
88
|
+
"\b".ord => '\b',
|
89
|
+
"\n".ord => "\\n",
|
90
|
+
"\r".ord => "\\r",
|
91
|
+
"\t".ord => "\\t"
|
92
|
+
}.freeze
|
93
|
+
def ascii_string s
|
94
|
+
@output << @indent
|
95
|
+
|
96
|
+
if s =~ /\A[\w\.\/]+\z/
|
97
|
+
@output << s
|
98
|
+
return
|
99
|
+
end
|
100
|
+
|
101
|
+
# there is also single quote string, in which we can use new lines and '' for single quote
|
102
|
+
@output << '"'
|
103
|
+
s.unpack('U*').each do |c|
|
104
|
+
if c > 127
|
105
|
+
# there is also a \OOO format, but we only generate the \U format
|
106
|
+
@output << "\\U#{c.to_s(16).rjust 4, '0'}"
|
107
|
+
elsif (escaped_c = TABLE_FOR_ASCII_STRING_ESCAPE[c])
|
108
|
+
@output << escaped_c
|
109
|
+
else
|
110
|
+
@output << c.chr
|
111
|
+
end
|
112
|
+
end
|
113
|
+
@output << '"'
|
114
|
+
end
|
115
|
+
|
116
|
+
def ascii_collection start_delim, end_delim
|
117
|
+
@output << @indent
|
118
|
+
@output << start_delim
|
119
|
+
@output << "\n"
|
120
|
+
@indent = @initial_indent + @indent_unit * (@indent_level += 1)
|
121
|
+
yield
|
122
|
+
@indent = @initial_indent + @indent_unit * (@indent_level -= 1)
|
123
|
+
@output << @indent
|
124
|
+
@output << end_delim
|
125
|
+
end
|
126
|
+
|
127
|
+
def ascii_hash_content object
|
128
|
+
keys = object.keys
|
129
|
+
keys.sort_by! &:to_s if @sort_keys
|
130
|
+
keys.each do |k|
|
131
|
+
v = object[k]
|
132
|
+
reset_indent = @indent
|
133
|
+
ascii_string k.to_s
|
134
|
+
@output << ' = '
|
135
|
+
@indent = '' # ignores the indent for first line
|
136
|
+
generate v
|
137
|
+
@output << ";\n"
|
138
|
+
@indent = reset_indent
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def ascii_data content
|
143
|
+
hex, _ = content.unpack 'H*'
|
144
|
+
hex.gsub! /(.{2})/, "\\1 "
|
145
|
+
@output << "< #{hex}>"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
module PropertyList
|
2
|
+
def self.load_ascii(data)
|
3
|
+
AsciiParser.new(data).parse
|
4
|
+
end
|
5
|
+
|
6
|
+
class AsciiParser
|
7
|
+
def initialize src
|
8
|
+
@lexer = StringScanner.new src.strip
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse
|
12
|
+
res = parse_object
|
13
|
+
skip_space_and_comment
|
14
|
+
if !@lexer.eos? or res.nil?
|
15
|
+
syntax_error "Unrecognized token"
|
16
|
+
end
|
17
|
+
res
|
18
|
+
end
|
19
|
+
|
20
|
+
def skip_space_and_comment
|
21
|
+
@lexer.skip(%r{(?:
|
22
|
+
[\x0A\x0D\u2028\u2029\x09\x0B\x0C\x20]+ # newline and space
|
23
|
+
|
|
24
|
+
//[^\x0A\x0D\u2028\u2029]* # one-line comment
|
25
|
+
|
|
26
|
+
/\*(?:.*?)\*/ # multi-line comment
|
27
|
+
)+}mx)
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_object
|
31
|
+
skip_space_and_comment
|
32
|
+
case @lexer.peek(1)
|
33
|
+
when '{'
|
34
|
+
parse_dict
|
35
|
+
when '('
|
36
|
+
parse_array
|
37
|
+
when '"'
|
38
|
+
parse_string '"'
|
39
|
+
when "'"
|
40
|
+
parse_string "'" # NOTE: not in GNU extension
|
41
|
+
when '<'
|
42
|
+
parse_extension_value
|
43
|
+
when /[\w\.\/]/
|
44
|
+
parse_unquoted_string
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_dict
|
49
|
+
@lexer.pos += 1
|
50
|
+
hash = {}
|
51
|
+
while (skip_space_and_comment; @lexer.peek(1) != '}')
|
52
|
+
k = \
|
53
|
+
case @lexer.peek(1)
|
54
|
+
when '"'
|
55
|
+
parse_string '"'
|
56
|
+
when "'"
|
57
|
+
parse_string "'"
|
58
|
+
when /\w/
|
59
|
+
parse_unquoted_string
|
60
|
+
end
|
61
|
+
if !k
|
62
|
+
syntax_error "Expect dictionary key"
|
63
|
+
end
|
64
|
+
|
65
|
+
skip_space_and_comment
|
66
|
+
if !@lexer.scan(/=/)
|
67
|
+
syntax_error "Expect '=' after dictionary key"
|
68
|
+
end
|
69
|
+
skip_space_and_comment
|
70
|
+
|
71
|
+
v = parse_object
|
72
|
+
if v.nil?
|
73
|
+
syntax_error "Expect dictionary value"
|
74
|
+
end
|
75
|
+
|
76
|
+
skip_space_and_comment
|
77
|
+
if !@lexer.scan(/;/)
|
78
|
+
syntax_error "Expect ';' after dictionary value"
|
79
|
+
end
|
80
|
+
skip_space_and_comment
|
81
|
+
|
82
|
+
hash[k] = v
|
83
|
+
end
|
84
|
+
if @lexer.getch != '}'
|
85
|
+
syntax_error "Unclosed hash"
|
86
|
+
end
|
87
|
+
hash
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_array
|
91
|
+
@lexer.pos += 1
|
92
|
+
array = []
|
93
|
+
while (skip_space_and_comment; @lexer.peek(1) != ')')
|
94
|
+
obj = parse_object
|
95
|
+
if obj.nil?
|
96
|
+
syntax_error "Failed to parse array element"
|
97
|
+
end
|
98
|
+
array << obj
|
99
|
+
skip_space_and_comment
|
100
|
+
@lexer.scan(/,/)
|
101
|
+
end
|
102
|
+
if @lexer.getch != ')'
|
103
|
+
syntax_error "Unclosed array"
|
104
|
+
end
|
105
|
+
array
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse_string delim
|
109
|
+
@lexer.pos += 1
|
110
|
+
|
111
|
+
# TODO (TextMate only, xcode cannot parse it) when delim is ', '' is the escape
|
112
|
+
|
113
|
+
chars = []
|
114
|
+
while (ch = @lexer.getch) != delim
|
115
|
+
case ch
|
116
|
+
when '\\'
|
117
|
+
case @lexer.getch
|
118
|
+
when '\\'
|
119
|
+
chars << '\\'
|
120
|
+
when '"'
|
121
|
+
chars << '"'
|
122
|
+
when "'"
|
123
|
+
chars << "'"
|
124
|
+
when 'b'
|
125
|
+
chars << "\b"
|
126
|
+
when 'n'
|
127
|
+
chars << "\n"
|
128
|
+
when 'r'
|
129
|
+
chars << "\r"
|
130
|
+
when 't'
|
131
|
+
chars << "\t"
|
132
|
+
when 'U'
|
133
|
+
if (hex = @lexer.scan /[0-9a-h]{4}/i)
|
134
|
+
chars << [hex].pack('U')
|
135
|
+
else
|
136
|
+
syntax_error "Expect 4 digit hex code"
|
137
|
+
end
|
138
|
+
else
|
139
|
+
if (oct = @lexer.scan /[0-7]{3}/)
|
140
|
+
chars << [oct.to_i(8)].pack('U')
|
141
|
+
else
|
142
|
+
syntax_error "Expect 3 digit oct code"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
else
|
146
|
+
chars << ch
|
147
|
+
end
|
148
|
+
end
|
149
|
+
chars.join
|
150
|
+
end
|
151
|
+
|
152
|
+
def parse_unquoted_string
|
153
|
+
@lexer.scan /[\w\.\/]+/
|
154
|
+
end
|
155
|
+
|
156
|
+
def parse_extension_value
|
157
|
+
@lexer.pos += 1
|
158
|
+
|
159
|
+
case @lexer.peek(2)
|
160
|
+
when '*D' # date
|
161
|
+
@lexer.pos += 2
|
162
|
+
if (d = @lexer.scan /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [+\-]\d{4}\>/)
|
163
|
+
Date.strptime d.chop, "%Y-%m-%d %H:%M:%S %z"
|
164
|
+
else
|
165
|
+
syntax_error "Expect date value"
|
166
|
+
end
|
167
|
+
|
168
|
+
when '*I' # integer
|
169
|
+
@lexer.pos += 2
|
170
|
+
if (i = @lexer.scan /[\+\-]?\d+\>/)
|
171
|
+
i.chop.to_i
|
172
|
+
else
|
173
|
+
syntax_error "Expect integer value"
|
174
|
+
end
|
175
|
+
|
176
|
+
when '*R' # real
|
177
|
+
@lexer.pos += 2
|
178
|
+
if (r = @lexer.scan /[\+\-]?\d+(\.\d+)?([eE][\+\-]?\d+)?\>/)
|
179
|
+
r.chop.to_f
|
180
|
+
else
|
181
|
+
syntax_error "Expect real value"
|
182
|
+
end
|
183
|
+
|
184
|
+
when '*B' # boolean
|
185
|
+
@lexer.pos += 2
|
186
|
+
case @lexer.scan(/[YN]\>/)
|
187
|
+
when 'Y>'
|
188
|
+
true
|
189
|
+
when 'N>'
|
190
|
+
false
|
191
|
+
else
|
192
|
+
syntax_error "Expect boolean value"
|
193
|
+
end
|
194
|
+
|
195
|
+
else
|
196
|
+
parse_data
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def parse_data
|
201
|
+
if (h = @lexer.scan /[0-9a-f\s]*\>/i)
|
202
|
+
h = h.gsub /[\s\>]/, ''
|
203
|
+
data = [h].pack 'H*'
|
204
|
+
StringIO.new data
|
205
|
+
else
|
206
|
+
syntax_error "Expect hex value"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def syntax_error msg
|
211
|
+
pre = @lexer.string[0...@lexer.pos]
|
212
|
+
line = pre.count("\n") + 1
|
213
|
+
col = pre.size - pre.rindex("\n")
|
214
|
+
raise SyntaxError, msg + " at line: #{line} col: #{col} #{@lexer.inspect}", caller
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|