CFPropertyList 2.0.7
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.
- data/README +34 -0
- data/lib/rbBinaryCFPropertyList.rb +669 -0
- data/lib/rbCFPlistError.rb +19 -0
- data/lib/rbCFPropertyList.rb +316 -0
- data/lib/rbCFTypes.rb +233 -0
- data/lib/rbXMLCFPropertyList.rb +122 -0
- metadata +79 -0
data/README
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
CFPropertyList implementation
|
2
|
+
class to read, manipulate and write both XML and binary property list
|
3
|
+
files (plist(5)) as defined by Apple
|
4
|
+
|
5
|
+
== Example
|
6
|
+
|
7
|
+
# create a arbitrary data structure of basic data types
|
8
|
+
data = {
|
9
|
+
'name' => 'John Doe',
|
10
|
+
'missing' => true,
|
11
|
+
'last_seen' => Time.now,
|
12
|
+
'friends' => ['Jane Doe','Julian Doe'],
|
13
|
+
'likes' => {
|
14
|
+
'me' => false
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
# create CFPropertyList::List object
|
19
|
+
plist = CFPropertyList::List.new
|
20
|
+
|
21
|
+
# call CFPropertyList.guess() to create corresponding CFType values
|
22
|
+
plist.value = CFPropertyList.guess(data)
|
23
|
+
|
24
|
+
# write plist to file
|
25
|
+
plist.save("example.plist", CFPropertyList::List::FORMAT_BINARY)
|
26
|
+
|
27
|
+
# … later, read it again
|
28
|
+
plist = CFPropertyList::List.new("example.plist")
|
29
|
+
data = CFPropertyList.native_types(plist.value)
|
30
|
+
|
31
|
+
Author:: Christian Kruse (mailto:cjk@wwwtech.de)
|
32
|
+
Copyright:: Copyright (c) 2010
|
33
|
+
License:: Distributes under the same terms as Ruby
|
34
|
+
|
@@ -0,0 +1,669 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# CFPropertyList implementation
|
4
|
+
# parser class to read, manipulate and write binary property list files (plist(5)) as defined by Apple
|
5
|
+
#
|
6
|
+
# Author:: Christian Kruse (mailto:cjk@wwwtech.de)
|
7
|
+
# Copyright:: Copyright (c) 2010
|
8
|
+
# License:: Distributes under the same terms as Ruby
|
9
|
+
|
10
|
+
module CFPropertyList
|
11
|
+
class Binary
|
12
|
+
# Read a binary plist file
|
13
|
+
def load(opts)
|
14
|
+
@unique_table = {}
|
15
|
+
@count_objects = 0
|
16
|
+
@string_size = 0
|
17
|
+
@int_size = 0
|
18
|
+
@misc_size = 0
|
19
|
+
@object_refs = 0
|
20
|
+
|
21
|
+
@written_object_count = 0
|
22
|
+
@object_table = []
|
23
|
+
@object_ref_size = 0
|
24
|
+
|
25
|
+
@offsets = []
|
26
|
+
|
27
|
+
fd = nil
|
28
|
+
if(opts.has_key?(:file)) then
|
29
|
+
fd = File.open(opts[:file],"rb")
|
30
|
+
file = opts[:file]
|
31
|
+
else
|
32
|
+
fd = StringIO.new(opts[:data],"rb")
|
33
|
+
file = "<string>"
|
34
|
+
end
|
35
|
+
|
36
|
+
# first, we read the trailer: 32 byte from the end
|
37
|
+
fd.seek(-32,IO::SEEK_END)
|
38
|
+
buff = fd.read(32)
|
39
|
+
|
40
|
+
offset_size, object_ref_size, number_of_objects, top_object, table_offset = buff.unpack "x6CCx4Nx4Nx4N"
|
41
|
+
|
42
|
+
# after that, get the offset table
|
43
|
+
fd.seek(table_offset, IO::SEEK_SET)
|
44
|
+
coded_offset_table = fd.read(number_of_objects * offset_size)
|
45
|
+
raise CFFormatError.new("#{file}: Format error!") unless coded_offset_table.bytesize == number_of_objects * offset_size
|
46
|
+
|
47
|
+
@count_objects = number_of_objects
|
48
|
+
|
49
|
+
# decode offset table
|
50
|
+
formats = ["","C*","n*","(H6)*","N*"]
|
51
|
+
@offsets = coded_offset_table.unpack(formats[offset_size])
|
52
|
+
if(offset_size == 3) then
|
53
|
+
0.upto(@offsets.count-1) { |i| @offsets[i] = @offsets[i].to_i(16) }
|
54
|
+
end
|
55
|
+
|
56
|
+
@object_ref_size = object_ref_size
|
57
|
+
val = read_binary_object_at(file,fd,top_object)
|
58
|
+
|
59
|
+
fd.close
|
60
|
+
return val
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Convert CFPropertyList to binary format; since we have to count our objects we simply unique CFDictionary and CFArray
|
65
|
+
def to_str(opts={})
|
66
|
+
@unique_table = {}
|
67
|
+
@count_objects = 0
|
68
|
+
@string_size = 0
|
69
|
+
@int_size = 0
|
70
|
+
@misc_size = 0
|
71
|
+
@object_refs = 0
|
72
|
+
|
73
|
+
@written_object_count = 0
|
74
|
+
@object_table = []
|
75
|
+
@object_ref_size = 0
|
76
|
+
|
77
|
+
@offsets = []
|
78
|
+
|
79
|
+
binary_str = "bplist00"
|
80
|
+
unique_and_count_values(opts[:root])
|
81
|
+
|
82
|
+
@count_objects += @unique_table.count
|
83
|
+
@object_ref_size = Binary.bytes_needed(@count_objects)
|
84
|
+
|
85
|
+
file_size = @string_size + @int_size + @misc_size + @object_refs * @object_ref_size + 40
|
86
|
+
offset_size = Binary.bytes_needed(file_size)
|
87
|
+
table_offset = file_size - 32
|
88
|
+
|
89
|
+
@object_table = []
|
90
|
+
@written_object_count = 0
|
91
|
+
@unique_table = {} # we needed it to calculate several values, but now we need an empty table
|
92
|
+
|
93
|
+
opts[:root].to_binary(self)
|
94
|
+
|
95
|
+
object_offset = 8
|
96
|
+
offsets = []
|
97
|
+
|
98
|
+
0.upto(@object_table.count-1) do |i|
|
99
|
+
binary_str += @object_table[i]
|
100
|
+
offsets[i] = object_offset
|
101
|
+
object_offset += @object_table[i].bytesize
|
102
|
+
end
|
103
|
+
|
104
|
+
offsets.each do |offset|
|
105
|
+
binary_str += Binary.pack_it_with_size(offset_size,offset)
|
106
|
+
end
|
107
|
+
|
108
|
+
binary_str += [offset_size, @object_ref_size].pack("x6CC")
|
109
|
+
binary_str += [@count_objects].pack("x4N")
|
110
|
+
binary_str += [0].pack("x4N")
|
111
|
+
binary_str += [table_offset].pack("x4N")
|
112
|
+
|
113
|
+
return binary_str
|
114
|
+
end
|
115
|
+
|
116
|
+
# read a „null” type (i.e. null byte, marker byte, bool value)
|
117
|
+
def read_binary_null_type(length)
|
118
|
+
case length
|
119
|
+
when 0 then return 0 # null byte
|
120
|
+
when 8 then return CFBoolean.new(false)
|
121
|
+
when 9 then return CFBoolean.new(true)
|
122
|
+
when 15 then return 15 # fill type
|
123
|
+
end
|
124
|
+
|
125
|
+
raise CFFormatError.new("unknown null type: #{length}")
|
126
|
+
end
|
127
|
+
protected :read_binary_null_type
|
128
|
+
|
129
|
+
# read a binary int value
|
130
|
+
def read_binary_int(fname,fd,length)
|
131
|
+
raise CFFormatError.new("Integer greater than 8 bytes: #{length}") if length > 3
|
132
|
+
|
133
|
+
nbytes = 1 << length
|
134
|
+
|
135
|
+
val = nil
|
136
|
+
buff = fd.read(nbytes)
|
137
|
+
|
138
|
+
case length
|
139
|
+
when 0 then
|
140
|
+
val = buff.unpack("C")
|
141
|
+
val = val[0]
|
142
|
+
when 1 then
|
143
|
+
val = buff.unpack("n")
|
144
|
+
val = val[0]
|
145
|
+
when 2 then
|
146
|
+
val = buff.unpack("N")
|
147
|
+
val = val[0]
|
148
|
+
when 3
|
149
|
+
hiword,loword = buff.unpack("NN")
|
150
|
+
val = hiword << 32 | loword
|
151
|
+
end
|
152
|
+
|
153
|
+
return CFInteger.new(val);
|
154
|
+
end
|
155
|
+
protected :read_binary_int
|
156
|
+
|
157
|
+
# read a binary real value
|
158
|
+
def read_binary_real(fname,fd,length)
|
159
|
+
raise CFFormatError.new("Real greater than 8 bytes: #{length}") if length > 3
|
160
|
+
|
161
|
+
nbytes = 1 << length
|
162
|
+
val = nil
|
163
|
+
buff = fd.read(nbytes)
|
164
|
+
|
165
|
+
case length
|
166
|
+
when 0 then # 1 byte float? must be an error
|
167
|
+
raise CFFormatError.new("got #{length+1} byte float, must be an error!")
|
168
|
+
when 1 then # 2 byte float? must be an error
|
169
|
+
raise CFFormatError.new("got #{length+1} byte float, must be an error!")
|
170
|
+
when 2 then
|
171
|
+
val = buff.reverse.unpack("f")
|
172
|
+
val = val[0]
|
173
|
+
when 3 then
|
174
|
+
val = buff.reverse.unpack("d")
|
175
|
+
val = val[0]
|
176
|
+
end
|
177
|
+
|
178
|
+
return CFReal.new(val)
|
179
|
+
end
|
180
|
+
protected :read_binary_real
|
181
|
+
|
182
|
+
# read a binary date value
|
183
|
+
def read_binary_date(fname,fd,length)
|
184
|
+
raise CFFormatError.new("Date greater than 8 bytes: #{length}") if length > 3
|
185
|
+
|
186
|
+
nbytes = 1 << length
|
187
|
+
val = nil
|
188
|
+
buff = fd.read(nbytes)
|
189
|
+
|
190
|
+
case length
|
191
|
+
when 0 then # 1 byte CFDate is an error
|
192
|
+
raise CFFormatError.new("#{length+1} byte CFDate, error")
|
193
|
+
when 1 then # 2 byte CFDate is an error
|
194
|
+
raise CFFormatError.new("#{length+1} byte CFDate, error")
|
195
|
+
when 2 then
|
196
|
+
val = buff.reverse.unpack("f")
|
197
|
+
val = val[0]
|
198
|
+
when 3 then
|
199
|
+
val = buff.reverse.unpack("d")
|
200
|
+
val = val[0]
|
201
|
+
end
|
202
|
+
|
203
|
+
return CFDate.new(val,CFDate::TIMESTAMP_APPLE)
|
204
|
+
end
|
205
|
+
protected :read_binary_date
|
206
|
+
|
207
|
+
# Read a binary data value
|
208
|
+
def read_binary_data(fname,fd,length)
|
209
|
+
buff = "";
|
210
|
+
buff = fd.read(length) if length > 0
|
211
|
+
return CFData.new(buff,CFData::DATA_RAW)
|
212
|
+
end
|
213
|
+
protected :read_binary_data
|
214
|
+
|
215
|
+
# Read a binary string value
|
216
|
+
def read_binary_string(fname,fd,length)
|
217
|
+
buff = ""
|
218
|
+
buff = fd.read(length) if length > 0
|
219
|
+
|
220
|
+
@unique_table[buff] = true unless @unique_table.has_key?(buff)
|
221
|
+
return CFString.new(buff)
|
222
|
+
end
|
223
|
+
protected :read_binary_string
|
224
|
+
|
225
|
+
# Convert the given string from one charset to another
|
226
|
+
def Binary.charset_convert(str,from,to="UTF-8")
|
227
|
+
return str.clone.force_encoding(from).encode(to) if str.respond_to?("encode")
|
228
|
+
return Iconv.conv(to,from,str)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Count characters considering character set
|
232
|
+
def Binary.charset_strlen(str,charset="UTF-8")
|
233
|
+
return str.length if str.respond_to?("encode")
|
234
|
+
|
235
|
+
str = Iconv.conv("UTF-8",charset,str) if charset != "UTF-8"
|
236
|
+
return str.scan(/./mu).size
|
237
|
+
end
|
238
|
+
|
239
|
+
# Read a unicode string value, coded as UTF-16BE
|
240
|
+
def read_binary_unicode_string(fname,fd,length)
|
241
|
+
# The problem is: we get the length of the string IN CHARACTERS;
|
242
|
+
# since a char in UTF-16 can be 16 or 32 bit long, we don't really know
|
243
|
+
# how long the string is in bytes
|
244
|
+
buff = fd.read(2*length)
|
245
|
+
|
246
|
+
@unique_table[buff] = true unless @unique_table.has_key?(buff)
|
247
|
+
return CFString.new(Binary.charset_convert(buff,"UTF-16BE","UTF-8"))
|
248
|
+
end
|
249
|
+
protected :read_binary_unicode_string
|
250
|
+
|
251
|
+
# Read an binary array value, including contained objects
|
252
|
+
def read_binary_array(fname,fd,length)
|
253
|
+
ary = []
|
254
|
+
|
255
|
+
# first: read object refs
|
256
|
+
if(length != 0) then
|
257
|
+
buff = fd.read(length * @object_ref_size)
|
258
|
+
objects = buff.unpack(@object_ref_size == 1 ? "C*" : "n*")
|
259
|
+
|
260
|
+
# now: read objects
|
261
|
+
0.upto(length-1) do |i|
|
262
|
+
object = read_binary_object_at(fname,fd,objects[i])
|
263
|
+
ary.push object
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
return CFArray.new(ary)
|
268
|
+
end
|
269
|
+
protected :read_binary_array
|
270
|
+
|
271
|
+
# Read a dictionary value, including contained objects
|
272
|
+
def read_binary_dict(fname,fd,length)
|
273
|
+
dict = {}
|
274
|
+
|
275
|
+
# first: read keys
|
276
|
+
if(length != 0) then
|
277
|
+
buff = fd.read(length * @object_ref_size)
|
278
|
+
keys = buff.unpack(@object_ref_size == 1 ? "C*" : "n*")
|
279
|
+
|
280
|
+
# second: read object refs
|
281
|
+
buff = fd.read(length * @object_ref_size)
|
282
|
+
objects = buff.unpack(@object_ref_size == 1 ? "C*" : "n*")
|
283
|
+
|
284
|
+
# read real keys and objects
|
285
|
+
0.upto(length-1) do |i|
|
286
|
+
key = read_binary_object_at(fname,fd,keys[i])
|
287
|
+
object = read_binary_object_at(fname,fd,objects[i])
|
288
|
+
dict[key.value] = object
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
return CFDictionary.new(dict)
|
293
|
+
end
|
294
|
+
protected :read_binary_dict
|
295
|
+
|
296
|
+
# Read an object type byte, decode it and delegate to the correct reader function
|
297
|
+
def read_binary_object(fname,fd)
|
298
|
+
# first: read the marker byte
|
299
|
+
buff = fd.read(1)
|
300
|
+
|
301
|
+
object_length = buff.unpack("C*")
|
302
|
+
object_length = object_length[0] & 0xF
|
303
|
+
|
304
|
+
buff = buff.unpack("H*")
|
305
|
+
object_type = buff[0][0].chr
|
306
|
+
|
307
|
+
if(object_type != "0" && object_length == 15) then
|
308
|
+
object_length = read_binary_object(fname,fd)
|
309
|
+
object_length = object_length.value
|
310
|
+
end
|
311
|
+
|
312
|
+
retval = nil
|
313
|
+
case object_type
|
314
|
+
when '0' then # null, false, true, fillbyte
|
315
|
+
retval = read_binary_null_type(object_length)
|
316
|
+
when '1' then # integer
|
317
|
+
retval = read_binary_int(fname,fd,object_length)
|
318
|
+
when '2' then # real
|
319
|
+
retval = read_binary_real(fname,fd,object_length)
|
320
|
+
when '3' then # date
|
321
|
+
retval = read_binary_date(fname,fd,object_length)
|
322
|
+
when '4' then # data
|
323
|
+
retval = read_binary_data(fname,fd,object_length)
|
324
|
+
when '5' then # byte string, usually utf8 encoded
|
325
|
+
retval = read_binary_string(fname,fd,object_length)
|
326
|
+
when '6' then # unicode string (utf16be)
|
327
|
+
retval = read_binary_unicode_string(fname,fd,object_length)
|
328
|
+
when 'a' then # array
|
329
|
+
retval = read_binary_array(fname,fd,object_length)
|
330
|
+
when 'd' then # dictionary
|
331
|
+
retval = read_binary_dict(fname,fd,object_length)
|
332
|
+
end
|
333
|
+
|
334
|
+
return retval
|
335
|
+
end
|
336
|
+
protected :read_binary_object
|
337
|
+
|
338
|
+
# Read an object type byte at position $pos, decode it and delegate to the correct reader function
|
339
|
+
def read_binary_object_at(fname,fd,pos)
|
340
|
+
position = @offsets[pos]
|
341
|
+
fd.seek(position,IO::SEEK_SET)
|
342
|
+
return read_binary_object(fname,fd)
|
343
|
+
end
|
344
|
+
protected :read_binary_object_at
|
345
|
+
|
346
|
+
# calculate the bytes needed for a size integer value
|
347
|
+
def Binary.bytes_size_int(int)
|
348
|
+
nbytes = 0
|
349
|
+
|
350
|
+
nbytes += 2 if int > 0xE # 2 bytes int
|
351
|
+
nbytes += 2 if int > 0xFF # 3 bytes int
|
352
|
+
nbytes += 2 if int > 0xFFFF # 5 bytes int
|
353
|
+
|
354
|
+
return nbytes
|
355
|
+
end
|
356
|
+
|
357
|
+
# Calculate the byte needed for a „normal” integer value
|
358
|
+
def Binary.bytes_int(int)
|
359
|
+
nbytes = 1
|
360
|
+
|
361
|
+
nbytes += 1 if int > 0xFF # 2 byte int
|
362
|
+
nbytes += 2 if int > 0xFFFF # 4 byte int
|
363
|
+
nbytes += 4 if int > 0xFFFFFFFF # 8 byte int
|
364
|
+
nbytes += 7 if int < 0 # 8 byte int (since it is signed)
|
365
|
+
|
366
|
+
return nbytes + 1 # one „marker” byte
|
367
|
+
end
|
368
|
+
|
369
|
+
# pack an +int+ of +nbytes+ with size
|
370
|
+
def Binary.pack_it_with_size(nbytes,int)
|
371
|
+
format = ["C", "n", "N", "N"][nbytes-1]
|
372
|
+
|
373
|
+
if(nbytes == 3) then
|
374
|
+
val = [int].pack(format)
|
375
|
+
return val.slice(-3)
|
376
|
+
end
|
377
|
+
|
378
|
+
return [int].pack(format)
|
379
|
+
end
|
380
|
+
|
381
|
+
# calculate how many bytes are needed to save +count+
|
382
|
+
def Binary.bytes_needed(count)
|
383
|
+
nbytes = 0
|
384
|
+
|
385
|
+
while count >= 1 do
|
386
|
+
nbytes += 1
|
387
|
+
count /= 256
|
388
|
+
end
|
389
|
+
|
390
|
+
return nbytes
|
391
|
+
end
|
392
|
+
|
393
|
+
# create integer bytes of +int+
|
394
|
+
def Binary.int_bytes(int)
|
395
|
+
intbytes = ""
|
396
|
+
|
397
|
+
if(int > 0xFFFF) then
|
398
|
+
intbytes = "\x12"+[int].pack("N") # 4 byte integer
|
399
|
+
elsif(int > 0xFF) then
|
400
|
+
intbytes = "\x11"+[int].pack("n") # 2 byte integer
|
401
|
+
else
|
402
|
+
intbytes = "\x10"+[int].pack("C") # 8 byte integer
|
403
|
+
end
|
404
|
+
|
405
|
+
return intbytes;
|
406
|
+
end
|
407
|
+
|
408
|
+
# Create a type byte for binary format as defined by apple
|
409
|
+
def Binary.type_bytes(type,type_len)
|
410
|
+
optional_int = ""
|
411
|
+
|
412
|
+
if(type_len < 15) then
|
413
|
+
type += sprintf("%x",type_len)
|
414
|
+
else
|
415
|
+
type += "f"
|
416
|
+
optional_int = Binary.int_bytes(type_len)
|
417
|
+
end
|
418
|
+
|
419
|
+
return [type].pack("H*") + optional_int
|
420
|
+
end
|
421
|
+
|
422
|
+
# „unique” and count values. „Unique” means, several objects (e.g. strings)
|
423
|
+
# will only be saved once and referenced later
|
424
|
+
def unique_and_count_values(value)
|
425
|
+
# no uniquing for other types than CFString and CFData
|
426
|
+
if(value.is_a?(CFInteger) || value.is_a?(CFReal)) then
|
427
|
+
val = value.value
|
428
|
+
if(value.is_a?(CFInteger)) then
|
429
|
+
@int_size += Binary.bytes_int(val)
|
430
|
+
else
|
431
|
+
@misc_size += 9 # 9 bytes (8 + marker byte) for real
|
432
|
+
end
|
433
|
+
|
434
|
+
@count_objects += 1
|
435
|
+
return
|
436
|
+
elsif(value.is_a?(CFDate)) then
|
437
|
+
@misc_size += 9
|
438
|
+
@count_objects += 1
|
439
|
+
return
|
440
|
+
elsif(value.is_a?(CFBoolean)) then
|
441
|
+
@count_objects += 1
|
442
|
+
@misc_size += 1
|
443
|
+
return
|
444
|
+
elsif(value.is_a?(CFArray)) then
|
445
|
+
cnt = 0
|
446
|
+
|
447
|
+
value.value.each do |v|
|
448
|
+
cnt += 1
|
449
|
+
unique_and_count_values(v)
|
450
|
+
@object_refs += 1 # each array member is a ref
|
451
|
+
end
|
452
|
+
|
453
|
+
@count_objects += 1
|
454
|
+
@int_size += Binary.bytes_size_int(cnt)
|
455
|
+
@misc_size += 1 # marker byte for array
|
456
|
+
return
|
457
|
+
elsif(value.is_a?(CFDictionary)) then
|
458
|
+
cnt = 0
|
459
|
+
|
460
|
+
value.value.each_pair do |k,v|
|
461
|
+
cnt += 1
|
462
|
+
|
463
|
+
if(!@unique_table.has_key?(k))
|
464
|
+
@unique_table[k] = 0
|
465
|
+
@string_size += Binary.binary_strlen(k) + 1
|
466
|
+
@int_size += Binary.bytes_size_int(Binary.charset_strlen(k,'UTF-8'))
|
467
|
+
end
|
468
|
+
|
469
|
+
@object_refs += 2 # both, key and value, are refs
|
470
|
+
@unique_table[k] += 1
|
471
|
+
unique_and_count_values(v)
|
472
|
+
end
|
473
|
+
|
474
|
+
@count_objects += 1
|
475
|
+
@misc_size += 1 # marker byte for dict
|
476
|
+
@int_size += Binary.bytes_size_int(cnt)
|
477
|
+
return
|
478
|
+
elsif(value.is_a?(CFData)) then
|
479
|
+
val = value.decoded_value
|
480
|
+
@int_size += Binary.bytes_size_int(val.length)
|
481
|
+
@misc_size += val.length
|
482
|
+
@count_objects += 1
|
483
|
+
return
|
484
|
+
end
|
485
|
+
|
486
|
+
val = value.value
|
487
|
+
if(!@unique_table.has_key?(val)) then
|
488
|
+
@unique_table[val] = 0
|
489
|
+
@string_size += Binary.binary_strlen(val) + 1
|
490
|
+
@int_size += Binary.bytes_size_int(Binary.charset_strlen(val,'UTF-8'))
|
491
|
+
end
|
492
|
+
|
493
|
+
@unique_table[val] += 1
|
494
|
+
end
|
495
|
+
protected :unique_and_count_values
|
496
|
+
|
497
|
+
# Counts the number of bytes the string will have when coded; utf-16be if non-ascii characters are present.
|
498
|
+
def Binary.binary_strlen(val)
|
499
|
+
val.each_byte do |b|
|
500
|
+
if(b > 127) then
|
501
|
+
val = Binary.charset_convert(val, 'UTF-8', 'UTF-16BE')
|
502
|
+
return val.bytesize
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
return val.bytesize
|
507
|
+
end
|
508
|
+
|
509
|
+
# Uniques and transforms a string value to binary format and adds it to the object table
|
510
|
+
def string_to_binary(val)
|
511
|
+
saved_object_count = -1
|
512
|
+
|
513
|
+
unless(@unique_table.has_key?(val)) then
|
514
|
+
saved_object_count = @written_object_count
|
515
|
+
@written_object_count += 1
|
516
|
+
|
517
|
+
@unique_table[val] = saved_object_count
|
518
|
+
utf16 = false
|
519
|
+
|
520
|
+
val.each_byte do |b|
|
521
|
+
if(b > 127) then
|
522
|
+
utf16 = true
|
523
|
+
break
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
if(utf16) then
|
528
|
+
bdata = Binary.type_bytes("6",Binary.charset_strlen(val,"UTF-8")) # 6 is 0110, unicode string (utf16be)
|
529
|
+
val = Binary.charset_convert(val,"UTF-8","UTF-16BE")
|
530
|
+
|
531
|
+
val.force_encoding("ASCII-8BIT") if val.respond_to?("encode")
|
532
|
+
@object_table[saved_object_count] = bdata + val
|
533
|
+
else
|
534
|
+
bdata = Binary.type_bytes("5",val.bytesize) # 5 is 0101 which is an ASCII string (seems to be ASCII encoded)
|
535
|
+
@object_table[saved_object_count] = bdata + val
|
536
|
+
end
|
537
|
+
else
|
538
|
+
saved_object_count = @unique_table[val]
|
539
|
+
end
|
540
|
+
|
541
|
+
return saved_object_count
|
542
|
+
end
|
543
|
+
|
544
|
+
# Codes an integer to binary format
|
545
|
+
def int_to_binary(value)
|
546
|
+
nbytes = 0
|
547
|
+
nbytes = 1 if value > 0xFF # 1 byte integer
|
548
|
+
nbytes += 1 if value > 0xFFFF # 4 byte integer
|
549
|
+
nbytes += 1 if value > 0xFFFFFFFF # 8 byte integer
|
550
|
+
nbytes = 3 if value < 0 # 8 byte integer, since signed
|
551
|
+
|
552
|
+
bdata = Binary.type_bytes("1", nbytes) # 1 is 0001, type indicator for integer
|
553
|
+
buff = ""
|
554
|
+
|
555
|
+
if(nbytes < 3) then
|
556
|
+
fmt = "N"
|
557
|
+
|
558
|
+
if(nbytes == 0) then
|
559
|
+
fmt = "C"
|
560
|
+
elsif(nbytes == 1)
|
561
|
+
fmt = "n"
|
562
|
+
end
|
563
|
+
|
564
|
+
buff = [value].pack(fmt)
|
565
|
+
else
|
566
|
+
# 64 bit signed integer; we need the higher and the lower 32 bit of the value
|
567
|
+
high_word = value >> 32
|
568
|
+
low_word = value & 0xFFFFFFFF
|
569
|
+
buff = [high_word,low_word].pack("NN")
|
570
|
+
end
|
571
|
+
|
572
|
+
return bdata + buff
|
573
|
+
end
|
574
|
+
|
575
|
+
# Codes a real value to binary format
|
576
|
+
def real_to_binary(val)
|
577
|
+
bdata = Binary.type_bytes("2",3) # 2 is 0010, type indicator for reals
|
578
|
+
buff = [val].pack("d")
|
579
|
+
return bdata + buff.reverse
|
580
|
+
end
|
581
|
+
|
582
|
+
# Converts a numeric value to binary and adds it to the object table
|
583
|
+
def num_to_binary(value)
|
584
|
+
saved_object_count = @written_object_count
|
585
|
+
@written_object_count += 1
|
586
|
+
|
587
|
+
val = ""
|
588
|
+
if(value.is_a?(CFInteger)) then
|
589
|
+
val = int_to_binary(value.value)
|
590
|
+
else
|
591
|
+
val = real_to_binary(value.value)
|
592
|
+
end
|
593
|
+
|
594
|
+
@object_table[saved_object_count] = val
|
595
|
+
return saved_object_count
|
596
|
+
end
|
597
|
+
|
598
|
+
# Convert date value (apple format) to binary and adds it to the object table
|
599
|
+
def date_to_binary(val)
|
600
|
+
saved_object_count = @written_object_count
|
601
|
+
@written_object_count += 1
|
602
|
+
|
603
|
+
val = val.getutc.to_f - CFDate::DATE_DIFF_APPLE_UNIX # CFDate is a real, number of seconds since 01/01/2001 00:00:00 GMT
|
604
|
+
|
605
|
+
bdata = Binary.type_bytes("3", 3) # 3 is 0011, type indicator for date
|
606
|
+
@object_table[saved_object_count] = bdata + [val].pack("d").reverse
|
607
|
+
|
608
|
+
return saved_object_count
|
609
|
+
end
|
610
|
+
|
611
|
+
# Convert a bool value to binary and add it to the object table
|
612
|
+
def bool_to_binary(val)
|
613
|
+
saved_object_count = @written_object_count
|
614
|
+
@written_object_count += 1
|
615
|
+
|
616
|
+
@object_table[saved_object_count] = val ? "\x9" : "\x8" # 0x9 is 1001, type indicator for true; 0x8 is 1000, type indicator for false
|
617
|
+
return saved_object_count
|
618
|
+
end
|
619
|
+
|
620
|
+
# Convert data value to binary format and add it to the object table
|
621
|
+
def data_to_binary(val)
|
622
|
+
saved_object_count = @written_object_count
|
623
|
+
@written_object_count += 1
|
624
|
+
|
625
|
+
bdata = Binary.type_bytes("4", val.bytesize) # a is 1000, type indicator for data
|
626
|
+
@object_table[saved_object_count] = bdata + val
|
627
|
+
|
628
|
+
return saved_object_count
|
629
|
+
end
|
630
|
+
|
631
|
+
# Convert array to binary format and add it to the object table
|
632
|
+
def array_to_binary(val)
|
633
|
+
saved_object_count = @written_object_count
|
634
|
+
@written_object_count += 1
|
635
|
+
|
636
|
+
bdata = Binary.type_bytes("a", val.value.count) # a is 1010, type indicator for arrays
|
637
|
+
|
638
|
+
val.value.each do |v|
|
639
|
+
bdata += Binary.pack_it_with_size(@object_ref_size, v.to_binary(self));
|
640
|
+
end
|
641
|
+
|
642
|
+
@object_table[saved_object_count] = bdata
|
643
|
+
return saved_object_count
|
644
|
+
end
|
645
|
+
|
646
|
+
# Convert dictionary to binary format and add it to the object table
|
647
|
+
def dict_to_binary(val)
|
648
|
+
saved_object_count = @written_object_count
|
649
|
+
@written_object_count += 1
|
650
|
+
|
651
|
+
bdata = Binary.type_bytes("d",val.value.count) # d=1101, type indicator for dictionary
|
652
|
+
|
653
|
+
val.value.each_key do |k|
|
654
|
+
str = CFString.new(k)
|
655
|
+
key = str.to_binary(self)
|
656
|
+
bdata += Binary.pack_it_with_size(@object_ref_size,key)
|
657
|
+
end
|
658
|
+
|
659
|
+
val.value.each_value do |v|
|
660
|
+
bdata += Binary.pack_it_with_size(@object_ref_size,v.to_binary(self))
|
661
|
+
end
|
662
|
+
|
663
|
+
@object_table[saved_object_count] = bdata
|
664
|
+
return saved_object_count
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
# eof
|