property-list 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +14 -0
- data/README.md +73 -35
- data/Rakefile +3 -0
- data/gems.locked +16 -11
- data/gems.rb +6 -1
- data/lib/property-list/ascii_parser.rb +9 -6
- data/lib/property-list/binary_generator.rb +110 -144
- data/lib/property-list/binary_parser.rb +44 -34
- data/lib/property-list/version.rb +1 -1
- data/lib/property-list/xml_generator.rb +1 -5
- data/lib/property-list/xml_parser.rb +1 -2
- data/lib/property-list.rb +39 -8
- data/property-list.gemspec +15 -10
- metadata +8 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c5d4e0a084175a1ea51a31be189467896203dc9
|
4
|
+
data.tar.gz: 796a23fede74ba3c0bcf9bd767963c3d7630123e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8dab686da733d62fc7dc66215b8f2df1d101fd12d73b82e385b4795ca83bdc1979e388cab4e94e2a6e88f7b73abe997cf0f7fdf0159c0e3522ace940a8f197b8
|
7
|
+
data.tar.gz: 1781700ecee2756062c5d45f6308e1bd7d90fc2f48172b9b03a52c6bf7b00b32b51e59fcb5442ce457cafb1859d624b836a83610a58fdcbd8d9f20d860858614
|
data/.travis.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 2.0.0-p648 # macOS
|
5
|
+
- 2.1.10
|
6
|
+
- 2.2.7
|
7
|
+
- 2.3.4
|
8
|
+
- 2.4.1
|
9
|
+
- ruby-head
|
10
|
+
- jruby-head
|
11
|
+
- rbx-3.72
|
12
|
+
before_install: gem install bundler -v '~> 1.15' --conservative
|
13
|
+
install: bundle install
|
14
|
+
script: CI_COVERAGE=1 bundle exec rake
|
data/README.md
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
[](https://travis-ci.org/luikore/property-list)
|
2
|
+
[](https://badge.fury.io/rb/property-list)
|
3
|
+
[](https://coveralls.io/github/luikore/property-list?branch=master)
|
4
|
+
|
5
|
+
Fully featured plist library.
|
6
|
+
Can load and dump XML/ASCII/Binary/SMIME propertylist and offer fine-grained formatting options.
|
3
7
|
Cross platform, clean code, performance tuned, no dependency.
|
4
8
|
|
5
9
|
## Install
|
6
10
|
|
7
|
-
Requires Ruby
|
11
|
+
Requires Ruby 2.0+
|
8
12
|
|
9
13
|
gem ins property-list
|
10
14
|
|
@@ -23,9 +27,9 @@ Generate a plist file data
|
|
23
27
|
PropertyList.dump_binary obj
|
24
28
|
PropertyList.dump_ascii obj
|
25
29
|
|
26
|
-
XML formatting options for `PropertyList.dump_xml object, options`
|
30
|
+
**XML formatting** options for `PropertyList.dump_xml object, options`
|
27
31
|
|
28
|
-
- `segment:` whether output an XML segment (not wrapped with `<?xml
|
32
|
+
- `segment:` whether output an XML segment (not wrapped with `<?xml>, <DOCTYPE>, <plist>` tags), default is `false`.
|
29
33
|
- `xml_version:` you can also specify `"1.1"` for https://www.w3.org/TR/xml11/, default is `"1.0"`, no effect if `:segment` is set to `true`.
|
30
34
|
- `gnu_dtd:` use GNUStep DTD instead (which is a bit different in string escaping), default is `false`.
|
31
35
|
- `indent_unit:` the indent unit, default value is `"\t"`, set to or `''` if you don't need indent.
|
@@ -33,44 +37,78 @@ XML formatting options for `PropertyList.dump_xml object, options`
|
|
33
37
|
- `base64_width:` the width of characters per line when serializing data with Base64, default value is `68`, must be multiple of `4`.
|
34
38
|
- `base64_indent:` whether indent the Base64 encoded data, you can use `false` for compatibility to generate same output for other frameworks, default value is `true`.
|
35
39
|
|
36
|
-
ASCII formatting options for `PropertyList.dump_ascii object, options`
|
40
|
+
**ASCII formatting** options for `PropertyList.dump_ascii object, options`
|
37
41
|
|
38
42
|
- `indent_unit:` the indent unit, default value is `"\t"`, set to `''` if you don't need indent.
|
39
43
|
- `initial_indent:` initial indent space, default is `''`, the indentation per line equals to `initial_indent + indent * current_indent_level`.
|
40
44
|
- `wrap:` wrap the top level output with `{...}` when obj is a Hash, default is `true`.
|
41
45
|
- `encoding_comment:` add encoding comment `// !$*UTF8*$!` on top of file, default is `false`.
|
42
46
|
- `sort_keys:` sort dict keys, default is `true`.
|
43
|
-
- `gnu_extension
|
47
|
+
- `gnu_extension:` whether allow GNUStep extensions for ASCII plist to support serializing more types, default is `true`.
|
44
48
|
|
45
49
|
## Data type mapping
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
PropertyList::
|
71
|
-
|
72
|
-
|
73
|
-
|
51
|
+
When loading, plist data types will be mapped to native Ruby types:
|
52
|
+
|
53
|
+
Plist type Ruby type
|
54
|
+
------------------------------
|
55
|
+
real Float
|
56
|
+
string String
|
57
|
+
unicode_string String
|
58
|
+
integer Integer
|
59
|
+
data StringIO
|
60
|
+
date DateTime
|
61
|
+
true TrueClass
|
62
|
+
false FalseClass
|
63
|
+
uid PropertyList::Uid [*1]
|
64
|
+
array Array
|
65
|
+
dict Hash
|
66
|
+
|
67
|
+
# binary plist v1x elements:
|
68
|
+
|
69
|
+
null NilClass
|
70
|
+
set Set
|
71
|
+
ordset PropertyList::OrdSet
|
72
|
+
uuid PropertyList::Uuid
|
73
|
+
url_base PropertyList::Url [*2]
|
74
|
+
url_no_base PropertyList::Url
|
75
|
+
|
76
|
+
Notes:
|
77
|
+
|
78
|
+
- \[\*1] **uid** is only available in binary plist, `PropertyList::Uid#uid` is the integer index.
|
79
|
+
- \[\*2] **url_base** means URL with base.
|
80
|
+
|
81
|
+
When dumping, native Ruby types will be mapped to plist data types:
|
82
|
+
|
83
|
+
Ruby type Plist type
|
84
|
+
-----------------------------------
|
85
|
+
Float real
|
86
|
+
String string, unicode_string
|
87
|
+
Symbol string, unicode_string
|
88
|
+
Integer integer
|
89
|
+
StringIO data
|
90
|
+
IO data
|
91
|
+
Time date
|
92
|
+
DateTime date
|
93
|
+
Date date
|
94
|
+
true true
|
95
|
+
false false
|
96
|
+
PropertyList::Uid uid
|
97
|
+
Dict dict
|
98
|
+
Array array
|
99
|
+
Set set
|
100
|
+
|
101
|
+
# binary plist v1x elements:
|
102
|
+
|
103
|
+
NilClass null
|
104
|
+
Set set
|
105
|
+
PropertyList::OrdSet ordset
|
106
|
+
PropertyList::Uuid uuid
|
107
|
+
PropertyList::Url url_base, url_no_base
|
108
|
+
|
109
|
+
Notes:
|
110
|
+
|
111
|
+
- `PropertyList::Uid` and `Set` can only be serialized in binary plist.
|
74
112
|
|
75
113
|
Type mappings in ASCII plist depends on the DTD.
|
76
114
|
|
@@ -81,4 +119,4 @@ The binary generating code is modified from https://github.com/jarib/plist with
|
|
81
119
|
## Alternative plist libraries for Ruby
|
82
120
|
|
83
121
|
- [plist](https://github.com/patsplat/plist): only deals with XML plist, and generates wrong plist when there is handle line endings in strings.
|
84
|
-
- [CFPropertyList](https://github.com/ckruse/CFPropertyList): also deals with XML/Binary/ASCII plist files, more
|
122
|
+
- [CFPropertyList](https://github.com/ckruse/CFPropertyList): also deals with XML/Binary/ASCII plist files, but not v1x binary plist, more hard-to-use API and more thourough tests.
|
data/Rakefile
CHANGED
data/gems.locked
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
property-list (1.0)
|
5
|
-
|
6
1
|
GEM
|
7
2
|
remote: https://rubygems.org/
|
8
3
|
specs:
|
9
4
|
coderay (1.1.1)
|
5
|
+
coveralls (0.8.21)
|
6
|
+
json (>= 1.8, < 3)
|
7
|
+
simplecov (~> 0.14.1)
|
8
|
+
term-ansicolor (~> 1.3)
|
9
|
+
thor (~> 0.19.4)
|
10
|
+
tins (~> 1.6)
|
10
11
|
docile (1.1.5)
|
11
12
|
hoe (3.16.1)
|
12
13
|
rake (>= 0.8, < 13.0)
|
@@ -23,19 +24,23 @@ GEM
|
|
23
24
|
simplecov-html (~> 0.10.0)
|
24
25
|
simplecov-html (0.10.2)
|
25
26
|
slop (3.6.0)
|
27
|
+
term-ansicolor (1.6.0)
|
28
|
+
tins (~> 1.0)
|
26
29
|
test-unit (1.2.3)
|
27
30
|
hoe (>= 1.5.1)
|
31
|
+
thor (0.19.4)
|
32
|
+
tins (1.15.0)
|
28
33
|
|
29
34
|
PLATFORMS
|
30
35
|
ruby
|
31
36
|
|
32
37
|
DEPENDENCIES
|
33
|
-
bundler (~> 1.15
|
34
|
-
|
35
|
-
pry (~> 0.10
|
36
|
-
rake (~> 12.0
|
37
|
-
simplecov (~> 0.14
|
38
|
-
test-unit (~> 1.2
|
38
|
+
bundler (~> 1.15)
|
39
|
+
coveralls
|
40
|
+
pry (~> 0.10)
|
41
|
+
rake (~> 12.0)
|
42
|
+
simplecov (~> 0.14)
|
43
|
+
test-unit (~> 1.2)
|
39
44
|
|
40
45
|
BUNDLED WITH
|
41
46
|
1.15.4
|
data/gems.rb
CHANGED
@@ -130,18 +130,21 @@ module PropertyList
|
|
130
130
|
chars << "\r"
|
131
131
|
when 't'
|
132
132
|
chars << "\t"
|
133
|
-
when 'U'
|
133
|
+
when 'U', 'u'
|
134
134
|
if (hex = @lexer.scan /[0-9a-h]{4}/i)
|
135
|
-
chars << [hex].pack('U')
|
135
|
+
chars << [hex.to_i(16)].pack('U')
|
136
136
|
else
|
137
137
|
syntax_error "Expect 4 digit hex code"
|
138
138
|
end
|
139
|
-
|
140
|
-
|
141
|
-
|
139
|
+
when /(\d)/
|
140
|
+
oct_init = $1
|
141
|
+
if (oct = @lexer.scan /[0-7]{2}/)
|
142
|
+
chars << [(oct_init + oct).to_i(8)].pack('U')
|
142
143
|
else
|
143
144
|
syntax_error "Expect 3 digit oct code"
|
144
145
|
end
|
146
|
+
else
|
147
|
+
syntax_error "Bad escape"
|
145
148
|
end
|
146
149
|
else
|
147
150
|
chars << ch
|
@@ -211,7 +214,7 @@ module PropertyList
|
|
211
214
|
def syntax_error msg
|
212
215
|
pre = @lexer.string[0...@lexer.pos]
|
213
216
|
line = pre.count("\n") + 1
|
214
|
-
col = pre.size - pre.rindex("\n")
|
217
|
+
col = pre.size - (pre.rindex("\n") || -1)
|
215
218
|
raise SyntaxError, msg + " at line: #{line} col: #{col} #{@lexer.inspect}", caller
|
216
219
|
end
|
217
220
|
end
|
@@ -5,6 +5,7 @@ module PropertyList
|
|
5
5
|
def self.dump_binary obj, options=nil
|
6
6
|
generator = BinaryGenerator.new options
|
7
7
|
generator.generate obj
|
8
|
+
binding.pry if $test
|
8
9
|
generator.output.join
|
9
10
|
end
|
10
11
|
|
@@ -15,109 +16,95 @@ module PropertyList
|
|
15
16
|
class BinaryGenerator #:nodoc:
|
16
17
|
include BinaryMarkers
|
17
18
|
|
19
|
+
Collection = Struct.new :marker, :size, :refs
|
20
|
+
|
21
|
+
V0_MAGIC = "bplist00".force_encoding('binary').freeze
|
22
|
+
V1_MAGIC = "bplist10".force_encoding('binary').freeze
|
23
|
+
TAIL_PADDING = "\0\0\0\0\0\0".force_encoding('binary').freeze
|
24
|
+
|
18
25
|
def initialize opts
|
19
26
|
@output = []
|
20
27
|
@offset = 0
|
28
|
+
@objs = []
|
29
|
+
@ref_size = 0
|
21
30
|
end
|
22
31
|
attr_reader :output
|
23
32
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
flatten_objects = flatten_collection object
|
28
|
-
ref_byte_size = min_byte_size flatten_objects.size - 1
|
33
|
+
def generate obj
|
34
|
+
flatten obj
|
35
|
+
ref_byte_size = min_byte_size @ref_size - 1
|
29
36
|
|
30
|
-
|
31
|
-
# TODO use bplist10 when there are version 1x elements
|
32
|
-
add_output "bplist00"
|
37
|
+
add_output V0_MAGIC
|
33
38
|
offset_table = []
|
34
|
-
|
39
|
+
@objs.each do |o|
|
35
40
|
offset_table << @offset
|
36
41
|
binary_object o, ref_byte_size
|
37
42
|
end
|
38
43
|
|
39
|
-
|
44
|
+
if @v1
|
45
|
+
@output[0] = V1_MAGIC
|
46
|
+
end
|
47
|
+
|
40
48
|
offset_table_addr = @offset
|
41
49
|
offset_byte_size = min_byte_size @offset
|
42
50
|
offset_table.each do |offset|
|
43
51
|
binary_integer offset, offset_byte_size
|
44
52
|
end
|
45
53
|
|
46
|
-
|
47
|
-
|
48
|
-
"\0\0\0\0\0\0", # padding
|
54
|
+
add_output pack([
|
55
|
+
TAIL_PADDING,
|
49
56
|
offset_byte_size, ref_byte_size,
|
50
|
-
|
57
|
+
@ref_size,
|
51
58
|
0, # index of root object
|
52
59
|
offset_table_addr
|
53
|
-
]
|
60
|
+
], "a*C2Q>3")
|
54
61
|
end
|
55
62
|
|
56
|
-
|
63
|
+
def flatten obj
|
64
|
+
@ref_size += 1
|
57
65
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
#
|
66
|
-
# ary = [:a, :b, :c]
|
67
|
-
# flatten_collection(ary) # => [[1, 2, 3], :a, :b, :c]
|
68
|
-
#
|
69
|
-
# In the case of a Hash, keys and values are both appended to the one-
|
70
|
-
# dimensional array and then replaced with indexes.
|
71
|
-
#
|
72
|
-
# hsh = {:a => "blue", :b => "purple", :c => "green"}
|
73
|
-
# flatten_collection(hsh)
|
74
|
-
# # => [{1 => 2, 3 => 4, 5 => 6}, :a, "blue", :b, "purple", :c, "green"]
|
75
|
-
#
|
76
|
-
# An object will never be added to the one-dimensional array twice. If a
|
77
|
-
# collection refers to an object more than once, the object will be added
|
78
|
-
# to the one-dimensional array only once.
|
79
|
-
#
|
80
|
-
# ary = [:a, :a, :a]
|
81
|
-
# flatten_collection(ary) # => [[1, 1, 1], :a]
|
82
|
-
#
|
83
|
-
# The +obj_list+ and +id_refs+ parameters are private; they're used for
|
84
|
-
# descending into sub-collections recursively.
|
85
|
-
def flatten_collection collection, obj_list=[], id_refs={}
|
86
|
-
case collection
|
87
|
-
when Array, Set
|
88
|
-
if id_refs[collection.object_id]
|
89
|
-
return obj_list[id_refs[collection.object_id]]
|
66
|
+
case obj
|
67
|
+
when Array
|
68
|
+
refs = []
|
69
|
+
@objs << Collection[MARKER_ARRAY, obj.size, refs]
|
70
|
+
obj.each do |e|
|
71
|
+
refs << @ref_size
|
72
|
+
flatten e
|
90
73
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
74
|
+
|
75
|
+
when Set
|
76
|
+
@v1 = true
|
77
|
+
refs = []
|
78
|
+
@objs << Collection[MARKER_SET, obj.size, refs]
|
79
|
+
obj.each do |e|
|
80
|
+
refs << @ref_size
|
81
|
+
flatten e
|
97
82
|
end
|
98
|
-
return obj_list
|
99
83
|
|
100
84
|
when Hash
|
101
|
-
|
102
|
-
|
85
|
+
refs = []
|
86
|
+
@objs << Collection[MARKER_DICT, obj.size, refs]
|
87
|
+
obj.each do |e, _|
|
88
|
+
refs << @ref_size
|
89
|
+
flatten e
|
103
90
|
end
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
collection.keys.sort.each do |key|
|
108
|
-
value = collection[key]
|
109
|
-
key = key.to_s if key.is_a?(Symbol)
|
110
|
-
flatten_collection(key, obj_list, id_refs)
|
111
|
-
flatten_collection(value, obj_list, id_refs)
|
112
|
-
obj_refs[id_refs[key.object_id]] = id_refs[value.object_id]
|
91
|
+
obj.each do |_, e|
|
92
|
+
refs << @ref_size
|
93
|
+
flatten e
|
113
94
|
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
95
|
+
|
96
|
+
when OrdSet
|
97
|
+
@v1 = true
|
98
|
+
refs = []
|
99
|
+
elements = obj.elements
|
100
|
+
@objs << Collection[MARKER_ORD_SET, elements.size, refs]
|
101
|
+
elements.each do |e|
|
102
|
+
refs << @ref_size
|
103
|
+
flatten e
|
119
104
|
end
|
120
|
-
|
105
|
+
|
106
|
+
else
|
107
|
+
@objs << obj
|
121
108
|
end
|
122
109
|
end
|
123
110
|
|
@@ -126,92 +113,72 @@ module PropertyList
|
|
126
113
|
@offset += data.bytesize
|
127
114
|
end
|
128
115
|
|
129
|
-
# Returns a binary property list fragment that represents +obj+. The
|
130
|
-
# returned string is not a complete property list, just a fragment that
|
131
|
-
# describes +obj+, and is not useful without a header, offset table, and
|
132
|
-
# trailer.
|
133
|
-
#
|
134
|
-
# The following classes are recognized: String, Float, Integer, the Boolean
|
135
|
-
# classes, Time, IO, StringIO, Array, Set, and Hash. IO and StringIO
|
136
|
-
# objects are rewound, read, and the contents stored as data (i.e., Cocoa
|
137
|
-
# applications will decode them as NSData). All other classes are dumped
|
138
|
-
# with Marshal and stored as data.
|
139
|
-
#
|
140
|
-
# Note that subclasses of the supported classes will be encoded as though
|
141
|
-
# they were the supported superclass. Thus, a subclass of (for example)
|
142
|
-
# String will be encoded and decoded as a String, not as the subclass:
|
143
|
-
#
|
144
|
-
# class ExampleString < String
|
145
|
-
# ...
|
146
|
-
# end
|
147
|
-
#
|
148
|
-
# s = ExampleString.new("disquieting plantlike mystery")
|
149
|
-
# encoded_s = binary_object(s)
|
150
|
-
# decoded_s = decode_binary_object(encoded_s)
|
151
|
-
# puts decoded_s.class # => String
|
152
|
-
#
|
153
|
-
# +ref_byte_size+ is the number of bytes to use for storing references to
|
154
|
-
# other objects.
|
155
116
|
def binary_object obj, ref_byte_size = 4
|
156
117
|
case obj
|
157
118
|
when Symbol
|
158
119
|
binary_string obj.to_s
|
159
120
|
when String
|
160
121
|
binary_string obj
|
161
|
-
when URL
|
162
|
-
binary_url obj.url
|
163
122
|
when Float
|
164
|
-
add_output [(MARKER_REAL | 3), obj]
|
123
|
+
add_output pack([(MARKER_REAL | 3), obj], "CG")
|
165
124
|
when Integer
|
166
125
|
nbytes = min_byte_size obj
|
167
126
|
size_bits = { 1 => 0, 2 => 1, 4 => 2, 8 => 3, 16 => 4 }[nbytes]
|
168
|
-
add_output (MARKER_INT | size_bits)
|
127
|
+
add_output chr(MARKER_INT | size_bits)
|
169
128
|
binary_integer obj, nbytes
|
170
129
|
when TrueClass
|
171
|
-
add_output MARKER_TRUE
|
130
|
+
add_output chr(MARKER_TRUE)
|
172
131
|
when FalseClass
|
173
|
-
add_output MARKER_FALSE
|
132
|
+
add_output chr(MARKER_FALSE)
|
174
133
|
when Time
|
175
|
-
add_output [MARKER_DATE, obj.to_f - TIME_INTERVAL_SINCE_1970]
|
134
|
+
add_output pack([MARKER_DATE, obj.to_f - TIME_INTERVAL_SINCE_1970], "CG")
|
176
135
|
when Date # also covers DateTime
|
177
|
-
add_output [MARKER_DATE, obj.to_time.to_f - TIME_INTERVAL_SINCE_1970]
|
178
|
-
when
|
136
|
+
add_output pack([MARKER_DATE, obj.to_time.to_f - TIME_INTERVAL_SINCE_1970], "CG")
|
137
|
+
when StringIO
|
138
|
+
data = obj.string.force_encoding('binary') # NOTE StringIO.binmode doesn't work in <2.0.0 & rbx
|
139
|
+
binary_marker MARKER_DATA, data.bytesize
|
140
|
+
add_output data
|
141
|
+
when IO
|
179
142
|
obj.rewind
|
180
143
|
obj.binmode
|
181
144
|
data = obj.read
|
182
145
|
binary_marker MARKER_DATA, data.bytesize
|
183
146
|
add_output data
|
184
|
-
when
|
185
|
-
|
186
|
-
|
187
|
-
obj.each do |i|
|
188
|
-
binary_integer i, ref_byte_size
|
189
|
-
end
|
190
|
-
when Set
|
191
|
-
# Must be a set of object references as returned by flatten_collection.
|
192
|
-
binary_marker MARKER_SET, obj.size
|
193
|
-
obj.each do |i|
|
147
|
+
when Collection
|
148
|
+
binary_marker obj.marker, obj.size
|
149
|
+
obj.refs.each do |i|
|
194
150
|
binary_integer i, ref_byte_size
|
195
151
|
end
|
196
|
-
when
|
197
|
-
#
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
152
|
+
when Uid
|
153
|
+
# Encoding is as integers, except values are unsigned.
|
154
|
+
nbytes = min_byte_size obj.uid
|
155
|
+
size_bits = { 1 => 0, 2 => 1, 4 => 2, 8 => 3, 16 => 4 }[nbytes]
|
156
|
+
add_output chr(MARKER_UID | size_bits)
|
157
|
+
binary_integer obj.uid, nbytes
|
158
|
+
|
159
|
+
## the following are v1x data types ##
|
160
|
+
|
161
|
+
when Uuid
|
162
|
+
@v1 = true
|
163
|
+
data = pack([obj.uuid], 'H*')
|
164
|
+
add_output chr(MARKER_UUID)
|
165
|
+
add_output data
|
166
|
+
when Url
|
167
|
+
@v1 = true
|
168
|
+
binary_url obj
|
169
|
+
when NilClass
|
170
|
+
@v1 = true
|
171
|
+
add_output chr(MARKER_NULL)
|
205
172
|
else
|
206
|
-
raise
|
173
|
+
raise UnsupportedTypeError, obj.class.to_s
|
207
174
|
end
|
208
175
|
end
|
209
176
|
|
210
177
|
def binary_marker marker, size
|
211
178
|
if size < 15
|
212
|
-
add_output (marker | size)
|
179
|
+
add_output chr(marker | size)
|
213
180
|
else
|
214
|
-
add_output (marker | 0xf)
|
181
|
+
add_output chr(marker | 0xf)
|
215
182
|
binary_object size
|
216
183
|
end
|
217
184
|
end
|
@@ -233,22 +200,21 @@ module PropertyList
|
|
233
200
|
end
|
234
201
|
|
235
202
|
def binary_url obj
|
236
|
-
|
237
|
-
|
238
|
-
add_output MARKER_WITH_BASE_URL.chr
|
203
|
+
if obj.url =~ /\A\w+:\/\//
|
204
|
+
add_output chr(MARKER_WITH_BASE_URL)
|
239
205
|
else
|
240
|
-
add_output MARKER_NO_BASE_URL
|
206
|
+
add_output chr(MARKER_NO_BASE_URL)
|
241
207
|
end
|
242
|
-
binary_marker MARKER_ASCII_STRING, obj.bytesize
|
243
|
-
add_output obj
|
208
|
+
binary_marker MARKER_ASCII_STRING, obj.url.bytesize
|
209
|
+
add_output obj.url
|
244
210
|
end
|
245
211
|
|
246
|
-
def
|
247
|
-
|
212
|
+
def chr c
|
213
|
+
c.chr.force_encoding 'binary'
|
248
214
|
end
|
249
215
|
|
250
|
-
def
|
251
|
-
|
216
|
+
def pack objs, format
|
217
|
+
objs.pack(format).force_encoding 'binary'
|
252
218
|
end
|
253
219
|
|
254
220
|
# Packs an integer +i+ into its binary representation in the specified
|
@@ -260,19 +226,19 @@ module PropertyList
|
|
260
226
|
end
|
261
227
|
case num_bytes
|
262
228
|
when 1
|
263
|
-
add_output [i]
|
229
|
+
add_output pack([i], "C")
|
264
230
|
when 2
|
265
|
-
add_output [i]
|
231
|
+
add_output pack([i], "n")
|
266
232
|
when 4
|
267
|
-
add_output [i]
|
233
|
+
add_output pack([i], "N")
|
268
234
|
when 8
|
269
|
-
add_output [i]
|
235
|
+
add_output pack([i], "q>")
|
270
236
|
when 16
|
271
237
|
# TODO verify 128 bit integer encoding
|
272
238
|
if i < 0
|
273
239
|
i = 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff ^ i.abs + 1
|
274
240
|
end
|
275
|
-
add_output [i >> 64, i & 0xffff_ffff_ffff_ffff]
|
241
|
+
add_output pack([i >> 64, i & 0xffff_ffff_ffff_ffff], "q>2")
|
276
242
|
else
|
277
243
|
raise ArgumentError, "num_bytes must be 1, 2, 4, 8, or 16"
|
278
244
|
end
|
@@ -24,36 +24,26 @@ module PropertyList
|
|
24
24
|
private
|
25
25
|
|
26
26
|
def decode_object offset
|
27
|
-
first_byte, = @src.unpack "@#{offset}C"
|
27
|
+
first_byte, _ = @src.unpack "@#{offset}C"
|
28
28
|
marker = first_byte & 0xF0
|
29
29
|
if marker == 0 or first_byte == MARKER_DATE
|
30
30
|
marker = first_byte
|
31
31
|
end
|
32
32
|
|
33
33
|
case marker
|
34
|
-
when MARKER_NULL
|
35
|
-
nil
|
36
34
|
when MARKER_FALSE
|
37
35
|
false
|
38
36
|
when MARKER_TRUE
|
39
37
|
true
|
40
|
-
when MARKER_NO_BASE_URL
|
41
|
-
raise 'todo'
|
42
|
-
when MARKER_WITH_BASE_URL
|
43
|
-
raise 'todo'
|
44
|
-
when MARKER_UUID
|
45
|
-
raise 'todo'
|
46
|
-
when MARKER_FILL
|
47
|
-
decode_object offest + 1
|
48
38
|
when MARKER_INT
|
49
39
|
size_bits = first_byte & 0x0F
|
50
40
|
num_bytes = 2 ** size_bits
|
51
41
|
decode_integer offset + 1, num_bytes
|
52
42
|
when MARKER_REAL
|
53
|
-
r, = @src.unpack "@#{offset + 1}G"
|
43
|
+
r, _ = @src.unpack "@#{offset + 1}G"
|
54
44
|
r
|
55
45
|
when MARKER_DATE
|
56
|
-
seconds_since_2001, = @src.unpack "@#{offset + 1}G"
|
46
|
+
seconds_since_2001, _ = @src.unpack "@#{offset + 1}G"
|
57
47
|
Time.at(TIME_INTERVAL_SINCE_1970 + seconds_since_2001).to_datetime
|
58
48
|
when MARKER_DATA
|
59
49
|
data = @src.byteslice *(decode_vl_info offset)
|
@@ -64,13 +54,7 @@ module PropertyList
|
|
64
54
|
str_offset, str_size = decode_vl_info offset
|
65
55
|
s = @src.byteslice str_offset, str_size * 2
|
66
56
|
s.force_encoding('utf-16be').encode 'utf-8'
|
67
|
-
when MARKER_UTF8_STRING
|
68
|
-
s = @src.byteslice *(decode_vl_info offset)
|
69
|
-
s.force_encoding 'utf-8'
|
70
57
|
when MARKER_UID
|
71
|
-
# Encoding is as integers, except values are unsigned.
|
72
|
-
# These are used extensively in files written using NSKeyedArchiver, a serializer for Objective-C objects.
|
73
|
-
# The value is the index in parse_result["$objects"]
|
74
58
|
size = (first_byte & 0xF) + 1
|
75
59
|
bytes = @src.byteslice offset + 1, size
|
76
60
|
res = 0
|
@@ -78,21 +62,13 @@ module PropertyList
|
|
78
62
|
res *= 256
|
79
63
|
res += byte
|
80
64
|
end
|
81
|
-
|
65
|
+
Uid.new res
|
82
66
|
when MARKER_ARRAY
|
83
67
|
offset, size = decode_vl_info offset
|
84
68
|
size.times.map do |i|
|
85
69
|
id = decode_ref_id offset + i * @ref_byte_size
|
86
70
|
decode_id id
|
87
71
|
end
|
88
|
-
when MARKER_ORD_SET, MARKER_SET
|
89
|
-
r = Set.new
|
90
|
-
offset, size = decode_vl_info offset
|
91
|
-
size.times do |i|
|
92
|
-
id = decode_ref_id offset + i * @ref_byte_size
|
93
|
-
r << (decode_id id)
|
94
|
-
end
|
95
|
-
r
|
96
72
|
when MARKER_DICT
|
97
73
|
offset, size = decode_vl_info offset
|
98
74
|
keys_byte_size = @ref_byte_size * size
|
@@ -107,18 +83,52 @@ module PropertyList
|
|
107
83
|
end
|
108
84
|
entries.sort_by! &:first
|
109
85
|
Hash[entries]
|
86
|
+
|
87
|
+
## the following are v1x data types ##
|
88
|
+
|
89
|
+
when MARKER_UTF8_STRING
|
90
|
+
s = @src.byteslice *(decode_vl_info offset)
|
91
|
+
s.force_encoding 'utf-8'
|
92
|
+
when MARKER_ORD_SET
|
93
|
+
r = []
|
94
|
+
offset, size = decode_vl_info offset
|
95
|
+
size.times do |i|
|
96
|
+
id = decode_ref_id offset + i * @ref_byte_size
|
97
|
+
r << (decode_id id)
|
98
|
+
end
|
99
|
+
OrdSet.new r
|
100
|
+
when MARKER_SET
|
101
|
+
r = Set.new
|
102
|
+
offset, size = decode_vl_info offset
|
103
|
+
size.times do |i|
|
104
|
+
id = decode_ref_id offset + i * @ref_byte_size
|
105
|
+
r << (decode_id id)
|
106
|
+
end
|
107
|
+
r
|
108
|
+
when MARKER_NULL
|
109
|
+
nil
|
110
|
+
when MARKER_NO_BASE_URL, MARKER_WITH_BASE_URL
|
111
|
+
# TODO check with real v1x plist
|
112
|
+
url = decode_object offset + 1
|
113
|
+
Url.new url
|
114
|
+
when MARKER_UUID
|
115
|
+
bytes = @src.byteslice offset + 1, 16
|
116
|
+
hex, _ = bytes.unpack 'H*'
|
117
|
+
Uuid.new hex.upcase
|
118
|
+
when MARKER_FILL
|
119
|
+
decode_object offest + 1
|
110
120
|
else
|
111
121
|
raise "unused marker: 0b#{marker.to_s(2).rjust 8, '0'}"
|
112
122
|
end
|
113
123
|
end
|
114
124
|
|
115
125
|
def decode_vl_info offset
|
116
|
-
marker, = @src.unpack "@#{offset}C"
|
126
|
+
marker, _ = @src.unpack "@#{offset}C"
|
117
127
|
vl_size_bits = marker & 0x0F
|
118
128
|
|
119
129
|
if vl_size_bits == 0x0F
|
120
130
|
# size is followed by marker int
|
121
|
-
int_marker, = @src.unpack "@#{offset + 1}C"
|
131
|
+
int_marker, _ = @src.unpack "@#{offset + 1}C"
|
122
132
|
num_bytes = 2 ** (int_marker & 0x0F)
|
123
133
|
size = decode_integer offset + 2, num_bytes
|
124
134
|
[offset + 2 + num_bytes, size]
|
@@ -151,13 +161,13 @@ module PropertyList
|
|
151
161
|
# NOTE: only num_bytes = 8 or 16 it can be negative
|
152
162
|
case num_bytes
|
153
163
|
when 1
|
154
|
-
i, = @src.unpack "@#{offset}C"
|
164
|
+
i, _ = @src.unpack "@#{offset}C"
|
155
165
|
when 2
|
156
|
-
i, = @src.unpack "@#{offset}n"
|
166
|
+
i, _ = @src.unpack "@#{offset}n"
|
157
167
|
when 4
|
158
|
-
i, = @src.unpack "@#{offset}N"
|
168
|
+
i, _ = @src.unpack "@#{offset}N"
|
159
169
|
when 8
|
160
|
-
i, = @src.unpack "@#{offset}q>"
|
170
|
+
i, _ = @src.unpack "@#{offset}q>"
|
161
171
|
when 16
|
162
172
|
hi, lo = @src.unpack "@#{offset}q>2"
|
163
173
|
i = (hi << 64) | lo
|
@@ -4,7 +4,7 @@ module PropertyList
|
|
4
4
|
#
|
5
5
|
# Options:
|
6
6
|
#
|
7
|
-
# - `segment:` whether output an XML segment (not wrapped with `<?xml
|
7
|
+
# - `segment:` whether output an XML segment (not wrapped with `<?xml>, <DOCTYPE>, <plist>` tags), default is `false`.
|
8
8
|
# - `xml_version:` you can also specify `"1.1"` for https://www.w3.org/TR/xml11/, default is `"1.0"`, no effect if `:segment` is set to `true`.
|
9
9
|
# - `gnu_dtd:` use GNUStep DTD instead (which is a bit different in string escaping), default is `false`.
|
10
10
|
# - `indent_unit:` the indent unit, default value is `"\t"`, set to or `''` if you don't need indent.
|
@@ -124,10 +124,6 @@ module PropertyList
|
|
124
124
|
@output << "#@indent<data>\n#{base64}#@indent</data>\n"
|
125
125
|
end
|
126
126
|
|
127
|
-
def comment_tag content
|
128
|
-
@output << "#@indent<!-- #{content} -->\n"
|
129
|
-
end
|
130
|
-
|
131
127
|
def tag name, contents=nil
|
132
128
|
if block_given?
|
133
129
|
@output << "#@indent<#{name}>\n"
|
@@ -22,7 +22,6 @@ module PropertyList
|
|
22
22
|
# TODO xml_encoding = xml_declaration.match(/(?:\A|\s)encoding=(?:"(.*?)"|'(.*?)')(?:\s|\Z)/)
|
23
23
|
|
24
24
|
skip_space_and_comments
|
25
|
-
# TODO doctype should co-exist with xml
|
26
25
|
@lexer.skip(/\s*<!DOCTYPE\s+.*?>/m)
|
27
26
|
skip_space_and_comments
|
28
27
|
|
@@ -115,7 +114,7 @@ module PropertyList
|
|
115
114
|
def syntax_error msg
|
116
115
|
pre = @lexer.string[0...@lexer.pos]
|
117
116
|
line = pre.count("\n") + 1
|
118
|
-
col = pre.size - pre.rindex("\n")
|
117
|
+
col = pre.size - (pre.rindex("\n") || -1)
|
119
118
|
raise SyntaxError, msg + " at line: #{line} col: #{col} #{@lexer.inspect}", caller
|
120
119
|
end
|
121
120
|
end
|
data/lib/property-list.rb
CHANGED
@@ -44,38 +44,69 @@ module PropertyList
|
|
44
44
|
|
45
45
|
# binary plist v0x elements:
|
46
46
|
|
47
|
-
|
47
|
+
# These are used extensively in files written using NSKeyedArchiver, a serializer for Objective-C objects.
|
48
|
+
# The value is the index in parse_result["$objects"]
|
49
|
+
#
|
50
|
+
# call-seq:
|
51
|
+
#
|
52
|
+
# PropertyList::Uid.new 34
|
53
|
+
class Uid
|
48
54
|
def initialize uid
|
49
55
|
@uid = uid
|
50
56
|
end
|
57
|
+
|
51
58
|
attr_reader :uid
|
59
|
+
|
60
|
+
def == other
|
61
|
+
other.is_a?(Uid) and @uid == other.uid
|
62
|
+
end
|
52
63
|
end
|
53
64
|
|
54
65
|
# binary plist v1x elements:
|
55
66
|
|
56
|
-
|
67
|
+
# call-seq:
|
68
|
+
#
|
69
|
+
# PropertyList::Url.new 'http://foo.com' # with base
|
70
|
+
# PropertyList::Url.new '/foo.com' # no base
|
71
|
+
class Url
|
57
72
|
def initialize url
|
58
73
|
@url = url
|
59
74
|
end
|
75
|
+
|
60
76
|
attr_reader :url
|
77
|
+
|
78
|
+
def == other
|
79
|
+
other.is_a?(Url) and @url == other.url
|
80
|
+
end
|
61
81
|
end
|
62
82
|
|
63
|
-
|
83
|
+
# call-seq:
|
84
|
+
#
|
85
|
+
# PropertyList::Uuid.new 'F' * 32
|
86
|
+
class Uuid
|
64
87
|
def initialize uuid
|
65
88
|
@uuid = uuid
|
66
89
|
end
|
90
|
+
|
67
91
|
attr_reader :uuid
|
92
|
+
|
93
|
+
def == other
|
94
|
+
other.is_a?(Uuid) and @uuid == other.uuid
|
95
|
+
end
|
68
96
|
end
|
69
97
|
|
70
|
-
|
98
|
+
# call-seq:
|
99
|
+
#
|
100
|
+
# PropertyList::OrdSet.new ['foo', 'bar', 3, 4]
|
101
|
+
class OrdSet
|
71
102
|
def initialize elements
|
72
103
|
@elements = elements.uniq
|
73
104
|
end
|
74
105
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
106
|
+
attr_reader :elements
|
107
|
+
|
108
|
+
def == other
|
109
|
+
other.is_a?(OrdSet) and @elements == other.elements
|
79
110
|
end
|
80
111
|
end
|
81
112
|
end
|
data/property-list.gemspec
CHANGED
@@ -1,24 +1,29 @@
|
|
1
1
|
require_relative 'lib/property-list/version'
|
2
2
|
|
3
|
+
class Array
|
4
|
+
if ![].respond_to?(:grep_v)
|
5
|
+
def grep_v reg
|
6
|
+
self - grep(reg)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
puts 'wa?'
|
3
12
|
Gem::Specification.new do |spec|
|
4
13
|
spec.name = "property-list"
|
5
14
|
spec.version = PropertyList::VERSION
|
6
|
-
|
15
|
+
puts 'gsd'
|
16
|
+
spec.author = "Luikore"
|
17
|
+
puts 'hmm'
|
7
18
|
spec.email = 'no@email'
|
8
19
|
|
9
|
-
spec.summary = "Property
|
10
|
-
spec.description = "Fully featured
|
11
|
-
Can load and dump XML/ASCII/Binary
|
20
|
+
spec.summary = "Property list (plist) library with all formats support"
|
21
|
+
spec.description = "Fully featured property_plist library.
|
22
|
+
Can load and dump XML/ASCII/Binary/SMIME propertyplist and offer fine-grained formatting options.
|
12
23
|
Cross platform, clean code, performance tuned, no dependency."
|
13
24
|
spec.homepage = "https://github.com/luikore/property-list"
|
14
25
|
spec.license = "BSD-3-Clause"
|
15
26
|
|
16
27
|
spec.files = `git ls-files -z`.split("\x0").grep_v %r{^(test|spec|features)/}
|
17
28
|
spec.require_paths = ["lib"]
|
18
|
-
|
19
|
-
spec.add_development_dependency "bundler", "~> 1.15"
|
20
|
-
spec.add_development_dependency "rake", "~> 12.0"
|
21
|
-
spec.add_development_dependency "test-unit", "~> 1.2"
|
22
|
-
spec.add_development_dependency "pry", "~> 0.10"
|
23
|
-
spec.add_development_dependency "simplecov", "~> 0.14"
|
24
29
|
end
|
metadata
CHANGED
@@ -1,88 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: property-list
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luikore
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-09-
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.15'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.15'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '12.0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '12.0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: test-unit
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1.2'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1.2'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: pry
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0.10'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0.10'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: simplecov
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0.14'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0.14'
|
11
|
+
date: 2017-09-15 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
83
13
|
description: |-
|
84
|
-
Fully featured
|
85
|
-
Can load and dump XML/ASCII/Binary
|
14
|
+
Fully featured property_plist library.
|
15
|
+
Can load and dump XML/ASCII/Binary/SMIME propertyplist and offer fine-grained formatting options.
|
86
16
|
Cross platform, clean code, performance tuned, no dependency.
|
87
17
|
email: no@email
|
88
18
|
executables: []
|
@@ -91,6 +21,7 @@ extra_rdoc_files: []
|
|
91
21
|
files:
|
92
22
|
- ".gitignore"
|
93
23
|
- ".rdoc_options"
|
24
|
+
- ".travis.yml"
|
94
25
|
- LICENSE
|
95
26
|
- README.md
|
96
27
|
- Rakefile
|
@@ -126,8 +57,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
57
|
version: '0'
|
127
58
|
requirements: []
|
128
59
|
rubyforge_project:
|
129
|
-
rubygems_version: 2.6.
|
60
|
+
rubygems_version: 2.6.13
|
130
61
|
signing_key:
|
131
62
|
specification_version: 4
|
132
|
-
summary: Property
|
63
|
+
summary: Property list (plist) library with all formats support
|
133
64
|
test_files: []
|