Skip to content

Instantly share code, notes, and snippets.

@UnderpantsGnome
Created June 12, 2009 21:01
Show Gist options
  • Save UnderpantsGnome/128917 to your computer and use it in GitHub Desktop.
Save UnderpantsGnome/128917 to your computer and use it in GitHub Desktop.
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)
!@value.nil? && 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
!@value.nil? && !@value.empty?
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