CFPropertyList-3.0.6/0000755000004100000410000000000014364771233014453 5ustar www-datawww-dataCFPropertyList-3.0.6/CFPropertyList.gemspec0000644000004100000410000000426514364771233020720 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: CFPropertyList 3.0.6 ruby lib Gem::Specification.new do |s| s.name = "CFPropertyList".freeze s.version = "3.0.6" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Christian Kruse".freeze] s.date = "2023-01-25" s.description = "This is a module to read, write and manipulate both binary and XML property lists as defined by apple.".freeze s.email = "cjk@defunct.ch".freeze s.extra_rdoc_files = ["README.rdoc".freeze] s.files = ["LICENSE".freeze, "README.md".freeze, "README.rdoc".freeze, "THANKS".freeze, "lib/cfpropertylist.rb".freeze, "lib/cfpropertylist/rbBinaryCFPropertyList.rb".freeze, "lib/cfpropertylist/rbCFPlistError.rb".freeze, "lib/cfpropertylist/rbCFPropertyList.rb".freeze, "lib/cfpropertylist/rbCFTypes.rb".freeze, "lib/cfpropertylist/rbLibXMLParser.rb".freeze, "lib/cfpropertylist/rbNokogiriParser.rb".freeze, "lib/cfpropertylist/rbPlainCFPropertyList.rb".freeze, "lib/cfpropertylist/rbREXMLParser.rb".freeze] s.homepage = "https://github.com/ckruse/CFPropertyList".freeze s.licenses = ["MIT".freeze] s.rubygems_version = "3.2.5".freeze s.summary = "Read, write and manipulate both binary and XML property lists as defined by apple".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0.7.0"]) s.add_runtime_dependency(%q.freeze, [">= 0"]) else s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0.7.0"]) s.add_dependency(%q.freeze, [">= 0"]) end end CFPropertyList-3.0.6/README.md0000644000004100000410000000301114364771233015725 0ustar www-datawww-dataCFPropertyList implementation class to read, manipulate and write both XML and binary property list files (plist(5)) as defined by Apple. Have a look at CFPropertyList::List for more documentation. # Caution! In version 3.0.0 we dropped Ruby 1.8 compatibility. If you are using Ruby 1.8 consider to update Ruby; if you can't upgrade, don't upgrade CFPropertyList. # Installation You could either use ruby gems and install it via ```bash gem install CFPropertyList ``` or you could clone this repository and place it somewhere in your load path. Example: ```ruby require 'cfpropertylist' ``` If you're using Rails, you can add it into your Gemfile ```ruby gem 'CFPropertyList' ``` # Usage ## create a arbitrary data structure of basic data types ```ruby data = { 'name' => 'John Doe', 'missing' => true, 'last_seen' => Time.now, 'friends' => ['Jane Doe','Julian Doe'], 'likes' => { 'me' => false } } ``` ## create CFPropertyList::List object ```ruby plist = CFPropertyList::List.new ``` ## call CFPropertyList.guess() to create corresponding CFType values ```ruby plist.value = CFPropertyList.guess(data) ``` ## write plist to file ```ruby plist.save("example.plist", CFPropertyList::List::FORMAT_BINARY) ``` ## … later, read it again ```ruby plist = CFPropertyList::List.new(:file => "example.plist") data = CFPropertyList.native_types(plist.value) ``` # Author and license **Author:** Christian Kruse (mailto:cjk@wwwtech.de) **Copyright:** Copyright (c) 2010 **License:** MIT License CFPropertyList-3.0.6/LICENSE0000644000004100000410000000206414364771233015462 0ustar www-datawww-dataCopyright (c) 2010 Christian Kruse, 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. CFPropertyList-3.0.6/lib/0000755000004100000410000000000014364771233015221 5ustar www-datawww-dataCFPropertyList-3.0.6/lib/cfpropertylist/0000755000004100000410000000000014364771233020312 5ustar www-datawww-dataCFPropertyList-3.0.6/lib/cfpropertylist/rbNokogiriParser.rb0000644000004100000410000000745014364771233024127 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'nokogiri' module CFPropertyList # XML parser class NokogiriXMLParser < ParserInterface PARSER_OPTIONS = Nokogiri::XML::ParseOptions::NOBLANKS|Nokogiri::XML::ParseOptions::NONET # read a XML file # opts:: # * :file - The filename of the file to load # * :data - The data to parse def load(opts) doc = nil if(opts.has_key?(:file)) then File.open(opts[:file], "rb") { |fd| doc = Nokogiri::XML::Document.parse(fd, nil, nil, PARSER_OPTIONS) } else doc = Nokogiri::XML::Document.parse(opts[:data], nil, nil, PARSER_OPTIONS) end if doc root = doc.root.children.first return import_xml(root) end rescue Nokogiri::XML::SyntaxError => e raise CFFormatError.new('invalid XML: ' + e.message) end # serialize CFPropertyList object to XML # opts = {}:: Specify options: :formatted - Use indention and line breaks def to_str(opts={}) doc = Nokogiri::XML::Document.new @doc = doc doc.root = doc.create_element 'plist', :version => '1.0' doc.encoding = 'UTF-8' doc.root << opts[:root].to_xml(self) # ugly hack, but there's no other possibility I know s_opts = Nokogiri::XML::Node::SaveOptions::AS_XML s_opts |= Nokogiri::XML::Node::SaveOptions::FORMAT if opts[:formatted] str = doc.serialize(:save_with => s_opts) str1 = String.new first = false str.each_line do |line| str1 << line unless(first) then str1 << "\n" if line =~ /^\s*<\?xml/ end first = true end str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) return str1 end def new_node(name) @doc.create_element name end def new_text(val) @doc.create_text_node val end def append_node(parent, child) parent << child end protected # get the value of a DOM node def get_value(n) content = if n.children.empty? n.content else n.children.first.content end content.force_encoding('UTF-8') if content.respond_to?(:force_encoding) content end # import the XML values def import_xml(node) ret = nil case node.name when 'dict' hsh = Hash.new key = nil children = node.children unless children.empty? then children.each do |n| next if n.text? # avoid a bug of libxml next if n.comment? 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 if hsh['CF$UID'] and hsh.keys.length == 1 ret = CFUid.new(hsh['CF$UID'].value) else ret = CFDictionary.new(hsh) end when 'array' ary = Array.new children = node.children unless children.empty? then children.each do |n| next if n.text? # avoid a bug of libxml next if n.comment? 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 CFPropertyList-3.0.6/lib/cfpropertylist/rbCFPropertyList.rb0000644000004100000410000003254714364771233024067 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'kconv' require 'date' require 'time' # # CFPropertyList implementation # # class to read, manipulate and write both XML and binary property list # files (plist(5)) as defined by Apple. Have a look at CFPropertyList::List # for more documentation. # # == Example # require 'cfpropertylist' # # # 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:: MIT License module CFPropertyList class << self attr_accessor :xml_parser_interface end # 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 class XMLParserInterface < ParserInterface def new_node(name) end def new_text(val) end def append_node(parent, child) end end end dirname = File.dirname(__FILE__) require dirname + '/rbCFPlistError.rb' require dirname + '/rbCFTypes.rb' require dirname + '/rbBinaryCFPropertyList.rb' require dirname + '/rbPlainCFPropertyList.rb' begin require dirname + '/rbLibXMLParser.rb' temp = LibXML::XML::Parser::Options::NOBLANKS # check if we have a version with parser options temp = false # avoid a warning try_nokogiri = false CFPropertyList.xml_parser_interface = CFPropertyList::LibXMLParser rescue LoadError, NameError try_nokogiri = true end if try_nokogiri then begin require dirname + '/rbNokogiriParser.rb' CFPropertyList.xml_parser_interface = CFPropertyList::NokogiriXMLParser rescue LoadError require dirname + '/rbREXMLParser.rb' CFPropertyList.xml_parser_interface = CFPropertyList::ReXMLParser end end 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+:: 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, :converter_with_opts => true) def guess(object, options = {}) case object when Integer then CFInteger.new(object) when UidFixnum then CFUid.new(object) when Float then CFReal.new(object) when TrueClass, FalseClass then CFBoolean.new(object) when Blob CFData.new(object, CFData::DATA_RAW) when String, Symbol CFString.new(object.to_s) when Time, DateTime, Date CFDate.new(object) when Array, Enumerator ary = Array.new object.each do |o| ary.push CFPropertyList.guess(o, options) end CFArray.new(ary) when Hash 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 CFDictionary.new(hsh) else case when Object.const_defined?('BigDecimal') && object.is_a?(BigDecimal) CFReal.new(object) when object.respond_to?(:read) raw_data = object.read # treat the data as a bytestring (ASCII-8BIT) if Ruby supports it. Do this by forcing # the encoding, on the assumption that the bytes were read correctly, and just tagged with # an inappropriate encoding, rather than transcoding. raw_data.force_encoding(Encoding::ASCII_8BIT) if raw_data.respond_to?(:force_encoding) CFData.new(raw_data, CFData::DATA_RAW) when options[:converter_method] && object.respond_to?(options[:converter_method]) if options[:converter_with_opts] CFPropertyList.guess(object.send(options[:converter_method],options),options) else CFPropertyList.guess(object.send(options[:converter_method]),options) end when options[:convert_unknown_to_string] 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 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)) || object.is_a?(CFUid) then return object.value elsif(object.is_a?(CFData)) then return CFPropertyList::Blob.new(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 representing a CFPropertyList. Instantiate with #new class List # Format constant for binary format FORMAT_BINARY = 1 # Format constant for XML format FORMAT_XML = 2 # Format constant for the old plain format FORMAT_PLAIN = 3 # Format constant for automatic format recognizing FORMAT_AUTO = 0 @@parsers = [Binary, CFPropertyList.xml_parser_interface, PlainParser] # Path of PropertyList attr_accessor :filename # the original format of the PropertyList attr_accessor :format # the root value in the plist file attr_accessor :value # default value for XML generation; if true generate formatted XML attr_accessor :formatted # initialize a new CFPropertyList, arguments are: # # :file:: Parse a file # :format:: Format is one of FORMAT_BINARY or FORMAT_XML. Defaults to FORMAT_AUTO # :data:: Parse a string # # All arguments are optional def initialize(opts={}) @filename = opts[:file] @format = opts[:format] || FORMAT_AUTO @data = opts[:data] @formatted = opts[:formatted] load(@filename) unless @filename.nil? load_str(@data) unless @data.nil? end # returns a list of registered parsers def self.parsers @@parsers end # set a list of parsers def self.parsers=(val) @@parsers = val 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 # read a plain plist file # filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+ def load_plain(filename=nil) load(filename,List::FORMAT_PLAIN) 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 plain string # str:: The string containing the plist def load_plain_str(str=nil) load_str(str,List::FORMAT_PLAIN) 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, List::FORMAT_PLAIN 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("Wrong file version #{version}") unless version == "00" prsr = Binary.new @format = List::FORMAT_BINARY else if str =~ /^<(\?xml|!DOCTYPE|plist)/ prsr = CFPropertyList.xml_parser_interface.new @format = List::FORMAT_XML else prsr = PlainParser.new @format = List::FORMAT_PLAIN end 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, List::FORMAT_PLAIN 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,12) raise IOError.new("File #{file} is empty.") unless magic_number filetype = magic_number[0..5] version = magic_number[6..7] prsr = nil if filetype == "bplist" then raise CFFormatError.new("Wrong file version #{version}") unless version == "00" prsr = Binary.new @format = List::FORMAT_BINARY else if magic_number =~ /^<(\?xml|!DOCTYPE|plist)/ prsr = CFPropertyList.xml_parser_interface.new @format = List::FORMAT_XML else prsr = PlainParser.new @format = List::FORMAT_PLAIN end end @value = prsr.load({:file => file}) end raise CFFormatError.new("Invalid format or parser error!") if @value.nil? 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? if format != FORMAT_BINARY && format != FORMAT_XML && format != FORMAT_PLAIN raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML") end if(!File.exist?(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 opts[:formatted] = @formatted unless opts.has_key?(:formatted) 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={}) if format != FORMAT_BINARY && format != FORMAT_XML && format != FORMAT_PLAIN raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML") end prsr = @@parsers[format-1].new opts[:root] = @value opts[:formatted] = @formatted unless opts.has_key?(:formatted) return prsr.to_str(opts) end end end class Array # convert an array to plist format 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], options) end end class Enumerator # convert an array to plist format 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], options) end end class Hash # convert a hash to plist format 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], options) end end # eof CFPropertyList-3.0.6/lib/cfpropertylist/rbLibXMLParser.rb0000644000004100000410000000720714364771233023435 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'libxml' module CFPropertyList # XML parser class LibXMLParser < XMLParserInterface LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER) PARSER_OPTIONS = LibXML::XML::Parser::Options::NOBLANKS|LibXML::XML::Parser::Options::NONET # read a XML file # opts:: # * :file - The filename of the file to load # * :data - The data to parse def load(opts) doc = nil if(opts.has_key?(:file)) then doc = LibXML::XML::Document.file(opts[:file],:options => PARSER_OPTIONS) else doc = LibXML::XML::Document.string(opts[:data],:options => PARSER_OPTIONS) end if doc root = doc.root.first return import_xml(root) end rescue LibXML::XML::Error => e raise CFFormatError.new('invalid XML: ' + e.message) 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(self) # 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 << "\n" if line =~ /^\s*<\?xml/ end first = true end str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) return str1 end def new_node(name) LibXML::XML::Node.new(name) end def new_text(val) LibXML::XML::Node.new_text(val) end def append_node(parent, child) parent << child end protected # get the value of a DOM node def get_value(n) content = if n.children? n.first.content else n.content end content.force_encoding('UTF-8') if content.respond_to?(:force_encoding) 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| next if n.text? # avoid a bug of libxml next if n.comment? 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 if hsh['CF$UID'] and hsh.keys.length == 1 ret = CFUid.new(hsh['CF$UID'].value) else ret = CFDictionary.new(hsh) end when 'array' ary = Array.new if node.children? then node.children.each do |n| next if n.text? # avoid a bug of libxml next if n.comment? 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 CFPropertyList-3.0.6/lib/cfpropertylist/rbPlainCFPropertyList.rb0000644000004100000410000001067614364771233025052 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'strscan' module CFPropertyList # XML parser class PlainParser < XMLParserInterface # read a XML file # opts:: # * :file - The filename of the file to load # * :data - The data to parse def load(opts) @doc = nil if(opts.has_key?(:file)) then File.open(opts[:file], :external_encoding => "ASCII") do |fd| @doc = StringScanner.new(fd.read) end else @doc = StringScanner.new(opts[:data]) end if @doc root = import_plain raise CFFormatError.new('content after root object') unless @doc.eos? return root end raise CFFormatError.new('invalid plist string or file not found') end SPACES_AND_COMMENTS = %r{((?:/\*.*?\*/)|(?://.*?$\n?)|(?:\s*))+}x # serialize CFPropertyList object to XML # opts = {}:: Specify options: :formatted - Use indention and line breaks def to_str(opts={}) opts[:root].to_plain(self) end protected def skip_whitespaces @doc.skip SPACES_AND_COMMENTS end def read_dict skip_whitespaces hsh = {} while not @doc.scan(/\}/) key = import_plain raise CFFormatError.new("invalid dictionary format") if !key if key.is_a?(CFString) key = key.value elsif key.is_a?(CFInteger) or key.is_a?(CFReal) key = key.value.to_s else raise CFFormatError.new("invalid key format") end skip_whitespaces raise CFFormatError.new("invalid dictionary format") unless @doc.scan(/=/) skip_whitespaces val = import_plain skip_whitespaces raise CFFormatError.new("invalid dictionary format") unless @doc.scan(/;/) skip_whitespaces hsh[key] = val raise CFFormatError.new("invalid dictionary format") if @doc.eos? end CFDictionary.new(hsh) end def read_array skip_whitespaces ary = [] while not @doc.scan(/\)/) val = import_plain return nil if not val or not val.value skip_whitespaces if not @doc.skip(/,\s*/) if @doc.scan(/\)/) ary << val return CFArray.new(ary) end raise CFFormatError.new("invalid array format") end ary << val raise CFFormatError.new("invalid array format") if @doc.eos? end CFArray.new(ary) end def escape_char case @doc.matched when '"' '"' when '\\' '\\' when 'a' "\a" when 'b' "\b" when 'f' "\f" when 'n' "\n" when 'v' "\v" when 'r' "\r" when 't' "\t" when 'U' @doc.scan(/.{4}/).hex.chr('utf-8') end end def read_quoted str = '' while not @doc.scan(/"/) if @doc.scan(/\\/) @doc.scan(/./) str << escape_char elsif @doc.eos? raise CFFormatError.new("unterminated string") else @doc.scan(/./) str << @doc.matched end end CFString.new(str) end def read_unquoted raise CFFormatError.new("unexpected end of file") if @doc.eos? if @doc.scan(/(\d\d\d\d)-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)(?:\s+(\+|-)(\d\d)(\d\d))?/) year,month,day,hour,min,sec,pl_min,tz_hour, tz_min = @doc[1], @doc[2], @doc[3], @doc[4], @doc[5], @doc[6], @doc[7], @doc[8], @doc[9] CFDate.new(Time.new(year, month, day, hour, min, sec, pl_min ? sprintf("%s%s:%s", pl_min, tz_hour, tz_min) : nil)) elsif @doc.scan(/-?\d+?\.\d+\b/) CFReal.new(@doc.matched.to_f) elsif @doc.scan(/-?\d+\b/) CFInteger.new(@doc.matched.to_i) elsif @doc.scan(/\b(true|false)\b/) CFBoolean.new(@doc.matched == 'true') else CFString.new(@doc.scan(/\w+/)) end end def read_binary @doc.scan(/(.*?)>/) hex_str = @doc[1].gsub(/ /, '') CFData.new([hex_str].pack("H*"), CFData::DATA_RAW) end # import the XML values def import_plain skip_whitespaces ret = nil if @doc.scan(/\{/) # dict ret = read_dict elsif @doc.scan(/\(/) # array ret = read_array elsif @doc.scan(/"/) # string ret = read_quoted elsif @doc.scan(/ e raise CFFormatError.new('invalid XML: ' + e.message) end # serialize CFPropertyList object to XML # opts = {}:: Specify options: :formatted - Use indention and line breaks def to_str(opts={}) doc = REXML::Document.new @doc = doc doc.context[:attribute_quote] = :quote doc.add_element 'plist', {'version' => '1.0'} doc.root << opts[:root].to_xml(self) formatter = if opts[:formatted] then f = REXML::Formatters::Pretty.new(2) f.compact = true f.width = Float::INFINITY f else REXML::Formatters::Default.new end str = formatter.write(doc.root, "") str1 = "\n\n" + str + "\n" str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) return str1 end def new_node(name) REXML::Element.new(name) end def new_text(val) val end def append_node(parent, child) if child.is_a?(String) then parent.add_text child else parent.elements << child end parent end protected # get the value of a DOM node def get_value(n) content = n.text content.force_encoding('UTF-8') if content.respond_to?(:force_encoding) content end # import the XML values def import_xml(node) ret = nil case node.name when 'dict' hsh = Hash.new key = nil if node.has_elements? then node.elements.each do |n| next if n.name == '#text' # avoid a bug of libxml next if n.name == '#comment' if n.name == "key" then key = get_value(n) key = '' if key.nil? # REXML returns nil if key is empty else raise CFFormatError.new("Format error!") if key.nil? hsh[key] = import_xml(n) key = nil end end end if hsh['CF$UID'] and hsh.keys.length == 1 ret = CFUid.new(hsh['CF$UID'].value) else ret = CFDictionary.new(hsh) end when 'array' ary = Array.new if node.has_elements? then node.elements.each do |n| next if n.name == '#text' # avoid a bug of libxml 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)) ret.value = '' if ret.value.nil? # REXML returns nil for empty elements' .text attribute 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 CFPropertyList-3.0.6/lib/cfpropertylist/rbCFTypes.rb0000644000004100000410000001746414364771233022514 0ustar www-datawww-data# -*- 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:: MIT License require 'base64' module CFPropertyList ## # Blob is intended to distinguish between a Ruby String instance that should # be converted to a CFString type and a Ruby String instance that should be # converted to a CFData type class Blob < String end ## # UidFixnum is intended to distinguish between a Ruby Integer # instance that should be converted to a CFInteger/CFReal type and a # Ruby Integer instance that should be converted to a CFUid type. class UidFixnum < Integer end # This class defines the base class for all CFType classes # class CFType # value of the type attr_accessor :value def initialize(value=nil) @value = value end def to_xml(parser) end def to_binary(bplist) end def to_plain(plist) 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(parser) n = parser.new_node('string') n = parser.append_node(n, parser.new_text(@value)) unless @value.nil? n end # convert to binary def to_binary(bplist) bplist.string_to_binary(@value); end def to_plain(plist) if @value =~ /^\w+$/ @value else quoted end end def quoted str = '"' @value.each_char do |c| str << case c when '"' '\\"' when '\\' '\\' when "\a" "\\a" when "\b" "\\b" when "\f" "\\f" when "\n" "\n" when "\v" "\\v" when "\r" "\\r" when "\t" "\\t" else c end end str << '"' end end # This class holds integer/fixnum values class CFInteger < CFType # convert to XML def to_xml(parser) n = parser.new_node('integer') n = parser.append_node(n, parser.new_text(@value.to_s)) n end # convert to binary def to_binary(bplist) bplist.num_to_binary(self) end def to_plain(plist) @value.to_s end end # This class holds float values class CFReal < CFType # convert to XML def to_xml(parser) n = parser.new_node('real') n = parser.append_node(n, parser.new_text(@value.to_s)) n end # convert to binary def to_binary(bplist) bplist.num_to_binary(self) end def to_plain(plist) @value.to_s 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 elsif value.instance_of? Date @value = Time.utc(value.year, value.month, value.day, 0, 0, 0) elsif value.instance_of? DateTime @value = value.to_time.utc 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 @value.to_i else @value.to_f - CFDate::DATE_DIFF_APPLE_UNIX end end # convert to XML def to_xml(parser) n = parser.new_node('date') n = parser.append_node(n, parser.new_text(CFDate::date_string(@value))) n end # convert to binary def to_binary(bplist) bplist.date_to_binary(@value) end def to_plain(plist) @value.strftime("%Y-%m-%d %H:%M:%S %z") end end # This class contains a boolean value class CFBoolean < CFType # convert to XML def to_xml(parser) parser.new_node(@value ? 'true' : 'false') end # convert to binary def to_binary(bplist) bplist.bool_to_binary(@value); end def to_plain(plist) @value ? "true" : "false" 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) @raw_value = value else @value = value end end # get base64 encoded value def encoded_value @value ||= "\n#{Base64.encode64(@raw_value).gsub("\n", '').scan(/.{1,76}/).join("\n")}\n" end # get base64 decoded value def decoded_value @raw_value ||= Blob.new(Base64.decode64(@value)) end # convert to XML def to_xml(parser) n = parser.new_node('data') n = parser.append_node(n, parser.new_text(encoded_value())) n end # convert to binary def to_binary(bplist) bplist.data_to_binary(decoded_value()) end def to_plain(plist) "<" + decoded_value.unpack("H*").join("") + ">" 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(parser) n = parser.new_node('array') @value.each do |v| n = parser.append_node(n, v.to_xml(parser)) end n end # convert to binary def to_binary(bplist) bplist.array_to_binary(self) end def to_plain(plist) ary = @value.map { |v| v.to_plain(plist) } "( " + ary.join(", ") + " )" 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(parser) n = parser.new_node('dict') @value.each_pair do |key, value| k = parser.append_node(parser.new_node('key'), parser.new_text(key.to_s)) n = parser.append_node(n, k) n = parser.append_node(n, value.to_xml(parser)) end n end # convert to binary def to_binary(bplist) bplist.dict_to_binary(self) end def to_plain(plist) str = "{ " cfstr = CFString.new() @value.each do |k,v| cfstr.value = k str << cfstr.to_plain(plist) + " = " + v.to_plain(plist) + "; " end str << "}" end end class CFUid < CFType def to_xml(parser) CFDictionary.new({'CF$UID' => CFInteger.new(@value)}).to_xml(parser) end # convert to binary def to_binary(bplist) bplist.uid_to_binary(@value) end def to_plain(plist) CFDictionary.new({'CF$UID' => CFInteger.new(@value)}).to_plain(plist) end end end # eof CFPropertyList-3.0.6/lib/cfpropertylist/rbCFPlistError.rb0000644000004100000410000000106714364771233023505 0ustar www-datawww-data# -*- coding: utf-8 -*- # # Exceptions used: # CFPlistError:: General base exception # CFFormatError:: Format error # CFTypeError:: Type error # # Easy and simple :-) # # Author:: Christian Kruse (mailto:cjk@wwwtech.de) # Copyright:: Copyright (c) 2010 # License:: MIT License # general plist error. All exceptions thrown are derived from this class. class CFPlistError < StandardError end # Exception thrown when format errors occur class CFFormatError < CFPlistError end # Exception thrown when type errors occur class CFTypeError < CFPlistError end # eof CFPropertyList-3.0.6/lib/cfpropertylist/rbBinaryCFPropertyList.rb0000644000004100000410000004412114364771233025223 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'stringio' module CFPropertyList # Binary PList parser class class Binary # Read a binary plist file def load(opts) @unique_table = {} @count_objects = 0 @object_refs = 0 @written_object_count = 0 @object_table = [] @object_ref_size = 0 @offsets = [] fd = nil if(opts.has_key?(:file)) fd = File.open(opts[:file],"rb") file = opts[:file] else fd = StringIO.new(opts[:data],"rb") file = "" 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 if(offset_size != 3) formats = ["","C*","n*","","N*"] @offsets = coded_offset_table.unpack(formats[offset_size]) else @offsets = coded_offset_table.unpack("C*").each_slice(3).map { |x,y,z| (x << 16) | (y << 8) | z } end @object_ref_size = object_ref_size val = read_binary_object_at(file,fd,top_object) fd.close 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 @object_refs = 0 @written_object_count = 0 @object_table = [] @offsets = [] binary_str = "bplist00" @object_refs = count_object_refs(opts[:root]) opts[:root].to_binary(self) next_offset = 8 offsets = @object_table.map do |object| offset = next_offset next_offset += object.bytesize offset end binary_str << @object_table.join table_offset = next_offset offset_size = Binary.bytes_needed(table_offset) if offset_size < 8 # Fast path: encode the entire offset array at once. binary_str << offsets.pack((%w(C n N N)[offset_size - 1]) + '*') else # Slow path: host may be little or big endian, must pack each offset # separately. offsets.each do |offset| binary_str << "#{Binary.pack_it_with_size(offset_size,offset)}" end end binary_str << [offset_size, object_ref_size(@object_refs)].pack("x6CC") binary_str << [@object_table.size].pack("x4N") binary_str << [0].pack("x4N") binary_str << [table_offset].pack("x4N") binary_str end def object_ref_size object_refs Binary.bytes_needed(object_refs) end # read a „null” type (i.e. null byte, marker byte, bool value) def read_binary_null_type(length) case length when 0 then 0 # null byte when 8 then CFBoolean.new(false) when 9 then CFBoolean.new(true) when 15 then 15 # fill type else raise CFFormatError.new("unknown null type: #{length}") end end protected :read_binary_null_type # read a binary int value def read_binary_int(fname,fd,length) if length > 4 raise CFFormatError.new("Integer greater than 16 bytes: #{length}") end nbytes = 1 << length buff = fd.read(nbytes) CFInteger.new( case length when 0 then buff.unpack("C")[0] when 1 then buff.unpack("n")[0] when 2 then buff.unpack("N")[0] # 8 byte integers are always signed when 3 then buff.unpack("q>")[0] # 16 byte integers are used to represent unsigned 8 byte integers # where the unsigned value is stored in the lower 8 bytes and the # upper 8 bytes are unused. when 4 then buff.unpack("Q>Q>")[1] end ) 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 buff = fd.read(nbytes) CFReal.new( case length when 0 # 1 byte float? must be an error raise CFFormatError.new("got #{length+1} byte float, must be an error!") when 1 # 2 byte float? must be an error raise CFFormatError.new("got #{length+1} byte float, must be an error!") when 2 then buff.reverse.unpack("e")[0] when 3 then buff.reverse.unpack("E")[0] else fail "unexpected length: #{length}" end ) 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 buff = fd.read(nbytes) CFDate.new( 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 buff.reverse.unpack("e")[0] when 3 then buff.reverse.unpack("E")[0] end, CFDate::TIMESTAMP_APPLE ) end protected :read_binary_date # Read a binary data value def read_binary_data(fname,fd,length) CFData.new(read_fd(fd, length), CFData::DATA_RAW) end protected :read_binary_data def read_fd fd, length length > 0 ? fd.read(length) : "" end # Read a binary string value def read_binary_string(fname,fd,length) buff = read_fd fd, length @unique_table[buff] = true unless @unique_table.has_key?(buff) 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.dup.force_encoding(from).encode(to) if str.respond_to?("encode") Iconv.conv(to,from,str) end # Count characters considering character set def Binary.charset_strlen(str,charset="UTF-8") if str.respond_to?(:encode) size = str.length else utf8_str = Iconv.conv("UTF-8",charset,str) size = utf8_str.scan(/./mu).size end # UTF-16 code units in the range D800-DBFF are the beginning of # a surrogate pair, and count as one additional character for # length calculation. if charset =~ /^UTF-16/ if str.respond_to?(:encode) str.bytes.to_a.each_slice(2) { |pair| size += 1 if (0xd8..0xdb).include?(pair[0]) } else str.split('').each_slice(2) { |pair| size += 1 if ("\xd8".."\xdb").include?(pair[0]) } end end 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) CFString.new(Binary.charset_convert(buff,"UTF-16BE","UTF-8")) end protected :read_binary_unicode_string def unpack_with_size(nbytes, buff) format = ["C*", "n*", "N*", "N*"][nbytes - 1]; if nbytes == 3 buff = "\0" + buff.scan(/.{1,3}/).join("\0") end return buff.unpack(format) end # Read an binary array value, including contained objects def read_binary_array(fname,fd,length) ary = [] # first: read object refs if(length != 0) buff = fd.read(length * @object_ref_size) objects = unpack_with_size(@object_ref_size, buff) #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 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 = unpack_with_size(@object_ref_size, buff) # second: read object refs buff = fd.read(length * @object_ref_size) objects = unpack_with_size(@object_ref_size, buff) # 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 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 case object_type when '0' # null, false, true, fillbyte read_binary_null_type(object_length) when '1' # integer read_binary_int(fname,fd,object_length) when '2' # real read_binary_real(fname,fd,object_length) when '3' # date read_binary_date(fname,fd,object_length) when '4' # data read_binary_data(fname,fd,object_length) when '5' # byte string, usually utf8 encoded read_binary_string(fname,fd,object_length) when '6' # unicode string (utf16be) read_binary_unicode_string(fname,fd,object_length) when '8' CFUid.new(read_binary_int(fname, fd, object_length).value) when 'a' # array read_binary_array(fname,fd,object_length) when 'd' # dictionary read_binary_dict(fname,fd,object_length) end 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) read_binary_object(fname,fd) end protected :read_binary_object_at # pack an +int+ of +nbytes+ with size def Binary.pack_it_with_size(nbytes,int) case nbytes when 1 then [int].pack('c') when 2 then [int].pack('n') when 4 then [int].pack('N') when 8 [int >> 32, int & 0xFFFFFFFF].pack('NN') else raise CFFormatError.new("Don't know how to pack #{nbytes} byte integer") end end def Binary.pack_int_array_with_size(nbytes, array) case nbytes when 1 then array.pack('C*') when 2 then array.pack('n*') when 4 then array.pack('N*') when 8 array.map { |int| [int >> 32, int & 0xFFFFFFFF].pack('NN') }.join else raise CFFormatError.new("Don't know how to pack #{nbytes} byte integer") end end # calculate how many bytes are needed to save +count+ def Binary.bytes_needed(count) case when count < 2**8 then 1 when count < 2**16 then 2 when count < 2**32 then 4 when count < 2**64 then 8 else raise CFFormatError.new("Data size too large: #{count}") end end # Create a type byte for binary format as defined by apple def Binary.type_bytes(type, length) if length < 15 [(type << 4) | length].pack('C') else bytes = [(type << 4) | 0xF] if length <= 0xFF bytes.push(0x10, length).pack('CCC') # 1 byte length elsif length <= 0xFFFF bytes.push(0x11, length).pack('CCn') # 2 byte length elsif length <= 0xFFFFFFFF bytes.push(0x12, length).pack('CCN') # 4 byte length elsif length <= 0x7FFFFFFFFFFFFFFF bytes.push(0x13, length >> 32, length & 0xFFFFFFFF).pack('CCNN') # 8 byte length else raise CFFormatError.new("Integer too large: #{int}") end end end def count_object_refs(object) case object when CFArray contained_refs = 0 object.value.each do |element| if CFArray === element || CFDictionary === element contained_refs += count_object_refs(element) end end return object.value.size + contained_refs when CFDictionary contained_refs = 0 object.value.each_value do |value| if CFArray === value || CFDictionary === value contained_refs += count_object_refs(value) end end return object.value.keys.size * 2 + contained_refs else return 0 end end def Binary.ascii_string?(str) if str.respond_to?(:ascii_only?) str.ascii_only? else str !~ /[\x80-\xFF]/mn end end # Uniques and transforms a string value to binary format and adds it to the object table def string_to_binary(val) val = val.to_s @unique_table[val] ||= begin if !Binary.ascii_string?(val) val = Binary.charset_convert(val,"UTF-8","UTF-16BE") bdata = Binary.type_bytes(0b0110, Binary.charset_strlen(val,"UTF-16BE")) val.force_encoding("ASCII-8BIT") if val.respond_to?("encode") @object_table[@written_object_count] = bdata << val else bdata = Binary.type_bytes(0b0101,val.bytesize) @object_table[@written_object_count] = bdata << val end @written_object_count += 1 @written_object_count - 1 end end # Codes an integer to binary format def int_to_binary(value) # Note: nbytes is actually an exponent. number of bytes = 2**nbytes. nbytes = 0 nbytes = 1 if value > 0xFF # 1 byte unsigned integer nbytes += 1 if value > 0xFFFF # 4 byte unsigned integer nbytes += 1 if value > 0xFFFFFFFF # 8 byte unsigned integer nbytes += 1 if value > 0x7FFFFFFFFFFFFFFF # 8 byte unsigned integer, stored in lower half of 16 bytes nbytes = 3 if value < 0 # signed integers always stored in 8 bytes Binary.type_bytes(0b0001, nbytes) << if nbytes < 4 [value].pack(["C", "n", "N", "q>"][nbytes]) else # nbytes == 4 [0,value].pack("Q>Q>") end end # Codes a real value to binary format def real_to_binary(val) Binary.type_bytes(0b0010,3) << [val].pack("E").reverse end # Converts a numeric value to binary and adds it to the object table def num_to_binary(value) @object_table[@written_object_count] = if value.is_a?(CFInteger) int_to_binary(value.value) else real_to_binary(value.value) end @written_object_count += 1 @written_object_count - 1 end def uid_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 @object_table[@written_object_count] = Binary.type_bytes(0b1000, nbytes) << if nbytes < 3 [value].pack( if nbytes == 0 then "C" elsif nbytes == 1 then "n" else "N" end ) 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 [high_word,low_word].pack("NN") end @written_object_count += 1 @written_object_count - 1 end # Convert date value (apple format) to binary and adds it to the object table def date_to_binary(val) 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 @object_table[@written_object_count] = (Binary.type_bytes(0b0011, 3) << [val].pack("E").reverse) @written_object_count += 1 @written_object_count - 1 end # Convert a bool value to binary and add it to the object table def bool_to_binary(val) @object_table[@written_object_count] = val ? "\x9" : "\x8" # 0x9 is 1001, type indicator for true; 0x8 is 1000, type indicator for false @written_object_count += 1 @written_object_count - 1 end # Convert data value to binary format and add it to the object table def data_to_binary(val) @object_table[@written_object_count] = (Binary.type_bytes(0b0100, val.bytesize) << val) @written_object_count += 1 @written_object_count - 1 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 #@object_refs += val.value.size values = val.value.map { |v| v.to_binary(self) } bdata = Binary.type_bytes(0b1010, val.value.size) << Binary.pack_int_array_with_size(object_ref_size(@object_refs), values) @object_table[saved_object_count] = bdata 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 #@object_refs += val.value.keys.size * 2 keys_and_values = val.value.keys.map { |k| CFString.new(k).to_binary(self) } keys_and_values.concat(val.value.values.map { |v| v.to_binary(self) }) bdata = Binary.type_bytes(0b1101,val.value.size) << Binary.pack_int_array_with_size(object_ref_size(@object_refs), keys_and_values) @object_table[saved_object_count] = bdata return saved_object_count end end end # eof CFPropertyList-3.0.6/lib/cfpropertylist.rb0000644000004100000410000000011314364771233020632 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'cfpropertylist/rbCFPropertyList' # eof CFPropertyList-3.0.6/README.rdoc0000644000004100000410000000223614364771233016264 0ustar www-datawww-dataCFPropertyList implementation class to read, manipulate and write both XML and binary property list files (plist(5)) as defined by Apple. Have a look at CFPropertyList::List for more documentation. == Installation You could either use ruby gems and install it via gem install CFPropertyList or you could clone this repository and place it somewhere in your load path. == Example require 'cfpropertylist' # 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(:file => "example.plist") data = CFPropertyList.native_types(plist.value) Author:: Christian Kruse (mailto:cjk@wwwtech.de) Copyright:: Copyright (c) 2010 License:: MIT License CFPropertyList-3.0.6/THANKS0000644000004100000410000000023514364771233015366 0ustar www-datawww-dataSpecial thanks to: Steve Madsen for providing a lot of performance patches and bugfixes! Have a look at his Github account: