plist.rb in plist-1.1.1 vs plist.rb in plist-2.0.0

- old
+ new

@@ -1,27 +1,78 @@ #require 'rexml/document' #require 'rexml/streamlistener' # Plist parses Mac OS X xml property list files into ruby data structures. # +# === Load a plist file +# This is the main point of the library: +# +# r = Plist::parse_xml( filename_or_xml ) +# +# === Save a plist +# You can turn the variables back into a plist string: +# +# r.to_plist +# +# There is a convenience method for saving a variable to a file: +# +# r.save_plist(filename) +# +# Only these ruby types can be converted into a plist: +# +# String +# Float +# DateTime +# Integer +# FalseClass +# TrueClass +# Array +# Hash +# +# Note that Array and Hash are recursive -- the elements of an Array and the values of a Hash +# must convert to a plist. Also note that the keys of the Hash must be strings. +# +# If you have suggestions for mapping other Ruby types to the plist types, send a note to: +# +# mailto:plist@hexane.org +# +# I'll take a look and probably add it, I'm just reticent to create too many +# "convenience" methods without at least agreeing with someone :-) +# +# === Credits +# plist.rb has been implemented by Patrick May. A few other folks have been helpful in developing plist.rb: +# +# + Martin Dittus, who pointed out that Time wasn't enough for plist Dates, +# especially those in "~/Library/Cookies/Cookies.plist" +# +# + Chuck Remes, who pushed me towards implementing #to_plist +class Plist + + TEMPLATE = <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +%plist% +</plist> +XML + def Plist::_xml( xml ) + TEMPLATE.sub( /%plist%/, xml ) + end + # Note that I don't use these two elements much: # -# + Date elements are returned as Time objects. +# + Date elements are returned as DateTime objects. # + Data elements are not yet implemented. # # Plist::parse_xml will blow up if it encounters a data element. # If you encounter such an error, or if you have a Date element which # can't be parsed into a Time object, please send your plist file to -# patrick@hexane.org so that I can implement the proper support. -# -# The main point of this api is one method: Plist::parse_xml( filename ) -class Plist - - def Plist::parse_xml( filename ) +# plist@hexane.org so that I can implement the proper support. + def Plist::parse_xml( filename_or_xml ) listener = Listener.new #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener) - parser = StreamParser.new(filename, listener) + parser = StreamParser.new(filename_or_xml, listener) parser.parse listener.result end class Listener @@ -52,12 +103,12 @@ end end end class StreamParser - def initialize( filename, listener ) - @filename = filename + def initialize( filename_or_xml, listener ) + @filename_or_xml = filename_or_xml @listener = listener end TEXT = /([^<]+)/ XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um @@ -68,11 +119,15 @@ plist_tags = PTag::mappings.keys.join('|') start_tag = /<(#{plist_tags})([^>]*)>/i end_tag = /<\/(#{plist_tags})[^>]*>/i require 'strscan' - @scanner = StringScanner.new( File.open(@filename, "r") {|f| f.read} ) + @scanner = StringScanner.new( if (File.exists? @filename_or_xml) + File.open(@filename_or_xml, "r") {|f| f.read} + else + @filename_or_xml + end ) until @scanner.eos? if @scanner.scan(XMLDECL_PATTERN) elsif @scanner.scan(DOCTYPE_PATTERN) elsif @scanner.scan(start_tag) @listener.tag_start(@scanner[1], nil) @@ -144,11 +199,11 @@ end end class PString < PTag def to_ruby - text + text || '' end end class PArray < PTag def to_ruby @@ -187,6 +242,95 @@ def to_ruby DateTime.parse(text) end end + module Emit + def save_plist(filename) + File.open(filename, 'wb') do |f| + f.write(self.to_plist) + end + end + + # Only the expected classes can be emitted as a plist: + # String, Float, DateTime, Integer, TrueClass, FalseClass, Array, Hash + # + # Write me if you think another class can be coerced safely into one of the + # expected plist classes (plist@hexane.org) + def to_plist + Plist::_xml(self.to_plist_fragment) + end + end +end + +class String + include Plist::Emit + def to_plist_fragment + "<string>#{self}</string>" + end +end + +class Float + include Plist::Emit + def to_plist_fragment + "<real>#{self}</real>" + end +end + +class DateTime + include Plist::Emit + def to_plist_fragment + "<date>#{self}</date>" + end +end + +class Integer + include Plist::Emit + def to_plist_fragment + "<integer>#{self}</integer>" + end +end + +class FalseClass + include Plist::Emit + def to_plist_fragment + "<false/>" + end +end + +class TrueClass + include Plist::Emit + def to_plist_fragment + "<true/>" + end +end + +class Array + include Plist::Emit + def to_plist_fragment + fragment = "<array>\n" + self.each do |e| + element_plist = e.to_plist_fragment + element_plist.each do |l| + fragment += " #{l}\n" + end + end + fragment += "</array>" + fragment + end +end + +class Hash + include Plist::Emit + def to_plist_fragment + fragment = "<dict>\n" + self.keys.sort.each do |k| + fragment += " <key>#{k}</key>\n" + element_plist = self[k].to_plist_fragment + element_plist.each do |l| + fragment += " #{l}\n" + end + end + fragment += "</dict>" + fragment + end end