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