This repository has been archived on 2025-08-18. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
plistifier/lib/cfpropertylist/rbCFPropertyList.rb
2010-06-18 23:23:00 +02:00

316 lines
9.2 KiB
Ruby

# -*- 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