cfpropertylist-2.2.8/0000755000175000017500000000000012446744140013610 5ustar anishanishcfpropertylist-2.2.8/lib/0000755000175000017500000000000012446744140014356 5ustar anishanishcfpropertylist-2.2.8/lib/cfpropertylist/0000755000175000017500000000000012446744140017447 5ustar anishanishcfpropertylist-2.2.8/lib/cfpropertylist/rbLibXMLParser.rb0000644000175000017500000000713012446744140022565 0ustar anishanish# -*- coding: utf-8 -*- require 'libxml' module CFPropertyList # XML parser class LibXMLParser < 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 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 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-2.2.8/lib/cfpropertylist/rbCFTypes.rb0000644000175000017500000001467412446744140021651 0ustar anishanish# -*- 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 Fixnum # instance that should be converted to a CFInteger/CFReal type and a # Ruby Fixnum instance that should be converted to a CFUid type. class UidFixnum < Fixnum 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 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 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 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 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 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 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 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 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 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 end end # eof cfpropertylist-2.2.8/lib/cfpropertylist/rbREXMLParser.rb0000644000175000017500000000711512446744140022370 0ustar anishanish# -*- coding: utf-8 -*- require 'rexml/document' module CFPropertyList # XML parser class ReXMLParser < ParserInterface # 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 = REXML::Document.new(fd) } else doc = REXML::Document.new(opts[:data]) end if doc root = doc.root.elements[1] return import_xml(root) end rescue REXML::ParseException => 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 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-2.2.8/lib/cfpropertylist/rbCFPropertyList.rb0000644000175000017500000003120212446744140023207 0ustar anishanish# -*- 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 class String unless("".respond_to?(:bytesize)) then def bytesize self.length end end end dirname = File.dirname(__FILE__) require dirname + '/rbCFPlistError.rb' require dirname + '/rbCFTypes.rb' require dirname + '/rbBinaryCFPropertyList.rb' require 'iconv' unless "".respond_to?("encode") # ensure that the module and class exist module Enumerable class Enumerator end end begin require dirname + '/rbLibXMLParser.rb' temp = LibXML::XML::Parser::Options::NOBLANKS; # check if we have a version with parser options 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 => e 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 Fixnum, 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 CFString.new(object) when Time, DateTime, Date then CFDate.new(object) when Array, Enumerator, Enumerable::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. Instanciate with #new 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, CFPropertyList.xml_parser_interface] # 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 # 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("Wrong file version #{version}") unless version == "00" prsr = Binary.new @format = List::FORMAT_BINARY else prsr = CFPropertyList.xml_parser_interface.new @format = List::FORMAT_XML 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) 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("Wong file version #{version}") unless version == "00" prsr = Binary.new @format = List::FORMAT_BINARY else prsr = CFPropertyList.xml_parser_interface.new @format = List::FORMAT_XML 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? 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 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={}) raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML") if format != FORMAT_BINARY && format != FORMAT_XML 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-2.2.8/lib/cfpropertylist/rbNokogiriParser.rb0000644000175000017500000000747612446744140023274 0ustar anishanish# -*- coding: utf-8 -*- require 'nokogiri' module CFPropertyList # XML parser class NokogiriXMLParser < ParserInterface # 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, Nokogiri::XML::ParseOptions::NOBLANKS|Nokogiri::XML::ParseOptions::NOENT) } else doc = Nokogiri::XML::Document.parse(opts[:data], nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS|Nokogiri::XML::ParseOptions::NOENT) 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-2.2.8/lib/cfpropertylist/rbCFPlistError.rb0000644000175000017500000000106312446744140022636 0ustar anishanish# -*- 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 < Exception end # Exception thrown when format errors occur class CFFormatError < CFPlistError end # Exception thrown when type errors occur class CFTypeError < CFPlistError end # eof cfpropertylist-2.2.8/lib/cfpropertylist/rbBinaryCFPropertyList.rb0000644000175000017500000004452212446744140024365 0ustar anishanish# -*- 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 > 3 raise CFFormatError.new("Integer greater than 8 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] when 3 hiword,loword = buff.unpack("NN") if (hiword & 0x80000000) != 0 # 8 byte integers are always signed, and are negative when bit 63 is # set. Decoding into either a Fixnum or Bignum is tricky, however, # because the size of a Fixnum varies among systems, and Ruby # doesn't consider the number to be negative, and won't sign extend. -(2**63 - ((hiword & 0x7fffffff) << 32 | loword)) else hiword << 32 | loword end 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("f")[0] when 3 then buff.reverse.unpack("d")[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("f")[0] when 3 then buff.reverse.unpack("d")[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) 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 Binary.type_bytes(0b0001, 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 end # Codes a real value to binary format def real_to_binary(val) Binary.type_bytes(0b0010,3) << [val].pack("d").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("d").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-2.2.8/lib/cfpropertylist.rb0000644000175000017500000000011312446744140017767 0ustar anishanish# -*- coding: utf-8 -*- require 'cfpropertylist/rbCFPropertyList' # eof cfpropertylist-2.2.8/metadata.yml0000644000175000017500000000330712446744140016116 0ustar anishanish--- !ruby/object:Gem::Specification name: CFPropertyList version: !ruby/object:Gem::Version version: 2.2.8 platform: ruby authors: - Christian Kruse autorequire: bindir: bin cert_chain: [] date: 2014-05-10 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.7.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.7.0 description: This is a module to read, write and manipulate both binary and XML property lists as defined by apple. email: cjk@wwwtech.de executables: [] extensions: [] extra_rdoc_files: - README files: - README - lib/cfpropertylist.rb - lib/cfpropertylist/rbBinaryCFPropertyList.rb - lib/cfpropertylist/rbCFPlistError.rb - lib/cfpropertylist/rbCFPropertyList.rb - lib/cfpropertylist/rbCFTypes.rb - lib/cfpropertylist/rbLibXMLParser.rb - lib/cfpropertylist/rbNokogiriParser.rb - lib/cfpropertylist/rbREXMLParser.rb homepage: http://github.com/ckruse/CFPropertyList licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: Read, write and manipulate both binary and XML property lists as defined by apple test_files: [] cfpropertylist-2.2.8/README0000644000175000017500000000223712446744140014474 0ustar anishanishCFPropertyList 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