diff options
author | Matt Brictson <mattbrictson@users.noreply.github.com> | 2017-05-06 15:16:04 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-06 15:16:04 -0700 |
commit | b6f5fa9fc8c1aa147d2eac2ede9a4e9db8eede3c (patch) | |
tree | 7b7930efdf2a1ac9ac8ace0ccbe2c61df2da8602 | |
parent | c7249cf5115dd21b5dc65b0122af6c3b0d063095 (diff) | |
parent | be2183c0fda330905b612058c6d28fadee282cdb (diff) | |
download | plist-b6f5fa9fc8c1aa147d2eac2ede9a4e9db8eede3c.tar.gz |
Merge pull request #41 from reitermarkus/code-cleanup
Code cleanup.
-rw-r--r-- | README.rdoc | 2 | ||||
-rw-r--r-- | lib/plist.rb | 3 | ||||
-rw-r--r-- | lib/plist/generator.rb | 332 | ||||
-rwxr-xr-x | lib/plist/parser.rb | 61 | ||||
-rw-r--r-- | test/test_data_elements.rb | 22 | ||||
-rw-r--r-- | test/test_generator.rb | 2 | ||||
-rwxr-xr-x | test/test_parser.rb | 53 |
7 files changed, 234 insertions, 241 deletions
diff --git a/README.rdoc b/README.rdoc index 1fe9a3f..0df9d97 100644 --- a/README.rdoc +++ b/README.rdoc @@ -9,7 +9,7 @@ Plist is a library to manipulate Property List files, also known as plists. It === Parsing - result = Plist::parse_xml('path/to/example.plist') + result = Plist.parse_xml('path/to/example.plist') result.class => Hash diff --git a/lib/plist.rb b/lib/plist.rb index 986dad4..5c06351 100644 --- a/lib/plist.rb +++ b/lib/plist.rb @@ -16,6 +16,3 @@ require 'stringio' require 'plist/generator' require 'plist/parser' require 'plist/version' - -module Plist -end diff --git a/lib/plist/generator.rb b/lib/plist/generator.rb index 84bef3a..21ef959 100644 --- a/lib/plist/generator.rb +++ b/lib/plist/generator.rb @@ -6,208 +6,208 @@ # Distributed under the MIT License # -module Plist; end - -# === Create a plist -# You can dump an object to a plist in one of two ways: -# -# * <tt>Plist::Emit.dump(obj)</tt> -# * <tt>obj.to_plist</tt> -# * This requires that you mixin the <tt>Plist::Emit</tt> module, which is already done for +Array+ and +Hash+. -# -# The following Ruby classes are converted into native plist types: -# Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false -# * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the <array> and <dict> containers (respectively). -# * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a <data> element. -# * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to <tt>Marshal.dump</tt> and the result placed in a <data> element. -# -# For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below. -module Plist::Emit - # Helper method for injecting into classes. Calls <tt>Plist::Emit.dump</tt> with +self+. - def to_plist(envelope = true) - return Plist::Emit.dump(self, envelope) - end - - # Helper method for injecting into classes. Calls <tt>Plist::Emit.save_plist</tt> with +self+. - def save_plist(filename) - Plist::Emit.save_plist(self, filename) - end - - # The following Ruby classes are converted into native plist types: - # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time +module Plist + # === Create a plist + # You can dump an object to a plist in one of two ways: # - # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes. + # * <tt>Plist::Emit.dump(obj)</tt> + # * <tt>obj.to_plist</tt> + # * This requires that you mixin the <tt>Plist::Emit</tt> module, which is already done for +Array+ and +Hash+. # - # +IO+ and +StringIO+ objects are encoded and placed in <data> elements; other objects are <tt>Marshal.dump</tt>'ed unless they implement +to_plist_node+. + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false + # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the <array> and <dict> containers (respectively). + # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a <data> element. + # * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to <tt>Marshal.dump</tt> and the result placed in a <data> element. # - # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment. - def self.dump(obj, envelope = true) - output = plist_node(obj) + # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below. + module Emit + # Helper method for injecting into classes. Calls <tt>Plist::Emit.dump</tt> with +self+. + def to_plist(envelope = true) + return Plist::Emit.dump(self, envelope) + end - output = wrap(output) if envelope + # Helper method for injecting into classes. Calls <tt>Plist::Emit.save_plist</tt> with +self+. + def save_plist(filename) + Plist::Emit.save_plist(self, filename) + end - return output - end + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time + # + # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes. + # + # +IO+ and +StringIO+ objects are encoded and placed in <data> elements; other objects are <tt>Marshal.dump</tt>'ed unless they implement +to_plist_node+. + # + # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment. + def self.dump(obj, envelope = true) + output = plist_node(obj) + + output = wrap(output) if envelope - # Writes the serialized object's plist to the specified filename. - def self.save_plist(obj, filename) - File.open(filename, 'wb') do |f| - f.write(obj.to_plist) + return output end - end - private - def self.plist_node(element) - output = '' - - if element.respond_to? :to_plist_node - output << element.to_plist_node - else - case element - when Array - if element.empty? - output << "<array/>\n" - else - output << tag('array') { - element.collect {|e| plist_node(e)} - } - end - when Hash - if element.empty? - output << "<dict/>\n" - else - inner_tags = [] + # Writes the serialized object's plist to the specified filename. + def self.save_plist(obj, filename) + File.open(filename, 'wb') do |f| + f.write(obj.to_plist) + end + end - element.keys.sort_by{|k| k.to_s }.each do |k| - v = element[k] - inner_tags << tag('key', CGI::escapeHTML(k.to_s)) - inner_tags << plist_node(v) - end + private + def self.plist_node(element) + output = '' - output << tag('dict') { - inner_tags - } - end - when true, false - output << "<#{element}/>\n" - when Time - output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) - when Date # also catches DateTime - output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) - when String, Symbol, Integer, Float - output << tag(element_type(element), CGI::escapeHTML(element.to_s)) - when IO, StringIO - element.rewind - contents = element.read - # note that apple plists are wrapped at a different length then - # what ruby's base64 wraps by default. - # I used #encode64 instead of #b64encode (which allows a length arg) - # because b64encode is b0rked and ignores the length arg. - data = "\n" - Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } - output << tag('data', data) + if element.respond_to? :to_plist_node + output << element.to_plist_node else - output << comment( 'The <data> element below contains a Ruby object which has been serialized with Marshal.dump.' ) - data = "\n" - Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } - output << tag('data', data ) + case element + when Array + if element.empty? + output << "<array/>\n" + else + output << tag('array') { + element.collect {|e| plist_node(e)} + } + end + when Hash + if element.empty? + output << "<dict/>\n" + else + inner_tags = [] + + element.keys.sort_by{|k| k.to_s }.each do |k| + v = element[k] + inner_tags << tag('key', CGI.escapeHTML(k.to_s)) + inner_tags << plist_node(v) + end + + output << tag('dict') { + inner_tags + } + end + when true, false + output << "<#{element}/>\n" + when Time + output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) + when Date # also catches DateTime + output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) + when String, Symbol, Integer, Float + output << tag(element_type(element), CGI.escapeHTML(element.to_s)) + when IO, StringIO + element.rewind + contents = element.read + # note that apple plists are wrapped at a different length then + # what ruby's base64 wraps by default. + # I used #encode64 instead of #b64encode (which allows a length arg) + # because b64encode is b0rked and ignores the length arg. + data = "\n" + Base64.encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data) + else + output << comment('The <data> element below contains a Ruby object which has been serialized with Marshal.dump.') + data = "\n" + Base64.encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data) + end end + + return output end - return output - end + def self.comment(content) + return "<!-- #{content} -->\n" + end - def self.comment(content) - return "<!-- #{content} -->\n" - end + def self.tag(type, contents = '', &block) + out = nil - def self.tag(type, contents = '', &block) - out = nil + if block_given? + out = IndentedString.new + out << "<#{type}>" + out.raise_indent - if block_given? - out = IndentedString.new - out << "<#{type}>" - out.raise_indent + out << block.call - out << block.call + out.lower_indent + out << "</#{type}>" + else + out = "<#{type}>#{contents.to_s}</#{type}>\n" + end - out.lower_indent - out << "</#{type}>" - else - out = "<#{type}>#{contents.to_s}</#{type}>\n" + return out.to_s end - return out.to_s - end - - def self.wrap(contents) - output = '' - - output << '<?xml version="1.0" encoding="UTF-8"?>' + "\n" - output << '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' + "\n" - output << '<plist version="1.0">' + "\n" + def self.wrap(contents) + output = '' - output << contents + output << '<?xml version="1.0" encoding="UTF-8"?>' + "\n" + output << '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' + "\n" + output << '<plist version="1.0">' + "\n" - output << '</plist>' + "\n" + output << contents - return output - end + output << '</plist>' + "\n" - def self.element_type(item) - case item - when String, Symbol - 'string' + return output + end - when Integer - 'integer' + def self.element_type(item) + case item + when String, Symbol + 'string' - when Float - 'real' + when Integer + 'integer' - else - raise "Don't know about this data type... something must be wrong!" - end - end - private - class IndentedString #:nodoc: - attr_accessor :indent_string - - def initialize(str = "\t") - @indent_string = str - @contents = '' - @indent_level = 0 - end + when Float + 'real' - def to_s - return @contents + else + raise "Don't know about this data type... something must be wrong!" + end end + private + class IndentedString #:nodoc: + attr_accessor :indent_string + + def initialize(str = "\t") + @indent_string = str + @contents = '' + @indent_level = 0 + end - def raise_indent - @indent_level += 1 - end + def to_s + return @contents + end - def lower_indent - @indent_level -= 1 if @indent_level > 0 - end + def raise_indent + @indent_level += 1 + end - def <<(val) - if val.is_a? Array - val.each do |f| - self << f - end - else - # if it's already indented, don't bother indenting further - unless val =~ /\A#{@indent_string}/ - indent = @indent_string * @indent_level + def lower_indent + @indent_level -= 1 if @indent_level > 0 + end - @contents << val.gsub(/^/, indent) + def <<(val) + if val.is_a? Array + val.each do |f| + self << f + end else - @contents << val - end + # if it's already indented, don't bother indenting further + unless val =~ /\A#{@indent_string}/ + indent = @indent_string * @indent_level - # it already has a newline, don't add another - @contents << "\n" unless val =~ /\n$/ + @contents << val.gsub(/^/, indent) + else + @contents << val + end + + # it already has a newline, don't add another + @contents << "\n" unless val =~ /\n$/ + end end end end diff --git a/lib/plist/parser.rb b/lib/plist/parser.rb index 4de13f8..9be3e23 100755 --- a/lib/plist/parser.rb +++ b/lib/plist/parser.rb @@ -11,41 +11,40 @@ # === Load a plist file # This is the main point of the library: # -# r = Plist::parse_xml( filename_or_xml ) +# r = Plist.parse_xml(filename_or_xml) module Plist -# 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 Date 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 ) + # 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 Date 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 self.parse_xml(filename_or_xml) listener = Listener.new - #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener) + # 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 + # include REXML::StreamListener attr_accessor :result, :open def initialize @result = nil - @open = Array.new + @open = [] end - def tag_start(name, attributes) - @open.push PTag::mappings[name].new + @open.push PTag.mappings[name].new end - def text( contents ) + def text(contents) @open.last.text = contents if @open.last end @@ -60,11 +59,11 @@ module Plist end class StreamParser - def initialize( plist_data_or_file, listener ) + def initialize(plist_data_or_file, listener) if plist_data_or_file.respond_to? :read @xml = plist_data_or_file.read elsif File.exist? plist_data_or_file - @xml = File.read( plist_data_or_file ) + @xml = File.read(plist_data_or_file) else @xml = plist_data_or_file end @@ -78,15 +77,14 @@ module Plist COMMENT_START = /\A<!--/ COMMENT_END = /.*?-->/m - def parse - plist_tags = PTag::mappings.keys.join('|') + plist_tags = PTag.mappings.keys.join('|') start_tag = /<(#{plist_tags})([^>]*)>/i end_tag = /<\/(#{plist_tags})[^>]*>/i require 'strscan' - @scanner = StringScanner.new( @xml ) + @scanner = StringScanner.new(@xml) until @scanner.eos? if @scanner.scan(COMMENT_START) @scanner.scan(COMMENT_END) @@ -132,22 +130,21 @@ module Plist end class PTag - @@mappings = { } - def PTag::mappings - @@mappings + def self.mappings + @mappings ||= {} end - def PTag::inherited( sub_class ) + def self.inherited(sub_class) key = sub_class.to_s.downcase - key.gsub!(/^plist::/, '' ) + key.gsub!(/^plist::/, '') key.gsub!(/^p/, '') unless key == "plist" - @@mappings[key] = sub_class + mappings[key] = sub_class end attr_accessor :text, :children def initialize - @children = Array.new + @children = [] end def to_ruby @@ -163,7 +160,7 @@ module Plist class PDict < PTag def to_ruby - dict = Hash.new + dict = {} key = nil children.each do |c| @@ -181,13 +178,13 @@ module Plist class PKey < PTag def to_ruby - CGI::unescapeHTML(text || '') + CGI.unescapeHTML(text || '') end end class PString < PTag def to_ruby - CGI::unescapeHTML(text || '') + CGI.unescapeHTML(text || '') end end diff --git a/test/test_data_elements.rb b/test/test_data_elements.rb index 6d061e8..02fcf20 100644 --- a/test/test_data_elements.rb +++ b/test/test_data_elements.rb @@ -13,7 +13,7 @@ end class TestDataElements < Test::Unit::TestCase def setup - @result = Plist.parse_xml( 'test/assets/test_data_elements.plist' ) + @result = Plist.parse_xml('test/assets/test_data_elements.plist') end def test_data_object_header @@ -24,10 +24,10 @@ BAhvOhZNYXJzaGFsYWJsZU9iamVjdAY6CUBmb28iHnRoaXMgb2JqZWN0IHdhcyBtYXJz aGFsZWQ= </data> END - expected_elements = expected.chomp.split( "\n" ) + expected_elements = expected.chomp.split("\n") - actual = Plist::Emit.dump( Object.new, false ) - actual_elements = actual.chomp.split( "\n" ) + actual = Plist::Emit.dump(Object.new, false) + actual_elements = actual.chomp.split("\n") # check for header assert_equal expected_elements.shift, actual_elements.shift @@ -39,7 +39,7 @@ END def test_marshal_round_trip expected = MarshalableObject.new('this object was marshaled') - actual = Plist.parse_xml( Plist::Emit.dump(expected, false) ) + actual = Plist.parse_xml(Plist::Emit.dump(expected, false)) assert_kind_of expected.class, actual assert_equal expected.foo, actual.foo @@ -99,23 +99,23 @@ END # supplied the test data, and provided the parsing code. def test_data # test reading plist <data> elements - data = Plist::parse_xml("test/assets/example_data.plist"); - assert_equal( File.open("test/assets/example_data.jpg"){|f| f.read }, data['image'].read ) + data = Plist.parse_xml("test/assets/example_data.plist"); + assert_equal(File.open("test/assets/example_data.jpg"){|f| f.read }, data['image'].read) # test writing data elements expected = File.read("test/assets/example_data.plist") result = data.to_plist - #File.open('result.plist', 'w') {|f|f.write(result)} # debug - assert_equal( expected, result ) + # File.open('result.plist', 'w') {|f|f.write(result)} # debug + assert_equal(expected, result) # Test changing the <data> object in the plist to a StringIO and writing. # This appears extraneous given that plist currently returns a StringIO, # so the above writing test also flexes StringIO#to_plist_node. # However, the interface promise is to return an IO, not a particular class. # plist used to return Tempfiles, which was changed solely for performance reasons. - data['image'] = StringIO.new( File.read("test/assets/example_data.jpg")) + data['image'] = StringIO.new(File.read("test/assets/example_data.jpg")) - assert_equal(expected, data.to_plist ) + assert_equal(expected, data.to_plist) end diff --git a/test/test_generator.rb b/test/test_generator.rb index 241ff4f..e03c49d 100644 --- a/test/test_generator.rb +++ b/test/test_generator.rb @@ -9,7 +9,7 @@ class SerializableObject end def to_plist_node - return "<string>#{CGI::escapeHTML @foo}</string>" + return "<string>#{CGI.escapeHTML(@foo)}</string>" end end diff --git a/test/test_parser.rb b/test/test_parser.rb index 3614799..94ef74e 100755 --- a/test/test_parser.rb +++ b/test/test_parser.rb @@ -2,11 +2,11 @@ require 'test/unit' require 'plist' class TestParser < Test::Unit::TestCase - def test_Plist_parse_xml - result = Plist::parse_xml("test/assets/AlbumData.xml") + def test_parse_xml + result = Plist.parse_xml("test/assets/AlbumData.xml") # dict - assert_kind_of( Hash, result ) + assert_kind_of(Hash, result) expected = [ "List of Albums", @@ -18,66 +18,65 @@ class TestParser < Test::Unit::TestCase "List of Rolls", "Application Version" ] - assert_equal( expected.sort, result.keys.sort ) + assert_equal(expected.sort, result.keys.sort) # array - assert_kind_of( Array, result["List of Rolls"] ) - assert_equal( [ {"PhotoCount"=>1, + assert_kind_of(Array, result["List of Rolls"]) + assert_equal([ {"PhotoCount"=>1, "KeyList"=>["7"], "Parent"=>999000, "Album Type"=>"Regular", "AlbumName"=>"Roll 1", "AlbumId"=>6}], - result["List of Rolls"] ) + result["List of Rolls"]) # string - assert_kind_of( String, result["Application Version"] ) - assert_equal( "5.0.4 (263)", result["Application Version"] ) + assert_kind_of(String, result["Application Version"]) + assert_equal("5.0.4 (263)", result["Application Version"]) # integer - assert_kind_of( Integer, result["Major Version"] ) - assert_equal( 2, result["Major Version"] ) + assert_kind_of(Integer, result["Major Version"]) + assert_equal(2, result["Major Version"]) # true - assert_kind_of( TrueClass, result["List of Albums"][0]["Master"] ) - assert( result["List of Albums"][0]["Master"] ) + assert_kind_of(TrueClass, result["List of Albums"][0]["Master"]) + assert(result["List of Albums"][0]["Master"]) # false - assert_kind_of( FalseClass, result["List of Albums"][1]["SlideShowUseTitles"] ) - assert( ! result["List of Albums"][1]["SlideShowUseTitles"] ) - + assert_kind_of(FalseClass, result["List of Albums"][1]["SlideShowUseTitles"]) + assert(!result["List of Albums"][1]["SlideShowUseTitles"]) end # uncomment this test to work on speed optimization - #def test_load_something_big - # plist = Plist::parse_xml( "~/Pictures/iPhoto Library/AlbumData.xml" ) - #end + # def test_load_something_big + # plist = Plist.parse_xml("~/Pictures/iPhoto Library/AlbumData.xml") + # end # date fields are credited to def test_date_fields - result = Plist::parse_xml("test/assets/Cookies.plist") - assert_kind_of( DateTime, result.first['Expires'] ) - assert_equal DateTime.parse( "2007-10-25T12:36:35Z" ), result.first['Expires'] + result = Plist.parse_xml("test/assets/Cookies.plist") + assert_kind_of(DateTime, result.first['Expires']) + assert_equal DateTime.parse("2007-10-25T12:36:35Z"), result.first['Expires'] end # bug fix for empty <key> # reported by Matthias Peick <matthias@peick.de> # reported and fixed by Frederik Seiffert <ego@frederikseiffert.de> def test_empty_dict_key - data = Plist::parse_xml("test/assets/test_empty_key.plist"); + data = Plist.parse_xml("test/assets/test_empty_key.plist"); assert_equal("2", data['key']['subkey']) end # bug fix for decoding entities # reported by Matthias Peick <matthias@peick.de> def test_decode_entities - data = Plist::parse_xml('<string>Fish & Chips</string>') + data = Plist.parse_xml('<string>Fish & Chips</string>') assert_equal('Fish & Chips', data) end def test_comment_handling_and_empty_plist assert_nothing_raised do - assert_nil( Plist::parse_xml( File.read('test/assets/commented.plist') ) ) + assert_nil(Plist.parse_xml(File.read('test/assets/commented.plist'))) end end @@ -85,7 +84,7 @@ class TestParser < Test::Unit::TestCase require 'stringio' str = StringIO.new - data = Plist::parse_xml(str) + data = Plist.parse_xml(str) assert_nil data end @@ -98,7 +97,7 @@ class TestParser < Test::Unit::TestCase xml.force_encoding("ASCII-8BIT") assert_nothing_raised do - data = Plist::parse_xml(xml) + data = Plist.parse_xml(xml) assert_equal("\u0099", data["non-ascii-but-utf8-character"]) end end |