Permalink
Browse files

Added libxml helper and refactored a few of the methods

  • Loading branch information...
1 parent 76c76db commit 278c9688d8eecb3fd87ad0ef970bf163ead256cb @jnunemaker jnunemaker committed Nov 17, 2008
Showing with 164 additions and 71 deletions.
  1. +61 −63 lib/happymapper.rb
  2. +93 −0 lib/happymapper/libxml_ext/libxml_helper.rb
  3. +1 −1 spec/fixtures/pita.xml
  4. +9 −7 spec/happymapper_spec.rb
View
@@ -1,8 +1,12 @@
+directory = File.dirname(__FILE__)
+$:.unshift(directory) unless $:.include?(directory) || $:.include?(File.expand_path(directory))
+
require 'date'
require 'time'
require 'rubygems'
gem 'libxml-ruby', '>= 0.8.3'
require 'xml'
+require 'happymapper/libxml_ext/libxml_helper'
class Boolean; end
@@ -66,26 +70,22 @@ def create_accessor(name)
create_setter(name)
end
- def parse(xml, o={})
+ def parse(xml, o={})
+ doc = xml.is_a?(LibXML::XML::Node) ? xml : xml.to_libxml_doc
+ nodes = doc.find(get_tag_name)
options = {:single => false}.merge(o)
- if xml.is_a?(LibXML::XML::Node)
- doc = xml
- else
- parser = XML::Parser.new
- parser.string = xml
- doc = parser.parse
- end
-
- collection = []
-
- doc.find(get_tag_name).each do |el|
+ collection = nodes.inject([]) do |acc, el|
obj = new
attributes.each { |attr| obj.send("#{attr.name}=", attr.from_xml_node(el)) }
elements.each { |elem| obj.send("#{elem.name}=", elem.from_xml_node(el)) }
- collection << obj
+ acc << obj
end
+ # per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
+ nodes = nil
+ GC.start
+
options[:single] ? collection.first : collection
end
end
@@ -105,68 +105,66 @@ def initialize(name, type, o={})
def name=(new_name)
@name = new_name.to_s
end
-
- # el.attributes[a.xml_name]
- # el.find(e.xml_name).first.content
- def typecast(value)
- return value if value.kind_of?(type) || value.nil?
- begin
- if type == String then value.to_s
- elsif type == Float then value.to_f
- elsif type == Time then Time.parse(value.to_s)
- elsif type == Date then Date.parse(value.to_s)
- elsif type == DateTime then DateTime.parse(value.to_s)
- elsif type == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
- elsif type == Integer
- # ganked from datamapper
- value_to_i = value.to_i
- if value_to_i == 0 && value != '0'
- value_to_s = value.to_s
- begin
- Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
- rescue ArgumentError
- nil
- end
- else
- value_to_i
- end
- else
- value
- end
- rescue
- value
- end
- end
-
+
def from_xml_node(node)
- if happy_mapper?
- type.parse(node, @options)
+ if primitive?
+ typecast(value_from_xml_node(node))
else
- value = value_from_xml_node(node)
- typecast(value)
- end
- end
-
- def value_from_xml_node(value)
- value = if element?
- result = value.find_first(xml_name)
- result ? result.content : nil
- else
- value[xml_name]
+ type.parse(node, @options)
end
end
- def happy_mapper?
- !Types.include?(type)
+ def primitive?
+ Types.include?(type)
end
def element?
@xml_type == 'element'
end
def attribute?
- @xml_type == 'attribute'
- end
+ !element?
+ end
+
+ private
+ def typecast(value)
+ return value if value.kind_of?(type) || value.nil?
+ begin
+ if type == String then value.to_s
+ elsif type == Float then value.to_f
+ elsif type == Time then Time.parse(value.to_s)
+ elsif type == Date then Date.parse(value.to_s)
+ elsif type == DateTime then DateTime.parse(value.to_s)
+ elsif type == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
+ elsif type == Integer
+ # ganked from datamapper
+ value_to_i = value.to_i
+ if value_to_i == 0 && value != '0'
+ value_to_s = value.to_s
+ begin
+ Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
+ rescue ArgumentError
+ nil
+ end
+ else
+ value_to_i
+ end
+ else
+ value
+ end
+ rescue
+ value
+ end
+ end
+
+ def value_from_xml_node(value)
+ if element?
+ result = value.find_first(xml_name)
+ result ? result.content : nil
+ else
+ value[xml_name]
+ end
+ end
end
class Element < Item; end
@@ -0,0 +1,93 @@
+require "xml/libxml"
+
+class XML::Node
+ ##
+ # Open up XML::Node from libxml and add convenience methods inspired
+ # by hpricot.
+ # (http://code.whytheluckystiff.net/hpricot/wiki/HpricotBasics)
+ # Also:
+ # * provide better handling of default namespaces
+
+ # an array of default namespaces to past into
+ attr_accessor :default_namespaces
+
+ # find the child node with the given xpath
+ def at(xpath)
+ self.find_first(xpath)
+ end
+
+ # find the array of child nodes matching the given xpath
+ def search(xpath)
+ results = self.find(xpath).to_a
+ if block_given?
+ results.each do |result|
+ yield result
+ end
+ end
+ return results
+ end
+
+ # alias for search
+ def /(xpath)
+ search(xpath)
+ end
+
+ # return the inner contents of this node as a string
+ def inner_xml
+ child.to_s
+ end
+
+ # alias for inner_xml
+ def inner_html
+ inner_xml
+ end
+
+ # return this node and its contents as an xml string
+ def to_xml
+ self.to_s
+ end
+
+ # alias for path
+ def xpath
+ self.path
+ end
+
+ # provide a name for the default namespace
+ def register_default_namespace(name)
+ self.namespace.each do |n|
+ if n.to_s == nil
+ register_namespace("#{name}:#{n.href}")
+ return
+ end
+ end
+ raise "No default namespace found"
+ end
+
+ # register a namespace, of the form "foo:http://example.com/ns"
+ def register_namespace(name_and_href)
+ (@default_namespaces ||= []) << name_and_href
+ end
+
+ def find_with_default_ns(xpath_expr, namespace=nil)
+ find_base(xpath_expr, namespace || default_namespaces)
+ end
+
+ def find_first_with_default_ns(xpath_expr, namespace=nil)
+ find_first_base(xpath_expr, namespace || default_namespaces)
+ end
+
+
+ alias_method :find_base, :find unless method_defined?(:find_base)
+ alias_method :find, :find_with_default_ns
+
+ alias_method :find_first_base, :find_first unless method_defined?(:find_first_base)
+ alias_method :find_first, :find_first_with_default_ns
+end
+
+class String
+ def to_libxml_doc
+ xp = XML::Parser.new
+ xp.string = self
+ return xp.parse
+ end
+end
View
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ItemSearchResponse>
+<ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2005-10-05">
<OperationRequest>
<HTTPHeaders>
<Header Name="UserAgent">
View
@@ -126,7 +126,7 @@ class Foo; include HappyMapper end
@posts.size.should == 20
end
- it "should properly assign attributes" do
+ it "should properly create objects" do
first = @posts.first
first.href.should == 'http://roxml.rubyforge.org/'
first.hash.should == '19bba2ab667be03a19f67fb67dc56917'
@@ -147,38 +147,40 @@ class Foo; include HappyMapper end
@statuses.size.should == 20
end
- it "should properly assign attributes" do
+ it "should properly create objects" do
first = @statuses.first
first.id.should == 882281424
first.created_at.should == Time.mktime(2008, 8, 9, 1, 38, 12)
first.source.should == 'web'
- first.truncated.should == false
+ first.truncated.should be_false
first.in_reply_to_status_id.should == 1234
first.in_reply_to_user_id.should == 12345
- first.favorited.should == false
+ first.favorited.should be_false
first.user.id.should == 4243
first.user.name.should == 'John Nunemaker'
first.user.screen_name.should == 'jnunemaker'
first.user.location.should == 'Mishawaka, IN, US'
first.user.description.should == 'Loves his wife, ruby, notre dame football and iu basketball'
first.user.profile_image_url.should == 'http://s3.amazonaws.com/twitter_production/profile_images/53781608/Photo_75_normal.jpg'
first.user.url.should == 'http://addictedtonew.com'
- first.user.protected.should == false
+ first.user.protected.should be_false
first.user.followers_count.should == 486
end
end
- describe "#parse (with PITA xml)" do
+ # TODO: someone please get xml with namespaces working, kthxbai
+ describe "#parse (with xml that has namespace)" do
before do
@items = PITA::Items.parse(File.read(File.dirname(__FILE__) + '/fixtures/pita.xml'), :single => true)
end
- it "should properly assign attributes" do
+ it "should properly create objects" do
@items.total_results.should == 22
@items.total_pages.should == 3
first = @items.items.first
first.asin.should == '0321480791'
first.detail_page_url.should == 'http://www.amazon.com/gp/redirect.html%3FASIN=0321480791%26tag=ws%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0321480791%253FSubscriptionId=13BGQE8Q6AKCRYPHG0G2'
+ first.manufacturer.should == 'Addison-Wesley Professional'
end
end

0 comments on commit 278c968

Please sign in to comment.