# -*- 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 Rubyrequire'libxml'require'kconv'require'date'moduleCFPropertyList# interface class for PList parsersclassParserInterface# load a plistdefload(opts={})return""end# convert a plist to stringdefto_str(opts={})returntrueendendenddirname=File.dirname(__FILE__)requiredirname+'/rbCFPlistError.rb'requiredirname+'/rbCFTypes.rb'requiredirname+'/rbXMLCFPropertyList.rb'requiredirname+'/rbBinaryCFPropertyList.rb'require'iconv'unless"".respond_to?("encode")moduleCFPropertyList# 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+:: Convert unknown objects to string calling to_str()# +converter_method+:: Convert unknown objects to known objects calling +method_name+## cftypes = CFPropertyList.guess(x,:convert_unknown_to_string => true,:converter_method => :to_hash)defguess(object,options={})if(object.is_a?(Fixnum)||object.is_a?(Integer))thenreturnCFInteger.new(object)elsif(object.is_a?(Float)||(Object.const_defined?('BigDecimal')andobject.is_a?(BigDecimal)))thenreturnCFReal.new(object)elsif(object.is_a?(TrueClass)||object.is_a?(FalseClass))thenreturnCFBoolean.new(object)elsif(object.is_a?(String))thenreturnCFString.new(object)elsif(object.is_a?(Time)||object.is_a?(DateTime))thenreturnCFDate.new(object)elsif(object.is_a?(IO))thenreturnCFData.new(object.read,CFData::DATA_RAW)elsif(object.is_a?(Array))thenary=Array.newobject.eachdo|o|ary.pushCFPropertyList.guess(o,options)endreturnCFArray.new(ary)elsif(object.is_a?(Hash))thenhsh=Hash.newobject.each_pairdo|k,v|k=k.to_sifk.is_a?(Symbol)hsh[k]=CFPropertyList.guess(v,options)endreturnCFDictionary.new(hsh)elsifoptions[:converter_method]andobject.respond_to?(options[:converter_method])thenreturnCFPropertyList.guess(object.send(options[:converter_method]))elsifoptions[:convert_unknown_to_string]thenreturnCFString.new(object.to_s)elseraiseCFTypeError.new("Unknown class #{object.class.to_s}! Try using :convert_unknown_to_string if you want to use unknown object types!")endend# Converts a CFType hiercharchy to native Ruby typesdefnative_types(object,keys_as_symbols=false)returnifobject.nil?if(object.is_a?(CFDate)||object.is_a?(CFString)||object.is_a?(CFInteger)||object.is_a?(CFReal)||object.is_a?(CFBoolean))thenreturnobject.valueelsif(object.is_a?(CFData))thenreturnobject.decoded_valueelsif(object.is_a?(CFArray))thenary=[]object.value.eachdo|v|ary.pushCFPropertyList.native_types(v)endreturnaryelsif(object.is_a?(CFDictionary))thenhsh={}object.value.each_pairdo|k,v|k=k.to_symifkeys_as_symbolshsh[k]=CFPropertyList.native_types(v)endreturnhshendendmodule_function:guess,:native_typesclassList# Format constant for binary formatFORMAT_BINARY=1# Format constant for XML formatFORMAT_XML=2# Format constant for automatic format recognizingFORMAT_AUTO=0@@parsers=[Binary,XML]# Path of PropertyListattr_accessor:filename# Path of PropertyListattr_accessor:format# the root value in the plist fileattr_accessor:valuedefinitialize(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+defload_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+defload_binary(filename=nil)load(filename,List::FORMAT_BINARY)end# load a plist from a XML string# str:: The string containing the plistdefload_xml_str(str=nil)load_str(str,List::FORMAT_XML)end# load a plist from a binary string# str:: The string containing the plistdefload_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 plistdefload_str(str=nil,format=nil)str=@dataifstr.nil?format=@formatifformat.nil?@value={}caseformatwhenList::FORMAT_BINARY,List::FORMAT_XMLthenprsr=@@parsers[format-1].new@value=prsr.load({:data=>str})whenList::FORMAT_AUTOthen# what we now do is ugly, but neccessary to recognize the file formatfiletype=str[0..5]version=str[6..7]prsr=niliffiletype=="bplist"thenraiseCFFormatError.new("Wong file version #{version}")unlessversion=="00"prsr=Binary.newelseprsr=XML.newend@value=prsr.load({:data=>str})endend# 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 nildefload(file=nil,format=nil)file=@filenameiffile.nil?format=@formatifformat.nil?@value={}raiseIOError.new("File #{file} not readable!")unlessFile.readable?filecaseformatwhenList::FORMAT_BINARY,List::FORMAT_XMLthenprsr=@@parsers[format-1].new@value=prsr.load({:file=>file})whenList::FORMAT_AUTOthen# what we now do is ugly, but neccessary to recognize the file formatmagic_number=IO.read(file,8)filetype=magic_number[0..5]version=magic_number[6..7]prsr=niliffiletype=="bplist"thenraiseCFFormatError.new("Wong file version #{version}")unlessversion=="00"prsr=Binary.newelseprsr=XML.newend@value=prsr.load({:file=>file})endend# 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 nildefsave(file=nil,format=nil,opts={})format=@formatifformat.nil?file=@filenameiffile.nil?raiseCFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML")ifformat!=FORMAT_BINARY&&format!=FORMAT_XMLif(!File.exists?(file))thenraiseIOError.new("File #{file} not writable!")unlessFile.writable?(File.dirname(file))elsif(!File.writable?(file))thenraiseIOError.new("File #{file} not writable!")endopts[:root]=@valueprsr=@@parsers[format-1].newcontent=prsr.to_str(opts)File.open(file,'wb'){|fd|fd.writecontent}end# convert plist to string# format = List::FORMAT_BINARY:: The format to save the plist# opts={}:: Pass parser optionsdefto_str(format=List::FORMAT_BINARY,opts={})prsr=@@parsers[format-1].newopts[:root]=@valuereturnprsr.to_str(opts)endendendclassArraydefto_plist(options={})options[:plist_format]||=CFPropertyList::List::FORMAT_BINARYplist=CFPropertyList::List.newplist.value=CFPropertyList.guess(self,options)plist.to_str(options[:plist_format])endendclassHashdefto_plist(options={})options[:plist_format]||=CFPropertyList::List::FORMAT_BINARYplist=CFPropertyList::List.newplist.value=CFPropertyList.guess(self,options)plist.to_str(options[:plist_format])endend# eof