removed cfpropertylist from the source

This commit is contained in:
Jeena Paradies 2010-07-06 18:46:11 +02:00
parent 9363d5fdb5
commit a507736fac
7 changed files with 0 additions and 1411 deletions

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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