summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bleything <ben@bleything.net>2006-08-19 17:17:50 +0000
committerBen Bleything <ben@bleything.net>2006-08-19 17:17:50 +0000
commit9ecf1c9ba1f05a98eea9a74c3d78b01ffaf4aee7 (patch)
treeb5adbecf400aca5dab03b09b72f9fa5f6e3ff09f
parent4eb4e939e84dbf921db94014bc35998e141389cf (diff)
downloadplist-9ecf1c9ba1f05a98eea9a74c3d78b01ffaf4aee7.tar.gz
finish splitting out functions into files
-rw-r--r--lib/plist.rb346
-rw-r--r--lib/plist/generator.rb253
-rw-r--r--lib/plist/parser.rb186
3 files changed, 71 insertions, 714 deletions
diff --git a/lib/plist.rb b/lib/plist.rb
index 64b404d..d8d33b9 100644
--- a/lib/plist.rb
+++ b/lib/plist.rb
@@ -6,347 +6,13 @@
# Distributed under the MIT license. #
##############################################################
#++
+# = Plist
#
-# 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 )
-class Plist
- VERSION = '0.0.1'
-
- 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 DateTime objects.
- # + Data elements are implemented as Tempfiles
- #
- # 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
- # 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_or_xml, listener)
- parser.parse
- listener.result
- end
-
- class Listener
- #include REXML::StreamListener
-
- attr_accessor :result, :open
-
- def initialize
- @result = nil
- @open = Array.new
- end
-
-
- def tag_start(name, attributes)
- @open.push PTag::mappings[name].new
- end
-
- def text( contents )
- @open.last.text = contents if @open.last
- end
-
- def tag_end(name)
- last = @open.pop
- if @open.empty?
- @result = last.to_ruby
- else
- @open.last.children.push last
- end
- end
- end
-
- class StreamParser
- def initialize( filename_or_xml, listener )
- @filename_or_xml = filename_or_xml
- @listener = listener
- end
-
- TEXT = /([^<]+)/
- XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um
- DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um
-
-
- def parse
- plist_tags = PTag::mappings.keys.join('|')
- start_tag = /<(#{plist_tags})([^>]*)>/i
- end_tag = /<\/(#{plist_tags})[^>]*>/i
-
- require 'strscan'
- @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)
- if (@scanner[2] =~ /\/$/)
- @listener.tag_end(@scanner[1])
- end
- elsif @scanner.scan(TEXT)
- @listener.text(@scanner[1])
- elsif @scanner.scan(end_tag)
- @listener.tag_end(@scanner[1])
- else
- raise "Unimplemented element"
- end
- end
- end
- end
-
- class PTag
- @@mappings = { }
- def PTag::mappings
- @@mappings
- end
-
- def PTag::inherited( sub_class )
- key = sub_class.to_s.downcase
- key.gsub!(/^plist::/, '' )
- key.gsub!(/^p/, '') unless key == "plist"
-
- @@mappings[key] = sub_class
- end
-
- attr_accessor :text, :children
- def initialize
- @children = Array.new
- end
-
- def to_ruby
- raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}"
- end
- end
-
- class PList < PTag
- def to_ruby
- children.first.to_ruby
- end
- end
-
- class PDict < PTag
- def to_ruby
- dict = Hash.new
- key = nil
-
- children.each do |c|
- if key.nil?
- key = c.to_ruby
- else
- dict[key] = c.to_ruby
- key = nil
- end
- end
-
- dict
- end
- end
-
- class PKey < PTag
- def to_ruby
- text
- end
- end
-
- class PString < PTag
- def to_ruby
- text || ''
- end
- end
-
- class PArray < PTag
- def to_ruby
- children.collect do |c|
- c.to_ruby
- end
- end
- end
-
- class PInteger < PTag
- def to_ruby
- text.to_i
- end
- end
-
- class PTrue < PTag
- def to_ruby
- true
- end
- end
-
- class PFalse < PTag
- def to_ruby
- false
- end
- end
-
- class PReal < PTag
- def to_ruby
- text.to_f
- end
- end
-
- require 'date'
- class PDate < PTag
- def to_ruby
- DateTime.parse(text)
- end
- end
-
- require 'base64'
- require 'tempfile'
- class PData < PTag
- def to_ruby
- tf = Tempfile.new("plist.tmp")
- tf.write Base64.decode64(text.gsub(/\s+/,''))
- tf.close
- # is this a good idea?
- tf.open
- tf
- 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( header = true )
- if (header)
- Plist::_xml(self.to_plist_fragment)
- else
- self.to_plist_fragment
- end
- end
- end
-end
-
-class String
- include Plist::Emit
- def to_plist_fragment
- "<string>#{self}</string>"
- end
-end
-
-class Symbol
- 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 Time
- include Plist::Emit
- def to_plist_fragment
- "<date>#{self.utc.strftime('%Y-%m-%dT%H:%M:%SZ')}</date>"
- end
-end
-
-class Date
- include Plist::Emit
- def to_plist_fragment
- "<date>#{self.strftime('%Y-%m-%dT%H:%M:%SZ')}</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 += "\t#{l.chomp}\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 += "\t<key>#{k}</key>\n"
- element_plist = self[k].to_plist_fragment
- element_plist.each do |l|
- fragment += "\t#{l.chomp}\n"
- end
- end
- fragment += "</dict>"
- fragment
- end
-end
-
-require 'stringio'
-[ IO, StringIO ].each do |io_class|
- io_class.module_eval do
- include Plist::Emit
- def to_plist_fragment
- self.rewind
- data = self.read
+# This is the main file for plist. Everything interesting happens in Plist and Plist::Emit.
- output = "<data>\n"
- Base64::encode64(data).gsub(/\s+/, '').scan(/.{1,68}/o) { output << $& << "\n" }
- output << "</data>"
+require 'plist/generator'
+require 'plist/parser'
- output
- end
- end
+module Plist
+ VERSION = '3.0.0'
end \ No newline at end of file
diff --git a/lib/plist/generator.rb b/lib/plist/generator.rb
index 64b404d..57fb179 100644
--- a/lib/plist/generator.rb
+++ b/lib/plist/generator.rb
@@ -6,225 +6,42 @@
# Distributed under the MIT license. #
##############################################################
#++
+
+# === Save a plist
+# You can turn the variables back into a plist string:
#
-# Plist parses Mac OS X xml property list files into ruby data structures.
+# r.to_plist
#
-# === Load a plist file
-# This is the main point of the library:
+# There is a convenience method for saving a variable to a file:
#
-# r = Plist::parse_xml( filename_or_xml )
-class Plist
- VERSION = '0.0.1'
-
- 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 DateTime objects.
- # + Data elements are implemented as Tempfiles
- #
- # 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
- # 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_or_xml, listener)
- parser.parse
- listener.result
- end
-
- class Listener
- #include REXML::StreamListener
-
- attr_accessor :result, :open
-
- def initialize
- @result = nil
- @open = Array.new
- end
-
-
- def tag_start(name, attributes)
- @open.push PTag::mappings[name].new
- end
-
- def text( contents )
- @open.last.text = contents if @open.last
- end
-
- def tag_end(name)
- last = @open.pop
- if @open.empty?
- @result = last.to_ruby
- else
- @open.last.children.push last
- end
- end
- end
-
- class StreamParser
- def initialize( filename_or_xml, listener )
- @filename_or_xml = filename_or_xml
- @listener = listener
- end
-
- TEXT = /([^<]+)/
- XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um
- DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um
-
-
- def parse
- plist_tags = PTag::mappings.keys.join('|')
- start_tag = /<(#{plist_tags})([^>]*)>/i
- end_tag = /<\/(#{plist_tags})[^>]*>/i
-
- require 'strscan'
- @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)
- if (@scanner[2] =~ /\/$/)
- @listener.tag_end(@scanner[1])
- end
- elsif @scanner.scan(TEXT)
- @listener.text(@scanner[1])
- elsif @scanner.scan(end_tag)
- @listener.tag_end(@scanner[1])
- else
- raise "Unimplemented element"
- end
- end
- end
- end
-
- class PTag
- @@mappings = { }
- def PTag::mappings
- @@mappings
- end
-
- def PTag::inherited( sub_class )
- key = sub_class.to_s.downcase
- key.gsub!(/^plist::/, '' )
- key.gsub!(/^p/, '') unless key == "plist"
-
- @@mappings[key] = sub_class
- end
-
- attr_accessor :text, :children
- def initialize
- @children = Array.new
- end
-
- def to_ruby
- raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}"
- end
- end
-
- class PList < PTag
- def to_ruby
- children.first.to_ruby
- end
- end
-
- class PDict < PTag
- def to_ruby
- dict = Hash.new
- key = nil
-
- children.each do |c|
- if key.nil?
- key = c.to_ruby
- else
- dict[key] = c.to_ruby
- key = nil
- end
- end
-
- dict
- end
- end
-
- class PKey < PTag
- def to_ruby
- text
- end
- end
-
- class PString < PTag
- def to_ruby
- text || ''
- end
- end
-
- class PArray < PTag
- def to_ruby
- children.collect do |c|
- c.to_ruby
- end
- end
- end
-
- class PInteger < PTag
- def to_ruby
- text.to_i
- end
- end
-
- class PTrue < PTag
- def to_ruby
- true
- end
- end
-
- class PFalse < PTag
- def to_ruby
- false
- end
- end
-
- class PReal < PTag
- def to_ruby
- text.to_f
- end
- end
-
- require 'date'
- class PDate < PTag
- def to_ruby
- DateTime.parse(text)
- end
- end
-
- require 'base64'
- require 'tempfile'
- class PData < PTag
- def to_ruby
- tf = Tempfile.new("plist.tmp")
- tf.write Base64.decode64(text.gsub(/\s+/,''))
- tf.close
- # is this a good idea?
- tf.open
- tf
- end
- end
+# r.save_plist(filename)
+#
+# Only these ruby types can be converted into a plist:
+#
+# String
+# Float
+# DateTime
+# Integer
+# FalseClass
+# TrueClass
+# Array
+# Hash
+#
+# Notes:
+#
+# + Array and Hash are recursive -- the elements of an Array and the values of a Hash
+# must convert to a plist.
+# + The keys of the Hash must be strings.
+# + The contents of data elements are returned as a Tempfile.
+# + Data elements can be set with to an open IO or a StringIO
+#
+# 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 :-)
+module Plist
module Emit
def save_plist(filename)
File.open(filename, 'wb') do |f|
@@ -341,11 +158,11 @@ require 'stringio'
def to_plist_fragment
self.rewind
data = self.read
-
+
output = "<data>\n"
Base64::encode64(data).gsub(/\s+/, '').scan(/.{1,68}/o) { output << $& << "\n" }
output << "</data>"
-
+
output
end
end
diff --git a/lib/plist/parser.rb b/lib/plist/parser.rb
index 64b404d..3d08a68 100644
--- a/lib/plist/parser.rb
+++ b/lib/plist/parser.rb
@@ -13,9 +13,7 @@
# This is the main point of the library:
#
# r = Plist::parse_xml( filename_or_xml )
-class Plist
- VERSION = '0.0.1'
-
+module 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">
@@ -27,15 +25,15 @@ XML
TEMPLATE.sub( /%plist%/, xml )
end
- # Note that I don't use these two elements much:
- #
- # + Date elements are returned as DateTime objects.
- # + Data elements are implemented as Tempfiles
- #
- # 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
- # plist@hexane.org so that I can implement the proper support.
+# Note that I don't use these two elements much:
+#
+# + Date elements are returned as DateTime objects.
+# + Data elements are implemented as Tempfiles
+#
+# 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
+# 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)
@@ -46,7 +44,7 @@ XML
class Listener
#include REXML::StreamListener
-
+
attr_accessor :result, :open
def initialize
@@ -54,11 +52,11 @@ XML
@open = Array.new
end
-
+
def tag_start(name, attributes)
@open.push PTag::mappings[name].new
end
-
+
def text( contents )
@open.last.text = contents if @open.last
end
@@ -78,23 +76,23 @@ XML
@filename_or_xml = filename_or_xml
@listener = listener
end
-
+
TEXT = /([^<]+)/
XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um
DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um
-
-
- def parse
- plist_tags = PTag::mappings.keys.join('|')
- start_tag = /<(#{plist_tags})([^>]*)>/i
- end_tag = /<\/(#{plist_tags})[^>]*>/i
-
- require 'strscan'
- @scanner = StringScanner.new( if (File.exists? @filename_or_xml)
- File.open(@filename_or_xml, "r") {|f| f.read}
- else
- @filename_or_xml
- end )
+
+
+ def parse
+ plist_tags = PTag::mappings.keys.join('|')
+ start_tag = /<(#{plist_tags})([^>]*)>/i
+ end_tag = /<\/(#{plist_tags})[^>]*>/i
+
+ require 'strscan'
+ @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)
@@ -148,7 +146,7 @@ XML
def to_ruby
dict = Hash.new
key = nil
-
+
children.each do |c|
if key.nil?
key = c.to_ruby
@@ -161,13 +159,13 @@ XML
dict
end
end
-
+
class PKey < PTag
def to_ruby
text
end
end
-
+
class PString < PTag
def to_ruby
text || ''
@@ -225,128 +223,4 @@ XML
tf
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( header = true )
- if (header)
- Plist::_xml(self.to_plist_fragment)
- else
- self.to_plist_fragment
- end
- end
- end
-end
-
-class String
- include Plist::Emit
- def to_plist_fragment
- "<string>#{self}</string>"
- end
-end
-
-class Symbol
- 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 Time
- include Plist::Emit
- def to_plist_fragment
- "<date>#{self.utc.strftime('%Y-%m-%dT%H:%M:%SZ')}</date>"
- end
-end
-
-class Date
- include Plist::Emit
- def to_plist_fragment
- "<date>#{self.strftime('%Y-%m-%dT%H:%M:%SZ')}</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 += "\t#{l.chomp}\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 += "\t<key>#{k}</key>\n"
- element_plist = self[k].to_plist_fragment
- element_plist.each do |l|
- fragment += "\t#{l.chomp}\n"
- end
- end
- fragment += "</dict>"
- fragment
- end
-end
-
-require 'stringio'
-[ IO, StringIO ].each do |io_class|
- io_class.module_eval do
- include Plist::Emit
- def to_plist_fragment
- self.rewind
- data = self.read
-
- output = "<data>\n"
- Base64::encode64(data).gsub(/\s+/, '').scan(/.{1,68}/o) { output << $& << "\n" }
- output << "</data>"
-
- output
- end
- end
-end \ No newline at end of file