removed cfpropertylist from the source
This commit is contained in:
parent
9363d5fdb5
commit
a507736fac
7 changed files with 0 additions and 1411 deletions
|
@ -1,19 +0,0 @@
|
||||||
Copyright (c) 2010 Christian Kruse, <cjk@wwwtech.de>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
The above copyright notice and this permission notice shall be included
|
|
||||||
in all copies or substantial portions of the Software.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
CFPropertyList implementation
|
|
||||||
class to read, manipulate and write both XML and binary property list
|
|
||||||
files (plist(5)) as defined by Apple
|
|
||||||
|
|
||||||
== Example
|
|
||||||
|
|
||||||
# create a arbitrary data structure of basic data types
|
|
||||||
data = {
|
|
||||||
'name' => 'John Doe',
|
|
||||||
'missing' => true,
|
|
||||||
'last_seen' => Time.now,
|
|
||||||
'friends' => ['Jane Doe','Julian Doe'],
|
|
||||||
'likes' => {
|
|
||||||
'me' => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# create CFPropertyList::List object
|
|
||||||
plist = CFPropertyList::List.new
|
|
||||||
|
|
||||||
# call CFPropertyList.guess() to create corresponding CFType values
|
|
||||||
plist.value = CFPropertyList.guess(data)
|
|
||||||
|
|
||||||
# write plist to file
|
|
||||||
plist.save("example.plist", CFPropertyList::List::FORMAT_BINARY)
|
|
||||||
|
|
||||||
# … later, read it again
|
|
||||||
plist = CFPropertyList::List.new("example.plist")
|
|
||||||
data = CFPropertyList.native_types(plist.value)
|
|
||||||
|
|
||||||
Author:: Christian Kruse (mailto:cjk@wwwtech.de)
|
|
||||||
Copyright:: Copyright (c) 2010
|
|
||||||
License:: Distributes under the same terms as Ruby
|
|
||||||
|
|
|
@ -1,669 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# CFPropertyList implementation
|
|
||||||
# parser class to read, manipulate and write binary property list files (plist(5)) as defined by Apple
|
|
||||||
#
|
|
||||||
# Author:: Christian Kruse (mailto:cjk@wwwtech.de)
|
|
||||||
# Copyright:: Copyright (c) 2010
|
|
||||||
# License:: Distributes under the same terms as Ruby
|
|
||||||
|
|
||||||
module CFPropertyList
|
|
||||||
class Binary
|
|
||||||
# Read a binary plist file
|
|
||||||
def load(opts)
|
|
||||||
@unique_table = {}
|
|
||||||
@count_objects = 0
|
|
||||||
@string_size = 0
|
|
||||||
@int_size = 0
|
|
||||||
@misc_size = 0
|
|
||||||
@object_refs = 0
|
|
||||||
|
|
||||||
@written_object_count = 0
|
|
||||||
@object_table = []
|
|
||||||
@object_ref_size = 0
|
|
||||||
|
|
||||||
@offsets = []
|
|
||||||
|
|
||||||
fd = nil
|
|
||||||
if(opts.has_key?(:file)) then
|
|
||||||
fd = File.open(opts[:file],"rb")
|
|
||||||
file = opts[:file]
|
|
||||||
else
|
|
||||||
fd = StringIO.new(opts[:data],"rb")
|
|
||||||
file = "<string>"
|
|
||||||
end
|
|
||||||
|
|
||||||
# first, we read the trailer: 32 byte from the end
|
|
||||||
fd.seek(-32,IO::SEEK_END)
|
|
||||||
buff = fd.read(32)
|
|
||||||
|
|
||||||
offset_size, object_ref_size, number_of_objects, top_object, table_offset = buff.unpack "x6CCx4Nx4Nx4N"
|
|
||||||
|
|
||||||
# after that, get the offset table
|
|
||||||
fd.seek(table_offset, IO::SEEK_SET)
|
|
||||||
coded_offset_table = fd.read(number_of_objects * offset_size)
|
|
||||||
raise CFFormatError.new("#{file}: Format error!") unless coded_offset_table.bytesize == number_of_objects * offset_size
|
|
||||||
|
|
||||||
@count_objects = number_of_objects
|
|
||||||
|
|
||||||
# decode offset table
|
|
||||||
formats = ["","C*","n*","(H6)*","N*"]
|
|
||||||
@offsets = coded_offset_table.unpack(formats[offset_size])
|
|
||||||
if(offset_size == 3) then
|
|
||||||
0.upto(@offsets.count-1) { |i| @offsets[i] = @offsets[i].to_i(16) }
|
|
||||||
end
|
|
||||||
|
|
||||||
@object_ref_size = object_ref_size
|
|
||||||
val = read_binary_object_at(file,fd,top_object)
|
|
||||||
|
|
||||||
fd.close
|
|
||||||
return val
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Convert CFPropertyList to binary format; since we have to count our objects we simply unique CFDictionary and CFArray
|
|
||||||
def to_str(opts={})
|
|
||||||
@unique_table = {}
|
|
||||||
@count_objects = 0
|
|
||||||
@string_size = 0
|
|
||||||
@int_size = 0
|
|
||||||
@misc_size = 0
|
|
||||||
@object_refs = 0
|
|
||||||
|
|
||||||
@written_object_count = 0
|
|
||||||
@object_table = []
|
|
||||||
@object_ref_size = 0
|
|
||||||
|
|
||||||
@offsets = []
|
|
||||||
|
|
||||||
binary_str = "bplist00"
|
|
||||||
unique_and_count_values(opts[:root])
|
|
||||||
|
|
||||||
@count_objects += @unique_table.count
|
|
||||||
@object_ref_size = Binary.bytes_needed(@count_objects)
|
|
||||||
|
|
||||||
file_size = @string_size + @int_size + @misc_size + @object_refs * @object_ref_size + 40
|
|
||||||
offset_size = Binary.bytes_needed(file_size)
|
|
||||||
table_offset = file_size - 32
|
|
||||||
|
|
||||||
@object_table = []
|
|
||||||
@written_object_count = 0
|
|
||||||
@unique_table = {} # we needed it to calculate several values, but now we need an empty table
|
|
||||||
|
|
||||||
opts[:root].to_binary(self)
|
|
||||||
|
|
||||||
object_offset = 8
|
|
||||||
offsets = []
|
|
||||||
|
|
||||||
0.upto(@object_table.count-1) do |i|
|
|
||||||
binary_str += @object_table[i]
|
|
||||||
offsets[i] = object_offset
|
|
||||||
object_offset += @object_table[i].bytesize
|
|
||||||
end
|
|
||||||
|
|
||||||
offsets.each do |offset|
|
|
||||||
binary_str += Binary.pack_it_with_size(offset_size,offset)
|
|
||||||
end
|
|
||||||
|
|
||||||
binary_str += [offset_size, @object_ref_size].pack("x6CC")
|
|
||||||
binary_str += [@count_objects].pack("x4N")
|
|
||||||
binary_str += [0].pack("x4N")
|
|
||||||
binary_str += [table_offset].pack("x4N")
|
|
||||||
|
|
||||||
return binary_str
|
|
||||||
end
|
|
||||||
|
|
||||||
# read a „null” type (i.e. null byte, marker byte, bool value)
|
|
||||||
def read_binary_null_type(length)
|
|
||||||
case length
|
|
||||||
when 0 then return 0 # null byte
|
|
||||||
when 8 then return CFBoolean.new(false)
|
|
||||||
when 9 then return CFBoolean.new(true)
|
|
||||||
when 15 then return 15 # fill type
|
|
||||||
end
|
|
||||||
|
|
||||||
raise CFFormatError.new("unknown null type: #{length}")
|
|
||||||
end
|
|
||||||
protected :read_binary_null_type
|
|
||||||
|
|
||||||
# read a binary int value
|
|
||||||
def read_binary_int(fname,fd,length)
|
|
||||||
raise CFFormatError.new("Integer greater than 8 bytes: #{length}") if length > 3
|
|
||||||
|
|
||||||
nbytes = 1 << length
|
|
||||||
|
|
||||||
val = nil
|
|
||||||
buff = fd.read(nbytes)
|
|
||||||
|
|
||||||
case length
|
|
||||||
when 0 then
|
|
||||||
val = buff.unpack("C")
|
|
||||||
val = val[0]
|
|
||||||
when 1 then
|
|
||||||
val = buff.unpack("n")
|
|
||||||
val = val[0]
|
|
||||||
when 2 then
|
|
||||||
val = buff.unpack("N")
|
|
||||||
val = val[0]
|
|
||||||
when 3
|
|
||||||
hiword,loword = buff.unpack("NN")
|
|
||||||
val = hiword << 32 | loword
|
|
||||||
end
|
|
||||||
|
|
||||||
return CFInteger.new(val);
|
|
||||||
end
|
|
||||||
protected :read_binary_int
|
|
||||||
|
|
||||||
# read a binary real value
|
|
||||||
def read_binary_real(fname,fd,length)
|
|
||||||
raise CFFormatError.new("Real greater than 8 bytes: #{length}") if length > 3
|
|
||||||
|
|
||||||
nbytes = 1 << length
|
|
||||||
val = nil
|
|
||||||
buff = fd.read(nbytes)
|
|
||||||
|
|
||||||
case length
|
|
||||||
when 0 then # 1 byte float? must be an error
|
|
||||||
raise CFFormatError.new("got #{length+1} byte float, must be an error!")
|
|
||||||
when 1 then # 2 byte float? must be an error
|
|
||||||
raise CFFormatError.new("got #{length+1} byte float, must be an error!")
|
|
||||||
when 2 then
|
|
||||||
val = buff.reverse.unpack("f")
|
|
||||||
val = val[0]
|
|
||||||
when 3 then
|
|
||||||
val = buff.reverse.unpack("d")
|
|
||||||
val = val[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
return CFReal.new(val)
|
|
||||||
end
|
|
||||||
protected :read_binary_real
|
|
||||||
|
|
||||||
# read a binary date value
|
|
||||||
def read_binary_date(fname,fd,length)
|
|
||||||
raise CFFormatError.new("Date greater than 8 bytes: #{length}") if length > 3
|
|
||||||
|
|
||||||
nbytes = 1 << length
|
|
||||||
val = nil
|
|
||||||
buff = fd.read(nbytes)
|
|
||||||
|
|
||||||
case length
|
|
||||||
when 0 then # 1 byte CFDate is an error
|
|
||||||
raise CFFormatError.new("#{length+1} byte CFDate, error")
|
|
||||||
when 1 then # 2 byte CFDate is an error
|
|
||||||
raise CFFormatError.new("#{length+1} byte CFDate, error")
|
|
||||||
when 2 then
|
|
||||||
val = buff.reverse.unpack("f")
|
|
||||||
val = val[0]
|
|
||||||
when 3 then
|
|
||||||
val = buff.reverse.unpack("d")
|
|
||||||
val = val[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
return CFDate.new(val,CFDate::TIMESTAMP_APPLE)
|
|
||||||
end
|
|
||||||
protected :read_binary_date
|
|
||||||
|
|
||||||
# Read a binary data value
|
|
||||||
def read_binary_data(fname,fd,length)
|
|
||||||
buff = "";
|
|
||||||
buff = fd.read(length) if length > 0
|
|
||||||
return CFData.new(buff,CFData::DATA_RAW)
|
|
||||||
end
|
|
||||||
protected :read_binary_data
|
|
||||||
|
|
||||||
# Read a binary string value
|
|
||||||
def read_binary_string(fname,fd,length)
|
|
||||||
buff = ""
|
|
||||||
buff = fd.read(length) if length > 0
|
|
||||||
|
|
||||||
@unique_table[buff] = true unless @unique_table.has_key?(buff)
|
|
||||||
return CFString.new(buff)
|
|
||||||
end
|
|
||||||
protected :read_binary_string
|
|
||||||
|
|
||||||
# Convert the given string from one charset to another
|
|
||||||
def Binary.charset_convert(str,from,to="UTF-8")
|
|
||||||
return str.clone.force_encoding(from).encode(to) if str.respond_to?("encode")
|
|
||||||
return Iconv.conv(to,from,str)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Count characters considering character set
|
|
||||||
def Binary.charset_strlen(str,charset="UTF-8")
|
|
||||||
return str.length if str.respond_to?("encode")
|
|
||||||
|
|
||||||
str = Iconv.conv("UTF-8",charset,str) if charset != "UTF-8"
|
|
||||||
return str.scan(/./mu).size
|
|
||||||
end
|
|
||||||
|
|
||||||
# Read a unicode string value, coded as UTF-16BE
|
|
||||||
def read_binary_unicode_string(fname,fd,length)
|
|
||||||
# The problem is: we get the length of the string IN CHARACTERS;
|
|
||||||
# since a char in UTF-16 can be 16 or 32 bit long, we don't really know
|
|
||||||
# how long the string is in bytes
|
|
||||||
buff = fd.read(2*length)
|
|
||||||
|
|
||||||
@unique_table[buff] = true unless @unique_table.has_key?(buff)
|
|
||||||
return CFString.new(Binary.charset_convert(buff,"UTF-16BE","UTF-8"))
|
|
||||||
end
|
|
||||||
protected :read_binary_unicode_string
|
|
||||||
|
|
||||||
# Read an binary array value, including contained objects
|
|
||||||
def read_binary_array(fname,fd,length)
|
|
||||||
ary = []
|
|
||||||
|
|
||||||
# first: read object refs
|
|
||||||
if(length != 0) then
|
|
||||||
buff = fd.read(length * @object_ref_size)
|
|
||||||
objects = buff.unpack(@object_ref_size == 1 ? "C*" : "n*")
|
|
||||||
|
|
||||||
# now: read objects
|
|
||||||
0.upto(length-1) do |i|
|
|
||||||
object = read_binary_object_at(fname,fd,objects[i])
|
|
||||||
ary.push object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return CFArray.new(ary)
|
|
||||||
end
|
|
||||||
protected :read_binary_array
|
|
||||||
|
|
||||||
# Read a dictionary value, including contained objects
|
|
||||||
def read_binary_dict(fname,fd,length)
|
|
||||||
dict = {}
|
|
||||||
|
|
||||||
# first: read keys
|
|
||||||
if(length != 0) then
|
|
||||||
buff = fd.read(length * @object_ref_size)
|
|
||||||
keys = buff.unpack(@object_ref_size == 1 ? "C*" : "n*")
|
|
||||||
|
|
||||||
# second: read object refs
|
|
||||||
buff = fd.read(length * @object_ref_size)
|
|
||||||
objects = buff.unpack(@object_ref_size == 1 ? "C*" : "n*")
|
|
||||||
|
|
||||||
# read real keys and objects
|
|
||||||
0.upto(length-1) do |i|
|
|
||||||
key = read_binary_object_at(fname,fd,keys[i])
|
|
||||||
object = read_binary_object_at(fname,fd,objects[i])
|
|
||||||
dict[key.value] = object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return CFDictionary.new(dict)
|
|
||||||
end
|
|
||||||
protected :read_binary_dict
|
|
||||||
|
|
||||||
# Read an object type byte, decode it and delegate to the correct reader function
|
|
||||||
def read_binary_object(fname,fd)
|
|
||||||
# first: read the marker byte
|
|
||||||
buff = fd.read(1)
|
|
||||||
|
|
||||||
object_length = buff.unpack("C*")
|
|
||||||
object_length = object_length[0] & 0xF
|
|
||||||
|
|
||||||
buff = buff.unpack("H*")
|
|
||||||
object_type = buff[0][0].chr
|
|
||||||
|
|
||||||
if(object_type != "0" && object_length == 15) then
|
|
||||||
object_length = read_binary_object(fname,fd)
|
|
||||||
object_length = object_length.value
|
|
||||||
end
|
|
||||||
|
|
||||||
retval = nil
|
|
||||||
case object_type
|
|
||||||
when '0' then # null, false, true, fillbyte
|
|
||||||
retval = read_binary_null_type(object_length)
|
|
||||||
when '1' then # integer
|
|
||||||
retval = read_binary_int(fname,fd,object_length)
|
|
||||||
when '2' then # real
|
|
||||||
retval = read_binary_real(fname,fd,object_length)
|
|
||||||
when '3' then # date
|
|
||||||
retval = read_binary_date(fname,fd,object_length)
|
|
||||||
when '4' then # data
|
|
||||||
retval = read_binary_data(fname,fd,object_length)
|
|
||||||
when '5' then # byte string, usually utf8 encoded
|
|
||||||
retval = read_binary_string(fname,fd,object_length)
|
|
||||||
when '6' then # unicode string (utf16be)
|
|
||||||
retval = read_binary_unicode_string(fname,fd,object_length)
|
|
||||||
when 'a' then # array
|
|
||||||
retval = read_binary_array(fname,fd,object_length)
|
|
||||||
when 'd' then # dictionary
|
|
||||||
retval = read_binary_dict(fname,fd,object_length)
|
|
||||||
end
|
|
||||||
|
|
||||||
return retval
|
|
||||||
end
|
|
||||||
protected :read_binary_object
|
|
||||||
|
|
||||||
# Read an object type byte at position $pos, decode it and delegate to the correct reader function
|
|
||||||
def read_binary_object_at(fname,fd,pos)
|
|
||||||
position = @offsets[pos]
|
|
||||||
fd.seek(position,IO::SEEK_SET)
|
|
||||||
return read_binary_object(fname,fd)
|
|
||||||
end
|
|
||||||
protected :read_binary_object_at
|
|
||||||
|
|
||||||
# calculate the bytes needed for a size integer value
|
|
||||||
def Binary.bytes_size_int(int)
|
|
||||||
nbytes = 0
|
|
||||||
|
|
||||||
nbytes += 2 if int > 0xE # 2 bytes int
|
|
||||||
nbytes += 2 if int > 0xFF # 3 bytes int
|
|
||||||
nbytes += 2 if int > 0xFFFF # 5 bytes int
|
|
||||||
|
|
||||||
return nbytes
|
|
||||||
end
|
|
||||||
|
|
||||||
# Calculate the byte needed for a „normal” integer value
|
|
||||||
def Binary.bytes_int(int)
|
|
||||||
nbytes = 1
|
|
||||||
|
|
||||||
nbytes += 1 if int > 0xFF # 2 byte int
|
|
||||||
nbytes += 2 if int > 0xFFFF # 4 byte int
|
|
||||||
nbytes += 4 if int > 0xFFFFFFFF # 8 byte int
|
|
||||||
nbytes += 7 if int < 0 # 8 byte int (since it is signed)
|
|
||||||
|
|
||||||
return nbytes + 1 # one „marker” byte
|
|
||||||
end
|
|
||||||
|
|
||||||
# pack an +int+ of +nbytes+ with size
|
|
||||||
def Binary.pack_it_with_size(nbytes,int)
|
|
||||||
format = ["C", "n", "N", "N"][nbytes-1]
|
|
||||||
|
|
||||||
if(nbytes == 3) then
|
|
||||||
val = [int].pack(format)
|
|
||||||
return val.slice(-3)
|
|
||||||
end
|
|
||||||
|
|
||||||
return [int].pack(format)
|
|
||||||
end
|
|
||||||
|
|
||||||
# calculate how many bytes are needed to save +count+
|
|
||||||
def Binary.bytes_needed(count)
|
|
||||||
nbytes = 0
|
|
||||||
|
|
||||||
while count >= 1 do
|
|
||||||
nbytes += 1
|
|
||||||
count /= 256
|
|
||||||
end
|
|
||||||
|
|
||||||
return nbytes
|
|
||||||
end
|
|
||||||
|
|
||||||
# create integer bytes of +int+
|
|
||||||
def Binary.int_bytes(int)
|
|
||||||
intbytes = ""
|
|
||||||
|
|
||||||
if(int > 0xFFFF) then
|
|
||||||
intbytes = "\x12"+[int].pack("N") # 4 byte integer
|
|
||||||
elsif(int > 0xFF) then
|
|
||||||
intbytes = "\x11"+[int].pack("n") # 2 byte integer
|
|
||||||
else
|
|
||||||
intbytes = "\x10"+[int].pack("C") # 8 byte integer
|
|
||||||
end
|
|
||||||
|
|
||||||
return intbytes;
|
|
||||||
end
|
|
||||||
|
|
||||||
# Create a type byte for binary format as defined by apple
|
|
||||||
def Binary.type_bytes(type,type_len)
|
|
||||||
optional_int = ""
|
|
||||||
|
|
||||||
if(type_len < 15) then
|
|
||||||
type += sprintf("%x",type_len)
|
|
||||||
else
|
|
||||||
type += "f"
|
|
||||||
optional_int = Binary.int_bytes(type_len)
|
|
||||||
end
|
|
||||||
|
|
||||||
return [type].pack("H*") + optional_int
|
|
||||||
end
|
|
||||||
|
|
||||||
# „unique” and count values. „Unique” means, several objects (e.g. strings)
|
|
||||||
# will only be saved once and referenced later
|
|
||||||
def unique_and_count_values(value)
|
|
||||||
# no uniquing for other types than CFString and CFData
|
|
||||||
if(value.is_a?(CFInteger) || value.is_a?(CFReal)) then
|
|
||||||
val = value.value
|
|
||||||
if(value.is_a?(CFInteger)) then
|
|
||||||
@int_size += Binary.bytes_int(val)
|
|
||||||
else
|
|
||||||
@misc_size += 9 # 9 bytes (8 + marker byte) for real
|
|
||||||
end
|
|
||||||
|
|
||||||
@count_objects += 1
|
|
||||||
return
|
|
||||||
elsif(value.is_a?(CFDate)) then
|
|
||||||
@misc_size += 9
|
|
||||||
@count_objects += 1
|
|
||||||
return
|
|
||||||
elsif(value.is_a?(CFBoolean)) then
|
|
||||||
@count_objects += 1
|
|
||||||
@misc_size += 1
|
|
||||||
return
|
|
||||||
elsif(value.is_a?(CFArray)) then
|
|
||||||
cnt = 0
|
|
||||||
|
|
||||||
value.value.each do |v|
|
|
||||||
cnt += 1
|
|
||||||
unique_and_count_values(v)
|
|
||||||
@object_refs += 1 # each array member is a ref
|
|
||||||
end
|
|
||||||
|
|
||||||
@count_objects += 1
|
|
||||||
@int_size += Binary.bytes_size_int(cnt)
|
|
||||||
@misc_size += 1 # marker byte for array
|
|
||||||
return
|
|
||||||
elsif(value.is_a?(CFDictionary)) then
|
|
||||||
cnt = 0
|
|
||||||
|
|
||||||
value.value.each_pair do |k,v|
|
|
||||||
cnt += 1
|
|
||||||
|
|
||||||
if(!@unique_table.has_key?(k))
|
|
||||||
@unique_table[k] = 0
|
|
||||||
@string_size += Binary.binary_strlen(k) + 1
|
|
||||||
@int_size += Binary.bytes_size_int(Binary.charset_strlen(k,'UTF-8'))
|
|
||||||
end
|
|
||||||
|
|
||||||
@object_refs += 2 # both, key and value, are refs
|
|
||||||
@unique_table[k] += 1
|
|
||||||
unique_and_count_values(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
@count_objects += 1
|
|
||||||
@misc_size += 1 # marker byte for dict
|
|
||||||
@int_size += Binary.bytes_size_int(cnt)
|
|
||||||
return
|
|
||||||
elsif(value.is_a?(CFData)) then
|
|
||||||
val = value.decoded_value
|
|
||||||
@int_size += Binary.bytes_size_int(val.length)
|
|
||||||
@misc_size += val.length
|
|
||||||
@count_objects += 1
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
val = value.value
|
|
||||||
if(!@unique_table.has_key?(val)) then
|
|
||||||
@unique_table[val] = 0
|
|
||||||
@string_size += Binary.binary_strlen(val) + 1
|
|
||||||
@int_size += Binary.bytes_size_int(Binary.charset_strlen(val,'UTF-8'))
|
|
||||||
end
|
|
||||||
|
|
||||||
@unique_table[val] += 1
|
|
||||||
end
|
|
||||||
protected :unique_and_count_values
|
|
||||||
|
|
||||||
# Counts the number of bytes the string will have when coded; utf-16be if non-ascii characters are present.
|
|
||||||
def Binary.binary_strlen(val)
|
|
||||||
val.each_byte do |b|
|
|
||||||
if(b > 127) then
|
|
||||||
val = Binary.charset_convert(val, 'UTF-8', 'UTF-16BE')
|
|
||||||
return val.bytesize
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return val.bytesize
|
|
||||||
end
|
|
||||||
|
|
||||||
# Uniques and transforms a string value to binary format and adds it to the object table
|
|
||||||
def string_to_binary(val)
|
|
||||||
saved_object_count = -1
|
|
||||||
|
|
||||||
unless(@unique_table.has_key?(val)) then
|
|
||||||
saved_object_count = @written_object_count
|
|
||||||
@written_object_count += 1
|
|
||||||
|
|
||||||
@unique_table[val] = saved_object_count
|
|
||||||
utf16 = false
|
|
||||||
|
|
||||||
val.each_byte do |b|
|
|
||||||
if(b > 127) then
|
|
||||||
utf16 = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if(utf16) then
|
|
||||||
bdata = Binary.type_bytes("6",Binary.charset_strlen(val,"UTF-8")) # 6 is 0110, unicode string (utf16be)
|
|
||||||
val = Binary.charset_convert(val,"UTF-8","UTF-16BE")
|
|
||||||
|
|
||||||
val.force_encoding("ASCII-8BIT") if val.respond_to?("encode")
|
|
||||||
@object_table[saved_object_count] = bdata + val
|
|
||||||
else
|
|
||||||
bdata = Binary.type_bytes("5",val.bytesize) # 5 is 0101 which is an ASCII string (seems to be ASCII encoded)
|
|
||||||
@object_table[saved_object_count] = bdata + val
|
|
||||||
end
|
|
||||||
else
|
|
||||||
saved_object_count = @unique_table[val]
|
|
||||||
end
|
|
||||||
|
|
||||||
return saved_object_count
|
|
||||||
end
|
|
||||||
|
|
||||||
# Codes an integer to binary format
|
|
||||||
def int_to_binary(value)
|
|
||||||
nbytes = 0
|
|
||||||
nbytes = 1 if value > 0xFF # 1 byte integer
|
|
||||||
nbytes += 1 if value > 0xFFFF # 4 byte integer
|
|
||||||
nbytes += 1 if value > 0xFFFFFFFF # 8 byte integer
|
|
||||||
nbytes = 3 if value < 0 # 8 byte integer, since signed
|
|
||||||
|
|
||||||
bdata = Binary.type_bytes("1", nbytes) # 1 is 0001, type indicator for integer
|
|
||||||
buff = ""
|
|
||||||
|
|
||||||
if(nbytes < 3) then
|
|
||||||
fmt = "N"
|
|
||||||
|
|
||||||
if(nbytes == 0) then
|
|
||||||
fmt = "C"
|
|
||||||
elsif(nbytes == 1)
|
|
||||||
fmt = "n"
|
|
||||||
end
|
|
||||||
|
|
||||||
buff = [value].pack(fmt)
|
|
||||||
else
|
|
||||||
# 64 bit signed integer; we need the higher and the lower 32 bit of the value
|
|
||||||
high_word = value >> 32
|
|
||||||
low_word = value & 0xFFFFFFFF
|
|
||||||
buff = [high_word,low_word].pack("NN")
|
|
||||||
end
|
|
||||||
|
|
||||||
return bdata + buff
|
|
||||||
end
|
|
||||||
|
|
||||||
# Codes a real value to binary format
|
|
||||||
def real_to_binary(val)
|
|
||||||
bdata = Binary.type_bytes("2",3) # 2 is 0010, type indicator for reals
|
|
||||||
buff = [val].pack("d")
|
|
||||||
return bdata + buff.reverse
|
|
||||||
end
|
|
||||||
|
|
||||||
# Converts a numeric value to binary and adds it to the object table
|
|
||||||
def num_to_binary(value)
|
|
||||||
saved_object_count = @written_object_count
|
|
||||||
@written_object_count += 1
|
|
||||||
|
|
||||||
val = ""
|
|
||||||
if(value.is_a?(CFInteger)) then
|
|
||||||
val = int_to_binary(value.value)
|
|
||||||
else
|
|
||||||
val = real_to_binary(value.value)
|
|
||||||
end
|
|
||||||
|
|
||||||
@object_table[saved_object_count] = val
|
|
||||||
return saved_object_count
|
|
||||||
end
|
|
||||||
|
|
||||||
# Convert date value (apple format) to binary and adds it to the object table
|
|
||||||
def date_to_binary(val)
|
|
||||||
saved_object_count = @written_object_count
|
|
||||||
@written_object_count += 1
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
bdata = Binary.type_bytes("3", 3) # 3 is 0011, type indicator for date
|
|
||||||
@object_table[saved_object_count] = bdata + [val].pack("d").reverse
|
|
||||||
|
|
||||||
return saved_object_count
|
|
||||||
end
|
|
||||||
|
|
||||||
# Convert a bool value to binary and add it to the object table
|
|
||||||
def bool_to_binary(val)
|
|
||||||
saved_object_count = @written_object_count
|
|
||||||
@written_object_count += 1
|
|
||||||
|
|
||||||
@object_table[saved_object_count] = val ? "\x9" : "\x8" # 0x9 is 1001, type indicator for true; 0x8 is 1000, type indicator for false
|
|
||||||
return saved_object_count
|
|
||||||
end
|
|
||||||
|
|
||||||
# Convert data value to binary format and add it to the object table
|
|
||||||
def data_to_binary(val)
|
|
||||||
saved_object_count = @written_object_count
|
|
||||||
@written_object_count += 1
|
|
||||||
|
|
||||||
bdata = Binary.type_bytes("4", val.bytesize) # a is 1000, type indicator for data
|
|
||||||
@object_table[saved_object_count] = bdata + val
|
|
||||||
|
|
||||||
return saved_object_count
|
|
||||||
end
|
|
||||||
|
|
||||||
# Convert array to binary format and add it to the object table
|
|
||||||
def array_to_binary(val)
|
|
||||||
saved_object_count = @written_object_count
|
|
||||||
@written_object_count += 1
|
|
||||||
|
|
||||||
bdata = Binary.type_bytes("a", val.value.count) # a is 1010, type indicator for arrays
|
|
||||||
|
|
||||||
val.value.each do |v|
|
|
||||||
bdata += Binary.pack_it_with_size(@object_ref_size, v.to_binary(self));
|
|
||||||
end
|
|
||||||
|
|
||||||
@object_table[saved_object_count] = bdata
|
|
||||||
return saved_object_count
|
|
||||||
end
|
|
||||||
|
|
||||||
# Convert dictionary to binary format and add it to the object table
|
|
||||||
def dict_to_binary(val)
|
|
||||||
saved_object_count = @written_object_count
|
|
||||||
@written_object_count += 1
|
|
||||||
|
|
||||||
bdata = Binary.type_bytes("d",val.value.count) # d=1101, type indicator for dictionary
|
|
||||||
|
|
||||||
val.value.each_key do |k|
|
|
||||||
str = CFString.new(k)
|
|
||||||
key = str.to_binary(self)
|
|
||||||
bdata += Binary.pack_it_with_size(@object_ref_size,key)
|
|
||||||
end
|
|
||||||
|
|
||||||
val.value.each_value do |v|
|
|
||||||
bdata += Binary.pack_it_with_size(@object_ref_size,v.to_binary(self))
|
|
||||||
end
|
|
||||||
|
|
||||||
@object_table[saved_object_count] = bdata
|
|
||||||
return saved_object_count
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# eof
|
|
|
@ -1,19 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# CFFormatError implementation
|
|
||||||
#
|
|
||||||
# Author:: Christian Kruse (mailto:cjk@wwwtech.de)
|
|
||||||
# Copyright:: Copyright (c) 2010
|
|
||||||
# License:: MIT License
|
|
||||||
|
|
||||||
class CFPlistError < Exception
|
|
||||||
end
|
|
||||||
|
|
||||||
# Exception thrown when format errors occur
|
|
||||||
class CFFormatError < CFPlistError
|
|
||||||
end
|
|
||||||
|
|
||||||
class CFTypeError < CFPlistError
|
|
||||||
end
|
|
||||||
|
|
||||||
# eof
|
|
|
@ -1,316 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# CFPropertyList implementation
|
|
||||||
# class to read, manipulate and write both XML and binary property list
|
|
||||||
# files (plist(5)) as defined by Apple
|
|
||||||
#
|
|
||||||
# == Example
|
|
||||||
#
|
|
||||||
# # create a arbitrary data structure of basic data types
|
|
||||||
# data = {
|
|
||||||
# 'name' => 'John Doe',
|
|
||||||
# 'missing' => true,
|
|
||||||
# 'last_seen' => Time.now,
|
|
||||||
# 'friends' => ['Jane Doe','Julian Doe'],
|
|
||||||
# 'likes' => {
|
|
||||||
# 'me' => false
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# # create CFPropertyList::List object
|
|
||||||
# plist = CFPropertyList::List.new
|
|
||||||
#
|
|
||||||
# # call CFPropertyList.guess() to create corresponding CFType values
|
|
||||||
# # pass in optional :convert_unknown_to_string => true to convert things like symbols into strings.
|
|
||||||
# plist.value = CFPropertyList.guess(data)
|
|
||||||
#
|
|
||||||
# # write plist to file
|
|
||||||
# plist.save("example.plist", CFPropertyList::List::FORMAT_BINARY)
|
|
||||||
#
|
|
||||||
# # … later, read it again
|
|
||||||
# plist = CFPropertyList::List.new({:file => "example.plist"})
|
|
||||||
# data = CFPropertyList.native_types(plist.value)
|
|
||||||
#
|
|
||||||
# Author:: Christian Kruse (mailto:cjk@wwwtech.de)
|
|
||||||
# Copyright:: Copyright (c) 2010
|
|
||||||
# License:: Distributes under the same terms as Ruby
|
|
||||||
|
|
||||||
require 'libxml'
|
|
||||||
require 'kconv'
|
|
||||||
require 'date'
|
|
||||||
|
|
||||||
module CFPropertyList
|
|
||||||
# interface class for PList parsers
|
|
||||||
class ParserInterface
|
|
||||||
# load a plist
|
|
||||||
def load(opts={})
|
|
||||||
return ""
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert a plist to string
|
|
||||||
def to_str(opts={})
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
dirname = File.dirname(__FILE__)
|
|
||||||
require dirname + '/rbCFPlistError.rb'
|
|
||||||
require dirname + '/rbCFTypes.rb'
|
|
||||||
require dirname + '/rbXMLCFPropertyList.rb'
|
|
||||||
require dirname + '/rbBinaryCFPropertyList.rb'
|
|
||||||
|
|
||||||
require 'iconv' unless "".respond_to?("encode")
|
|
||||||
|
|
||||||
module CFPropertyList
|
|
||||||
# Create CFType hierarchy by guessing the correct CFType, e.g.
|
|
||||||
#
|
|
||||||
# x = {
|
|
||||||
# 'a' => ['b','c','d']
|
|
||||||
# }
|
|
||||||
# cftypes = CFPropertyList.guess(x)
|
|
||||||
#
|
|
||||||
# pass optional options hash. Only possible value actually:
|
|
||||||
# :convert_unknown_to_string => true
|
|
||||||
# :converter_method => :method_name
|
|
||||||
#
|
|
||||||
# cftypes = CFPropertyList.guess(x,:convert_unknown_to_string => true)
|
|
||||||
def guess(object, options = {})
|
|
||||||
if(object.is_a?(Fixnum) || object.is_a?(Integer)) then
|
|
||||||
return CFInteger.new(object)
|
|
||||||
elsif(object.is_a?(Float) || (Object.const_defined?('BigDecimal') and object.is_a?(BigDecimal))) then
|
|
||||||
return CFReal.new(object)
|
|
||||||
elsif(object.is_a?(TrueClass) || object.is_a?(FalseClass)) then
|
|
||||||
return CFBoolean.new(object)
|
|
||||||
elsif(object.is_a?(String)) then
|
|
||||||
return CFString.new(object)
|
|
||||||
elsif(object.is_a?(Time) || object.is_a?(DateTime)) then
|
|
||||||
return CFDate.new(object)
|
|
||||||
elsif(object.is_a?(IO)) then
|
|
||||||
return CFData.new(object.read, CFData::DATA_RAW)
|
|
||||||
elsif(object.is_a?(Array)) then
|
|
||||||
ary = Array.new
|
|
||||||
object.each do
|
|
||||||
|o|
|
|
||||||
ary.push CFPropertyList.guess(o, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
return CFArray.new(ary)
|
|
||||||
elsif(object.is_a?(Hash)) then
|
|
||||||
hsh = Hash.new
|
|
||||||
object.each_pair do
|
|
||||||
|k,v|
|
|
||||||
k = k.to_s if k.is_a?(Symbol)
|
|
||||||
hsh[k] = CFPropertyList.guess(v, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
return CFDictionary.new(hsh)
|
|
||||||
elsif options[:converter_method] and object.respond_to?(options[:converter_method]) then
|
|
||||||
return CFPropertyList.guess(object.send(options[:converter_method]))
|
|
||||||
elsif options[:convert_unknown_to_string] then
|
|
||||||
return CFString.new(object.to_s)
|
|
||||||
else
|
|
||||||
raise CFTypeError.new("Unknown class #{object.class.to_s}! Try using :convert_unknown_to_string if you want to use unknown object types!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Converts a CFType hiercharchy to native Ruby types
|
|
||||||
def native_types(object,keys_as_symbols=false)
|
|
||||||
return if object.nil?
|
|
||||||
|
|
||||||
if(object.is_a?(CFDate) || object.is_a?(CFString) || object.is_a?(CFInteger) || object.is_a?(CFReal) || object.is_a?(CFBoolean)) then
|
|
||||||
return object.value
|
|
||||||
elsif(object.is_a?(CFData)) then
|
|
||||||
return object.decoded_value
|
|
||||||
elsif(object.is_a?(CFArray)) then
|
|
||||||
ary = []
|
|
||||||
object.value.each do
|
|
||||||
|v|
|
|
||||||
ary.push CFPropertyList.native_types(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
return ary
|
|
||||||
elsif(object.is_a?(CFDictionary)) then
|
|
||||||
hsh = {}
|
|
||||||
object.value.each_pair do
|
|
||||||
|k,v|
|
|
||||||
k = k.to_sym if keys_as_symbols
|
|
||||||
hsh[k] = CFPropertyList.native_types(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
return hsh
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module_function :guess, :native_types
|
|
||||||
|
|
||||||
class List
|
|
||||||
# Format constant for binary format
|
|
||||||
FORMAT_BINARY = 1
|
|
||||||
|
|
||||||
# Format constant for XML format
|
|
||||||
FORMAT_XML = 2
|
|
||||||
|
|
||||||
# Format constant for automatic format recognizing
|
|
||||||
FORMAT_AUTO = 0
|
|
||||||
|
|
||||||
@@parsers = [Binary,XML]
|
|
||||||
|
|
||||||
# Path of PropertyList
|
|
||||||
attr_accessor :filename
|
|
||||||
# Path of PropertyList
|
|
||||||
attr_accessor :format
|
|
||||||
# the root value in the plist file
|
|
||||||
attr_accessor :value
|
|
||||||
|
|
||||||
def initialize(opts={})
|
|
||||||
@filename = opts[:file]
|
|
||||||
@format = opts[:format] || FORMAT_AUTO
|
|
||||||
@data = opts[:data]
|
|
||||||
|
|
||||||
load(@filename) unless @filename.nil?
|
|
||||||
load_str(@data) unless @data.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load an XML PropertyList
|
|
||||||
# filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+
|
|
||||||
def load_xml(filename=nil)
|
|
||||||
load(filename,List::FORMAT_XML)
|
|
||||||
end
|
|
||||||
|
|
||||||
# read a binary plist file
|
|
||||||
# filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+
|
|
||||||
def load_binary(filename=nil)
|
|
||||||
load(filename,List::FORMAT_BINARY)
|
|
||||||
end
|
|
||||||
|
|
||||||
# load a plist from a XML string
|
|
||||||
# str:: The string containing the plist
|
|
||||||
def load_xml_str(str=nil)
|
|
||||||
load_str(str,List::FORMAT_XML)
|
|
||||||
end
|
|
||||||
|
|
||||||
# load a plist from a binary string
|
|
||||||
# str:: The string containing the plist
|
|
||||||
def load_binary_str(str=nil)
|
|
||||||
load_str(str,List::FORMAT_BINARY)
|
|
||||||
end
|
|
||||||
|
|
||||||
# load a plist from a string
|
|
||||||
# str = nil:: The string containing the plist
|
|
||||||
# format = nil:: The format of the plist
|
|
||||||
def load_str(str=nil,format=nil)
|
|
||||||
str = @data if str.nil?
|
|
||||||
format = @format if format.nil?
|
|
||||||
|
|
||||||
@value = {}
|
|
||||||
case format
|
|
||||||
when List::FORMAT_BINARY, List::FORMAT_XML then
|
|
||||||
prsr = @@parsers[format-1].new
|
|
||||||
@value = prsr.load({:data => str})
|
|
||||||
|
|
||||||
when List::FORMAT_AUTO then # what we now do is ugly, but neccessary to recognize the file format
|
|
||||||
filetype = str[0..5]
|
|
||||||
version = str[6..7]
|
|
||||||
|
|
||||||
prsr = nil
|
|
||||||
if filetype == "bplist" then
|
|
||||||
raise CFFormatError.new("Wong file version #{version}") unless version == "00"
|
|
||||||
prsr = Binary.new
|
|
||||||
else
|
|
||||||
prsr = XML.new
|
|
||||||
end
|
|
||||||
|
|
||||||
@value = prsr.load({:data => str})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Read a plist file
|
|
||||||
# file = nil:: The filename of the file to read. If nil, use +filename+ instance variable
|
|
||||||
# format = nil:: The format of the plist file. Auto-detect if nil
|
|
||||||
def load(file=nil,format=nil)
|
|
||||||
file = @filename if file.nil?
|
|
||||||
format = @format if format.nil?
|
|
||||||
@value = {}
|
|
||||||
|
|
||||||
raise IOError.new("File #{file} not readable!") unless File.readable? file
|
|
||||||
|
|
||||||
case format
|
|
||||||
when List::FORMAT_BINARY, List::FORMAT_XML then
|
|
||||||
prsr = @@parsers[format-1].new
|
|
||||||
@value = prsr.load({:file => file})
|
|
||||||
|
|
||||||
when List::FORMAT_AUTO then # what we now do is ugly, but neccessary to recognize the file format
|
|
||||||
magic_number = IO.read(file,8)
|
|
||||||
filetype = magic_number[0..5]
|
|
||||||
version = magic_number[6..7]
|
|
||||||
|
|
||||||
prsr = nil
|
|
||||||
if filetype == "bplist" then
|
|
||||||
raise CFFormatError.new("Wong file version #{version}") unless version == "00"
|
|
||||||
prsr = Binary.new
|
|
||||||
else
|
|
||||||
prsr = XML.new
|
|
||||||
end
|
|
||||||
|
|
||||||
@value = prsr.load({:file => file})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Serialize CFPropertyList object to specified format and write it to file
|
|
||||||
# file = nil:: The filename of the file to write to. Uses +filename+ instance variable if nil
|
|
||||||
# format = nil:: The format to save in. Uses +format+ instance variable if nil
|
|
||||||
def save(file=nil,format=nil,opts={})
|
|
||||||
format = @format if format.nil?
|
|
||||||
file = @filename if file.nil?
|
|
||||||
|
|
||||||
raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML") if format != FORMAT_BINARY && format != FORMAT_XML
|
|
||||||
|
|
||||||
if(!File.exists?(file)) then
|
|
||||||
raise IOError.new("File #{file} not writable!") unless File.writable?(File.dirname(file))
|
|
||||||
elsif(!File.writable?(file)) then
|
|
||||||
raise IOError.new("File #{file} not writable!")
|
|
||||||
end
|
|
||||||
|
|
||||||
opts[:root] = @value
|
|
||||||
prsr = @@parsers[format-1].new
|
|
||||||
content = prsr.to_str(opts)
|
|
||||||
|
|
||||||
File.open(file, 'wb') {
|
|
||||||
|fd|
|
|
||||||
fd.write content
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert plist to string
|
|
||||||
# format = List::FORMAT_BINARY:: The format to save the plist
|
|
||||||
# opts={}:: Pass parser options
|
|
||||||
def to_str(format=List::FORMAT_BINARY,opts={})
|
|
||||||
prsr = @@parsers[format-1].new
|
|
||||||
opts[:root] = @value
|
|
||||||
return prsr.to_str(opts)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Array
|
|
||||||
def to_plist(options={})
|
|
||||||
options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY
|
|
||||||
|
|
||||||
plist = CFPropertyList::List.new
|
|
||||||
plist.value = CFPropertyList.guess(self, options)
|
|
||||||
plist.to_str(options[:plist_format])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Hash
|
|
||||||
def to_plist(options={})
|
|
||||||
options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY
|
|
||||||
|
|
||||||
plist = CFPropertyList::List.new
|
|
||||||
plist.value = CFPropertyList.guess(self, options)
|
|
||||||
plist.to_str(options[:plist_format])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# eof
|
|
|
@ -1,233 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# CFTypes, e.g. CFString, CFInteger
|
|
||||||
# needed to create unambiguous plists
|
|
||||||
#
|
|
||||||
# Author:: Christian Kruse (mailto:cjk@wwwtech.de)
|
|
||||||
# Copyright:: Copyright (c) 2009
|
|
||||||
# License:: Distributes under the same terms as Ruby
|
|
||||||
|
|
||||||
require 'base64'
|
|
||||||
|
|
||||||
module CFPropertyList
|
|
||||||
# This class defines the base class for all CFType classes
|
|
||||||
#
|
|
||||||
class CFType
|
|
||||||
# value of the type
|
|
||||||
attr_accessor :value
|
|
||||||
|
|
||||||
|
|
||||||
# set internal value to parameter value by default
|
|
||||||
def initialize(value=nil)
|
|
||||||
@value = value
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert type to XML
|
|
||||||
def to_xml
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert type to binary
|
|
||||||
def to_binary(bplist)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This class holds string values, both, UTF-8 and UTF-16BE
|
|
||||||
# It will convert the value to UTF-16BE if necessary (i.e. if non-ascii char contained)
|
|
||||||
class CFString < CFType
|
|
||||||
# convert to XML
|
|
||||||
def to_xml
|
|
||||||
n = LibXML::XML::Node.new('string')
|
|
||||||
n << LibXML::XML::Node.new_text(@value) unless @value.nil?
|
|
||||||
return n
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to binary
|
|
||||||
def to_binary(bplist)
|
|
||||||
return bplist.string_to_binary(@value);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This class holds integer/fixnum values
|
|
||||||
class CFInteger < CFType
|
|
||||||
# convert to XML
|
|
||||||
def to_xml
|
|
||||||
return LibXML::XML::Node.new('integer') << LibXML::XML::Node.new_text(@value.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to binary
|
|
||||||
def to_binary(bplist)
|
|
||||||
return bplist.num_to_binary(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This class holds float values
|
|
||||||
class CFReal < CFType
|
|
||||||
# convert to XML
|
|
||||||
def to_xml
|
|
||||||
return LibXML::XML::Node.new('real') << LibXML::XML::Node.new_text(@value.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to binary
|
|
||||||
def to_binary(bplist)
|
|
||||||
return bplist.num_to_binary(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This class holds Time values. While Apple uses seconds since 2001,
|
|
||||||
# the rest of the world uses seconds since 1970. So if you access value
|
|
||||||
# directly, you get the Time class. If you access via get_value you either
|
|
||||||
# geht the timestamp or the Apple timestamp
|
|
||||||
class CFDate < CFType
|
|
||||||
TIMESTAMP_APPLE = 0
|
|
||||||
TIMESTAMP_UNIX = 1;
|
|
||||||
DATE_DIFF_APPLE_UNIX = 978307200
|
|
||||||
|
|
||||||
# create a XML date strimg from a time object
|
|
||||||
def CFDate.date_string(val)
|
|
||||||
# 2009-05-13T20:23:43Z
|
|
||||||
val.getutc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
end
|
|
||||||
|
|
||||||
# parse a XML date string
|
|
||||||
def CFDate.parse_date(val)
|
|
||||||
# 2009-05-13T20:23:43Z
|
|
||||||
val =~ %r{^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$}
|
|
||||||
year,month,day,hour,min,sec = $1, $2, $3, $4, $5, $6
|
|
||||||
return Time.utc(year,month,day,hour,min,sec).getlocal
|
|
||||||
end
|
|
||||||
|
|
||||||
# set value to defined state
|
|
||||||
def initialize(value = nil,format=CFDate::TIMESTAMP_UNIX)
|
|
||||||
if(value.is_a?(Time) || value.nil?) then
|
|
||||||
@value = value.nil? ? Time.now : value
|
|
||||||
else
|
|
||||||
set_value(value,format)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# set value with timestamp, either Apple or UNIX
|
|
||||||
def set_value(value,format=CFDate::TIMESTAMP_UNIX)
|
|
||||||
if(format == CFDate::TIMESTAMP_UNIX) then
|
|
||||||
@value = Time.at(value)
|
|
||||||
else
|
|
||||||
@value = Time.at(value + CFDate::DATE_DIFF_APPLE_UNIX)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# get timestamp, either UNIX or Apple timestamp
|
|
||||||
def get_value(format=CFDate::TIMESTAMP_UNIX)
|
|
||||||
if(format == CFDate::TIMESTAMP_UNIX) then
|
|
||||||
return @value.to_i
|
|
||||||
else
|
|
||||||
return @value.to_f - CFDate::DATE_DIFF_APPLE_UNIX
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to XML
|
|
||||||
def to_xml
|
|
||||||
return LibXML::XML::Node.new('date') << LibXML::XML::Node.new_text(CFDate::date_string(@value))
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to binary
|
|
||||||
def to_binary(bplist)
|
|
||||||
return bplist.date_to_binary(@value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This class contains a boolean value
|
|
||||||
class CFBoolean < CFType
|
|
||||||
# convert to XML
|
|
||||||
def to_xml
|
|
||||||
return LibXML::XML::Node.new(@value ? 'true' : 'false')
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to binary
|
|
||||||
def to_binary(bplist)
|
|
||||||
return bplist.bool_to_binary(@value);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This class contains binary data values
|
|
||||||
class CFData < CFType
|
|
||||||
# Base64 encoded data
|
|
||||||
DATA_BASE64 = 0
|
|
||||||
# Raw data
|
|
||||||
DATA_RAW = 1
|
|
||||||
|
|
||||||
# set value to defined state, either base64 encoded or raw
|
|
||||||
def initialize(value=nil,format=DATA_BASE64)
|
|
||||||
if(format == DATA_RAW) then
|
|
||||||
@value = Base64.encode64(value)
|
|
||||||
else
|
|
||||||
@value = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# get base64 decoded value
|
|
||||||
def decoded_value
|
|
||||||
return Base64.decode64(@value)
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to XML
|
|
||||||
def to_xml
|
|
||||||
return LibXML::XML::Node.new('data') << LibXML::XML::Node.new_text(@value)
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to binary
|
|
||||||
def to_binary(bplist)
|
|
||||||
return bplist.data_to_binary(decoded_value())
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This class contains an array of values
|
|
||||||
class CFArray < CFType
|
|
||||||
# create a new array CFType
|
|
||||||
def initialize(val=[])
|
|
||||||
@value = val
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to XML
|
|
||||||
def to_xml
|
|
||||||
n = LibXML::XML::Node.new('array')
|
|
||||||
@value.each do
|
|
||||||
|v|
|
|
||||||
n << v.to_xml
|
|
||||||
end
|
|
||||||
|
|
||||||
return n
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to binary
|
|
||||||
def to_binary(bplist)
|
|
||||||
return bplist.array_to_binary(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# this class contains a hash of values
|
|
||||||
class CFDictionary < CFType
|
|
||||||
# Create new CFDictonary type.
|
|
||||||
def initialize(value={})
|
|
||||||
@value = value
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to XML
|
|
||||||
def to_xml
|
|
||||||
n = LibXML::XML::Node.new('dict')
|
|
||||||
@value.each_pair do
|
|
||||||
|key,value|
|
|
||||||
k = LibXML::XML::Node.new('key') << LibXML::XML::Node.new_text(key)
|
|
||||||
n << k
|
|
||||||
n << value.to_xml
|
|
||||||
end
|
|
||||||
|
|
||||||
return n
|
|
||||||
end
|
|
||||||
|
|
||||||
# convert to binary
|
|
||||||
def to_binary(bplist)
|
|
||||||
return bplist.dict_to_binary(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# eof
|
|
|
@ -1,121 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# CFPropertyList implementation
|
|
||||||
# parser class to read, manipulate and write XML property list files (plist(5)) as defined by Apple
|
|
||||||
#
|
|
||||||
# Author:: Christian Kruse (mailto:cjk@wwwtech.de)
|
|
||||||
# Copyright:: Copyright (c) 2010
|
|
||||||
# License:: Distributes under the same terms as Ruby
|
|
||||||
|
|
||||||
module CFPropertyList
|
|
||||||
# XML parser
|
|
||||||
class XML < ParserInterface
|
|
||||||
# read a XML file
|
|
||||||
# opts::
|
|
||||||
# * :file - The filename of the file to load
|
|
||||||
# * :data - The data to parse
|
|
||||||
def load(opts)
|
|
||||||
if(opts.has_key?(:file)) then
|
|
||||||
doc = LibXML::XML::Document.file(opts[:file],:options => LibXML::XML::Parser::Options::NOBLANKS|LibXML::XML::Parser::Options::NOENT)
|
|
||||||
else
|
|
||||||
doc = LibXML::XML::Document.string(opts[:data],:options => LibXML::XML::Parser::Options::NOBLANKS|LibXML::XML::Parser::Options::NOENT)
|
|
||||||
end
|
|
||||||
|
|
||||||
root = doc.root.first
|
|
||||||
return import_xml(root)
|
|
||||||
end
|
|
||||||
|
|
||||||
# serialize CFPropertyList object to XML
|
|
||||||
# opts = {}:: Specify options: :formatted - Use indention and line breaks
|
|
||||||
def to_str(opts={})
|
|
||||||
doc = LibXML::XML::Document.new
|
|
||||||
|
|
||||||
doc.root = LibXML::XML::Node.new('plist')
|
|
||||||
doc.encoding = LibXML::XML::Encoding::UTF_8
|
|
||||||
|
|
||||||
doc.root['version'] = '1.0'
|
|
||||||
doc.root << opts[:root].to_xml()
|
|
||||||
|
|
||||||
# ugly hack, but there's no other possibility I know
|
|
||||||
str = doc.to_s(:indent => opts[:formatted])
|
|
||||||
str1 = String.new
|
|
||||||
first = false
|
|
||||||
str.each_line do
|
|
||||||
|line|
|
|
||||||
str1 << line
|
|
||||||
unless(first) then
|
|
||||||
str1 << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" if line =~ /^\s*<\?xml/
|
|
||||||
end
|
|
||||||
|
|
||||||
first = true
|
|
||||||
end
|
|
||||||
|
|
||||||
return str1
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# get the value of a DOM node
|
|
||||||
def get_value(n)
|
|
||||||
return n.first.content if n.children?
|
|
||||||
return n.content
|
|
||||||
end
|
|
||||||
|
|
||||||
# import the XML values
|
|
||||||
def import_xml(node)
|
|
||||||
ret = nil
|
|
||||||
|
|
||||||
case node.name
|
|
||||||
when 'dict'
|
|
||||||
hsh = Hash.new
|
|
||||||
key = nil
|
|
||||||
|
|
||||||
if node.children? then
|
|
||||||
node.children.each do
|
|
||||||
|n|
|
|
||||||
|
|
||||||
if n.name == "key" then
|
|
||||||
key = get_value(n)
|
|
||||||
else
|
|
||||||
raise CFFormatError.new("Format error!") if key.nil?
|
|
||||||
hsh[key] = import_xml(n)
|
|
||||||
key = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ret = CFDictionary.new(hsh)
|
|
||||||
|
|
||||||
when 'array'
|
|
||||||
ary = Array.new
|
|
||||||
|
|
||||||
if node.children? then
|
|
||||||
node.children.each do
|
|
||||||
|n|
|
|
||||||
ary.push import_xml(n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ret = CFArray.new(ary)
|
|
||||||
|
|
||||||
when 'true'
|
|
||||||
ret = CFBoolean.new(true)
|
|
||||||
when 'false'
|
|
||||||
ret = CFBoolean.new(false)
|
|
||||||
when 'real'
|
|
||||||
ret = CFReal.new(get_value(node).to_f)
|
|
||||||
when 'integer'
|
|
||||||
ret = CFInteger.new(get_value(node).to_i)
|
|
||||||
when 'string'
|
|
||||||
ret = CFString.new(get_value(node))
|
|
||||||
when 'data'
|
|
||||||
ret = CFData.new(get_value(node))
|
|
||||||
when 'date'
|
|
||||||
ret = CFDate.new(CFDate.parse_date(get_value(node)))
|
|
||||||
end
|
|
||||||
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# eof
|
|
Reference in a new issue