Created
June 12, 2009 21:01
-
-
Save UnderpantsGnome/128917 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/.gitignore b/.gitignore | |
new file mode 100644 | |
index 0000000..a3a6afd | |
--- /dev/null | |
+++ b/.gitignore | |
@@ -0,0 +1,2 @@ | |
+*.gem | |
+ | |
diff --git a/lib/sax-machine/sax_collection_config.rb b/lib/sax-machine/sax_collection_config.rb | |
index 3d22a08..88a637a 100644 | |
--- a/lib/sax-machine/sax_collection_config.rb | |
+++ b/lib/sax-machine/sax_collection_config.rb | |
@@ -5,9 +5,9 @@ module SAXMachine | |
attr_reader :name | |
def initialize(name, options) | |
- @name = name.to_s | |
- @class = options[:class] | |
- @as = options[:as].to_s | |
+ @name = name.to_s | |
+ @class = options[:class] | |
+ @as = options[:as].to_s | |
end | |
def handler | |
@@ -30,4 +30,4 @@ module SAXMachine | |
end | |
end | |
-end | |
\ No newline at end of file | |
+end | |
diff --git a/lib/sax-machine/sax_config.rb b/lib/sax-machine/sax_config.rb | |
index eee0f89..d482961 100644 | |
--- a/lib/sax-machine/sax_config.rb | |
+++ b/lib/sax-machine/sax_config.rb | |
@@ -5,6 +5,7 @@ module SAXMachine | |
class SAXConfig | |
def initialize | |
@top_level_elements = [] | |
+ @complex_elements = [] | |
@collection_elements = [] | |
end | |
@@ -12,10 +13,18 @@ module SAXMachine | |
@top_level_elements << ElementConfig.new(name, options) | |
end | |
+ def add_complex_element(name, options) | |
+ @complex_elements << ElementConfig.new(name, options) | |
+ end | |
+ | |
def add_collection_element(name, options) | |
@collection_elements << CollectionConfig.new(name, options) | |
end | |
+ def complex_config(name) | |
+ @complex_elements.detect { |ce| ce.name.to_s == name.to_s } | |
+ end | |
+ | |
def collection_config(name) | |
@collection_elements.detect { |ce| ce.name.to_s == name.to_s } | |
end | |
@@ -33,6 +42,5 @@ module SAXMachine | |
element_config.attrs_match?(attrs) | |
end | |
end | |
- | |
end | |
-end | |
\ No newline at end of file | |
+end | |
diff --git a/lib/sax-machine/sax_document.rb b/lib/sax-machine/sax_document.rb | |
index 5e2c3fc..a0fc208 100644 | |
--- a/lib/sax-machine/sax_document.rb | |
+++ b/lib/sax-machine/sax_document.rb | |
@@ -1,37 +1,44 @@ | |
require "nokogiri" | |
module SAXMachine | |
- | |
def self.included(base) | |
base.extend ClassMethods | |
end | |
- | |
+ | |
def parse(xml_text) | |
sax_handler = SAXHandler.new(self) | |
parser = Nokogiri::XML::SAX::Parser.new(sax_handler) | |
parser.parse(xml_text) | |
self | |
end | |
- | |
- module ClassMethods | |
+ module ClassMethods | |
def parse(xml_text) | |
new.parse(xml_text) | |
end | |
- | |
+ | |
def element(name, options = {}) | |
options[:as] ||= name | |
- sax_config.add_top_level_element(name, options) | |
- | |
- # we only want to insert the getter and setter if they haven't defined it from elsewhere. | |
- # this is how we allow custom parsing behavior. So you could define the setter | |
- # and have it parse the string into a date or whatever. | |
- attr_reader options[:as] unless instance_methods.include?(options[:as].to_s) | |
- attr_writer options[:as] unless instance_methods.include?("#{options[:as]}=") | |
+ | |
+ unless options[:class] | |
+ sax_config.add_top_level_element(name, options) | |
+ else | |
+ sax_config.add_complex_element(name, options) | |
+ end | |
+ | |
+ # We only want to insert the getter and setter if they haven't been | |
+ # defined elsewhere. This is how we allow custom parsing behavior. So you | |
+ # could define the setter and have it parse the string into a date or | |
+ # whatever. However, if the getter or setter is defined by a superclass, | |
+ # we go ahead and overwrite it. This allows use to still access elements | |
+ # with names like "id". | |
+ attr_reader options[:as] unless instance_methods(false).include?(options[:as].to_s) | |
+ attr_writer options[:as] unless instance_methods(false).include?("#{options[:as]}=") | |
end | |
- | |
+ | |
def elements(name, options = {}) | |
options[:as] ||= name | |
+ | |
if options[:class] | |
sax_config.add_collection_element(name, options) | |
else | |
@@ -40,9 +47,10 @@ module SAXMachine | |
#{options[:as]} << value | |
end | |
SRC | |
+ | |
sax_config.add_top_level_element(name, options.merge(:collection => true)) | |
end | |
- | |
+ | |
if !instance_methods.include?(options[:as].to_s) | |
class_eval <<-SRC | |
def #{options[:as]} | |
@@ -50,13 +58,20 @@ module SAXMachine | |
end | |
SRC | |
end | |
- | |
+ | |
attr_writer options[:as] unless instance_methods.include?("#{options[:as]}=") | |
+ | |
+ class_eval <<-SRC | |
+ def #{options[:as]} | |
+ @#{options[:as]} ||= [] | |
+ end | |
+ SRC | |
+ | |
+ attr_writer options[:as] | |
end | |
- | |
+ | |
def sax_config | |
@sax_config ||= SAXConfig.new | |
end | |
end | |
- | |
-end | |
\ No newline at end of file | |
+end | |
diff --git a/lib/sax-machine/sax_element_config.rb b/lib/sax-machine/sax_element_config.rb | |
index f82f2dd..27de02a 100644 | |
--- a/lib/sax-machine/sax_element_config.rb | |
+++ b/lib/sax-machine/sax_element_config.rb | |
@@ -1,28 +1,28 @@ | |
module SAXMachine | |
class SAXConfig | |
- | |
class ElementConfig | |
attr_reader :name, :setter | |
- | |
+ | |
def initialize(name, options) | |
- @name = name.to_s | |
- | |
+ @name = name.to_s | |
+ @class = options[:class] | |
+ | |
if options.has_key?(:with) | |
# for faster comparisons later | |
@with = options[:with].to_a.flatten.collect {|o| o.to_s} | |
else | |
@with = nil | |
end | |
- | |
+ | |
if options.has_key?(:value) | |
@value = options[:value].to_s | |
else | |
@value = nil | |
end | |
- | |
+ | |
@as = options[:as] | |
@collection = options[:collection] | |
- | |
+ | |
if @collection | |
@setter = "add_#{options[:as]}" | |
else | |
@@ -33,7 +33,7 @@ module SAXMachine | |
def value_from_attrs(attrs) | |
attrs.index(@value) ? attrs[attrs.index(@value) + 1] : nil | |
end | |
- | |
+ | |
def attrs_match?(attrs) | |
if @with | |
@with == (@with & attrs) | |
@@ -41,15 +41,18 @@ module SAXMachine | |
true | |
end | |
end | |
- | |
+ | |
def has_value_and_attrs_match?(attrs) | |
[email protected]? && attrs_match?(attrs) | |
end | |
- | |
+ | |
def collection? | |
@collection | |
end | |
+ | |
+ def handler | |
+ SAXHandler.new(@class.new) if @class | |
+ end | |
end | |
- | |
end | |
-end | |
\ No newline at end of file | |
+end | |
diff --git a/lib/sax-machine/sax_handler.rb b/lib/sax-machine/sax_handler.rb | |
index e309ec4..e098375 100644 | |
--- a/lib/sax-machine/sax_handler.rb | |
+++ b/lib/sax-machine/sax_handler.rb | |
@@ -7,10 +7,13 @@ module SAXMachine | |
def initialize(object) | |
@object = object | |
@parsed_configs = {} | |
+ @parsed_complex_configs = {} | |
end | |
def characters(string) | |
- if parsing_collection? | |
+ if parsing_complex? | |
+ @complex_handler.characters(string) | |
+ elsif parsing_collection? | |
@collection_handler.characters(string) | |
elsif @element_config | |
@value << string | |
@@ -25,9 +28,16 @@ module SAXMachine | |
@name = name | |
@attrs = attrs | |
- if parsing_collection? | |
+ if parsing_complex? | |
+ @complex_handler.start_element(@name, @attrs) | |
+ | |
+ elsif parsing_collection? | |
@collection_handler.start_element(@name, @attrs) | |
+ elsif @complex_config = sax_config.complex_config(@name) | |
+ @complex_handler = @complex_config.handler | |
+ @complex_handler.start_element(@name, @attrs) | |
+ | |
elsif @collection_config = sax_config.collection_config(@name) | |
@collection_handler = @collection_config.handler | |
@collection_handler.start_element(@name, @attrs) | |
@@ -42,7 +52,15 @@ module SAXMachine | |
end | |
def end_element(name) | |
- if parsing_collection? && @collection_config.name == name | |
+ if parsing_complex? && @complex_config.name == name && !parsed_complex_config? | |
+ complex_mark_as_parsed | |
+ @object.send(@complex_config.setter, @complex_handler.object) | |
+ reset_current_complex | |
+ | |
+ elsif parsing_complex? && !parsed_complex_config? | |
+ @complex_handler.end_element(name) | |
+ | |
+ elsif parsing_collection? && @collection_config.name == name | |
@object.send(@collection_config.accessor) << @collection_handler.object | |
reset_current_collection | |
@@ -61,6 +79,10 @@ module SAXMachine | |
[email protected]? && [email protected]? | |
end | |
+ def parsing_complex? | |
+ !@complex_handler.nil? | |
+ end | |
+ | |
def parsing_collection? | |
!@collection_handler.nil? | |
end | |
@@ -97,15 +119,28 @@ module SAXMachine | |
@parsed_configs[element_config] | |
end | |
+ def complex_mark_as_parsed | |
+ @parsed_complex_configs[@complex_config] = true | |
+ end | |
+ | |
+ def parsed_complex_config? | |
+ @parsed_complex_configs[@complex_config] | |
+ end | |
+ | |
def reset_current_collection | |
@collection_handler = nil | |
@collection_config = nil | |
end | |
+ def reset_current_complex | |
+ @complex_handler = nil | |
+ @complex_config = nil | |
+ end | |
+ | |
def reset_current_tag | |
- @name = nil | |
- @attrs = nil | |
- @value = nil | |
+ @name = nil | |
+ @attrs = nil | |
+ @value = nil | |
@element_config = nil | |
end | |
@@ -113,4 +148,4 @@ module SAXMachine | |
@object.class.sax_config | |
end | |
end | |
-end | |
\ No newline at end of file | |
+end | |
diff --git a/sax-machine.gemspec b/sax-machine.gemspec | |
index f6c6004..43039f2 100644 | |
--- a/sax-machine.gemspec | |
+++ b/sax-machine.gemspec | |
@@ -1,8 +1,8 @@ | |
# -*- encoding: utf-8 -*- | |
Gem::Specification.new do |s| | |
- s.name = %q{sax-machine} | |
- s.version = "0.0.13" | |
+ s.name = %q{UnderpantsGnome-sax-machine} | |
+ s.version = "0.0.14" | |
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= | |
s.authors = ["Paul Dix"] | |
s.date = %q{2009-01-13} | |
diff --git a/spec/sax-machine/sax_document_spec.rb b/spec/sax-machine/sax_document_spec.rb | |
index 90d6e35..17c0b9f 100644 | |
--- a/spec/sax-machine/sax_document_spec.rb | |
+++ b/spec/sax-machine/sax_document_spec.rb | |
@@ -252,6 +252,43 @@ describe "SAXMachine" do | |
end | |
end | |
+ | |
+ describe "when using the class option" do | |
+ before :each do | |
+ class Foo | |
+ include SAXMachine | |
+ element :title | |
+ end | |
+ @klass = Class.new do | |
+ include SAXMachine | |
+ element :entry, :class => Foo | |
+ end | |
+ end | |
+ | |
+ it "should parse a single element with children" do | |
+ document = @klass.parse("<entry><title>a title</title></entry>") | |
+ document.entry.title.should == "a title" | |
+ end | |
+ | |
+ it "should use the first element when there are multiple of the same element" do | |
+ document = @klass.parse("<xml><entry><title>title 1</title></entry><entry><title>title 2</title></entry></xml>") | |
+ document.entry.title.should == "title 1" | |
+ end | |
+ | |
+ it "should not parse a top level element that is specified only in a child" do | |
+ document = @klass.parse("<xml><title>no parse</title><entry><title>correct title</title></entry></xml>") | |
+ document.entry.title.should == "correct title" | |
+ end | |
+ | |
+ it "should parse out an attribute value from the tag that starts the element" do | |
+ class Foo | |
+ element :entry, :value => :href, :as => :url | |
+ end | |
+ document = @klass.parse("<xml><entry href='http://pauldix.net'><title>paul</title></entry></xml>") | |
+ document.entry.title.should == "paul" | |
+ document.entry.url.should == "http://pauldix.net" | |
+ end | |
+ end | |
end | |
describe "elements" do | |
@@ -291,6 +328,7 @@ describe "SAXMachine" do | |
describe "when using the class option" do | |
before :each do | |
+ Object.send(:remove_const, :Foo) | |
class Foo | |
include SAXMachine | |
element :title |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment