<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>test/fixtures/articles/noroot.xml</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,10 +1,10 @@
-require 'xmlsimple'
+require 'xml'
 
 module WithXml
-  # Customize the xml serialization for ActiveRecord objects. It uses the  with_xml block to 
+
+  # Customize the xml serialization for ActiveRecord objects. It uses the  with_xml block to
   # define options declared in the ActiveRecord that allows for the customization of the to_xml and
   # from_xml call.
-  #
   def self.included(base)
     base.extend ClassMethods
   end
@@ -14,7 +14,7 @@ module WithXml
   end
 
   module ClassMethods
-    
+
     # Specifies the options of handling incoming (from_xml) xml data and serialization (to_xml)
     #
     #   class Article &lt; ActiveRecord::Base
@@ -38,16 +38,15 @@ module WithXml
 
   module InstanceMethods
 
-    ##
     # Override the default from_xml and perform some mapping goodness
+    #  +xml+ string of the xml to import
     def from_xml(xml)
-      mapper = SupportingClasses::Binder.new(&quot;&lt;opt&gt;#{xml}&lt;/opt&gt;&quot;, self)
-      mapped_attributes = mapper.bind
+      mapper = SupportingClasses::Binder.new(&quot;#{xml}&quot;, self)
+      mapped_attributes = mapper.map
       self.attributes = mapped_attributes if mapped_attributes
       self
     end
-    
-    ##
+
     # Override the default to_xml with our custom options
     def to_xml(options = {}, &amp;block)
       with_xml = self.class.read_inheritable_attribute(:with_xml)
@@ -56,7 +55,7 @@ module WithXml
       end
       super(options, &amp;block)
     end
-    
+
   end
 
   module SupportingClasses
@@ -69,26 +68,25 @@ module WithXml
       attr_reader :serialize_opts
 
       def initialize(name, opts, binder_table, &amp;block)
+        @matchers = []
         @name = name.to_sym
-        @binders = binder_table[@name] = []
+        @binders = binder_table[@name] = {}
         instance_eval(&amp;block) if block
         extract_options!(opts)
       end
 
-      ##
       # Adds each 'bind' option to the binders array
-      def bind(name, xml_attr_opts)
-        @binders &lt;&lt; SupportingClasses::XmlAttr.new(name, xml_attr_opts)
+      #  +name+ of the attribute
+      def map(name, xml_attr_opts)
+        @binders[name] = SupportingClasses::XmlAttr.new(name, xml_attr_opts)
       end
 
-      ##
       # Adds the options used to export the xml (to_xml)
       def serialize(opts)
         @serialize_opts = opts
       end
 
-      ##
-      # Return true or false if 
+      # Return true or false if we have a match
       def has_match?(name)
         matchers = @opts[:match] || Array.new
         return true if matchers.find {|n| n == name}
@@ -96,10 +94,12 @@ module WithXml
 
       private
 
+      # Parse options decalared in the with_xml
       def extract_options!(opts)
         return nil if opts.nil?
         @opts = opts
         @matchers = opts[:alias] if opts[:alias]
+        @matchers.collect!{|x| x.to_s}
       end
 
     end
@@ -107,10 +107,13 @@ module WithXml
     class XmlAttr
       attr_reader :name
       attr_reader :matchers
-      
-      def initialize(name, opts)
-        @name, @opts = name, opts
-        @matchers = opts[:to] if opts[:to]
+
+      def initialize(name, opts = {})
+        @matchers = []
+        @name, @opts = name.to_s, opts
+        opts[:to] = [] unless opts[:to]
+        opts[:to].insert(0, name.to_s)
+        @matchers = opts[:to].flatten
       end
     end
 
@@ -123,56 +126,79 @@ module WithXml
         @with_xml.matchers &lt;&lt; activerecord.class.class_name.downcase
       end
 
-      ##
       # Creates a new hash map that matches the ActiveRecord fields from the given xml
-      def bind
-        xml_hash =  Hash.from_xml(xml)
-        new_xml = find_hash_for(xml_hash, @with_xml.matchers) do |key, value|
-          break(value)
+      def map
+        xml_attrs = {}
+        return xml_attrs unless valid_xml?(xml)
+
+        activerecord.attributes.each_key do |key|
+          if !@with_xml.binders.has_key?(key.to_sym)
+            @with_xml.binders[key.to_sym] = SupportingClasses::XmlAttr.new(key)
+          end
+        end
+
+        root_xml = find_root_element(xml)
+        if root_xml
+          xml_attrs = find_attributes_for(root_xml)
         end
-        return hash_for(new_xml)
+        return xml_attrs
       end
 
       private
 
-      ##
-      # Searches all the keys (even nested) and returns the first one
-      def find_hash_for(hash, desired_keys, &amp;block)
-        return false unless Hash === hash
-        hash.each_pair do |key, value|
-          if desired_keys.include?(key) || find_hash_for(value, desired_keys, &amp;block)
-            yield(key, value)
-            return true
+      # Determine if the xml is well-formed or not
+      #   +xml+ string to validate
+      def valid_xml?(xml)
+        begin
+          XML::Document.string(xml)
+        rescue Exception
+          return false
+          # Return nil if an exception is thrown
+        end
+      end
+
+      # Based on the aliases, find the root element of the xml document
+      #   +xml+ string that contains the root elements
+      def find_root_element(xml)
+        doc = XML::Document.string(xml)
+        @with_xml.matchers.each do |root|
+          if new_xml = find_element(doc, root)
+            return new_xml
           end
         end
         return false
       end
-      
-      ##
-      # Update the xml hash with the correct keys and values
-      def hash_for(xml)
-        return false unless Hash === xml
-        xml.each do |element_name, element_value|
-          field = field_for(element_name)
-          xml.delete(element_name)
-          if activerecord.has_attribute?(field)
-            xml[field] = element_value.strip unless element_value.nil?
+
+      # Given, the list of :to options, find the first ActiveRecord field that matches the given key
+      def find_attributes_for(element)
+        attrs = {}
+        @with_xml.binders.each do |key, binder|
+          binder.matchers.each do |matcher|
+            if node = find_element(element, matcher)
+              attrs[binder.name] = find_text(node)
+            end
           end
         end
+        return attrs
+      end
+
+      # Find the first element matching the xpath (ignores case)
+      # +doc+ to be searched
+      def find_element(doc, xpath = '/')
+        xml = (doc.find_first(&quot;//#{xpath}&quot;) ||
+              doc.find_first(&quot;//#{xpath.downcase}&quot;) ||
+              doc.find_first( &quot;//#{xpath.upcase}&quot;))
         return xml
       end
 
-      ##
-      # Given, the list of :to options, find the first ActiveRecord field that matches the given key
-      def field_for(element)
-        return element unless String === element
-        atrribute_name = element.downcase # ignore case by default
-        @with_xml.binders.each do |binder|
-          if binder.name.to_s == atrribute_name || binder.matchers.include?(atrribute_name)
-            return binder.name
-          end
+      # Find and format each element text
+      def find_text(doc)
+        return if doc.nil?
+        text = &quot;&quot;
+        doc.children.each do |child|
+          text &lt;&lt; &quot;#{child.content.strip} &quot; unless child.content.strip.empty?
         end
-        return atrribute_name
+        text.strip
       end
 
     end</diff>
      <filename>lib/with_xml.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,9 @@
 class Article &lt; ActiveRecord::Base
+
   with_xml :feeds, :alias =&gt; [&quot;magazine&quot;, &quot;blog&quot;, &quot;newspaper&quot;] do
-    bind :heading, :to =&gt; [&quot;subject&quot;, &quot;title&quot;]
-    bind :body, :to =&gt; [&quot;content&quot;, &quot;message&quot;, &quot;post&quot;]
+    map :heading, :to =&gt; [&quot;subject&quot;, &quot;title&quot;]
+    map :body, :to =&gt; [&quot;content[@type='copy']&quot;, &quot;message&quot;, &quot;post&quot;]
     serialize :except =&gt; [ :name ], :skip_instruct =&gt; true
   end
-end
\ No newline at end of file
+  attr_accessor :sources
+end</diff>
      <filename>test/fixtures/article.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,8 @@
-&lt;article&gt;
+&lt;ARTICLE xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot;&gt;
 	&lt;heading&gt;Page Six&lt;/heading&gt;
 	&lt;body&gt;Content for article&lt;/body&gt;
-&lt;/article&gt;
\ No newline at end of file
+	&lt;sources&gt;
+		&lt;source&gt;Source 1&lt;/source&gt;
+		&lt;source&gt;Source 2&lt;/source&gt;
+	&lt;/sources&gt;
+&lt;/ARTICLE&gt;
\ No newline at end of file</diff>
      <filename>test/fixtures/articles/article.xml</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,9 @@
 &lt;magazine&gt;
 	&lt;title&gt;The Washington Post&lt;/title&gt;
-	&lt;content&gt;This is the content&lt;/content&gt;
+    &lt;nested&gt;
+      &lt;content type=&quot;copy&quot;&gt;This is the content&lt;/content&gt;
+      &lt;content type=&quot;copy2&quot;&gt;This is NOT the content&lt;/content&gt;
+      &lt;content type=&quot;copy3&quot;&gt;This is NOT the content&lt;/content&gt;
+      &lt;content type=&quot;copy4&quot;&gt;This is NOT the content&lt;/content&gt;
+    &lt;/nested&gt;
 &lt;/magazine&gt;
\ No newline at end of file</diff>
      <filename>test/fixtures/articles/magazine.xml</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
 ActiveRecord::Schema.define(:version =&gt; 1) do
   create_table :articles do |t|
-    t.string :name, :heading, :body, :author
+    t.string :name, :heading, :body, :author, :sources
   end
 end
\ No newline at end of file</diff>
      <filename>test/schema.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,31 +3,39 @@ require File.expand_path(File.dirname(__FILE__) + &quot;/test_helper&quot;)
 class WithXmlTest &lt; Test::Unit::TestCase
   fixtures :articles
   include TagMatchers
-  
+
+  def test_field_override
+    article = articles(:magazine) # bind :heading, :to =&gt; [&quot;subject&quot;, &quot;title&quot;]
+    assert_equal &quot;New Yorker&quot;, article.heading
+    magazine = load_xml_article(&quot;magazine.xml&quot;)  #title = &quot;The Washington Post&quot;
+    article.from_xml(magazine)
+    assert_equal &quot;The Washington Post&quot;, article.heading
+  end
+
   # test options settings
-  
+
   def test_name_setter
     fixture = Article.with_xml(:test, {:alias =&gt; [&quot;DAILYNEWS&quot;]} ) do
-      bind :heading, :to =&gt; [&quot;subject&quot;, &quot;title&quot;]
+      map :heading, :to =&gt; [&quot;subject&quot;, &quot;title&quot;]
     end
     assert_equal :test, fixture.name
   end
-  
+
   def test_options_setter
     fixture = Article.with_xml(:test, {:alias =&gt; [&quot;DAILYNEWS&quot;]} ) do
-      bind :heading, :to =&gt; [&quot;subject&quot;, &quot;title&quot;]
+      map :heading, :to =&gt; [&quot;subject&quot;, &quot;title&quot;]
     end
     assert_not_nil fixture.opts[:alias]
   end
-  
+
   def test_matchers
     fixture = Article.with_xml(:test, {:alias =&gt; [&quot;BEGIN_ARTICLE&quot;, &quot;ARTICLE&quot;]} ) do
-      bind :heading, :to =&gt; [&quot;subject&quot;, &quot;title&quot;]
-      bind :body, :to =&gt; [&quot;message&quot;, &quot;content&quot;]
+      map :heading, :to =&gt; [&quot;subject&quot;, &quot;title&quot;]
+      map :body, :to =&gt; [&quot;message&quot;, &quot;content&quot;]
     end
-    assert_equal fixture.matchers.size, 2
+    assert_equal 2, fixture.matchers.size
   end
-  
+
   # calling from_xml
 
   def test_field_override
@@ -37,7 +45,7 @@ class WithXmlTest &lt; Test::Unit::TestCase
     article.from_xml(magazine)
     assert_equal &quot;The Washington Post&quot;, article.heading
   end
-  
+
   def test_from_xml_with_new_instance
     article = Article.new  # bind :body, :to =&gt; [&quot;content&quot;, &quot;message&quot;, &quot;post&quot;]
     blog = load_xml_article(&quot;blog.xml&quot;)
@@ -45,14 +53,14 @@ class WithXmlTest &lt; Test::Unit::TestCase
     assert_equal &quot;The Post&quot;, article.body
     assert_equal &quot;TechCrunch&quot;, article.heading
   end
-  
+
   def test_from_xml_with_unknown_field
     article = Article.new
     newspaper = load_xml_article(&quot;newspaper.xml&quot;) # contains unkown field
     article.from_xml(newspaper)
     assert_equal &quot;The Wall Street Journal&quot;, article.heading
   end
-  
+
   def test_mapper_with_no_matchers
     article = Article.new
     article_xml = load_xml_article(&quot;article.xml&quot;)
@@ -60,24 +68,51 @@ class WithXmlTest &lt; Test::Unit::TestCase
     assert_equal &quot;Page Six&quot;, article.heading
     assert_equal &quot;Content for article&quot;, article.body
   end
-  
+
   def test_mapper_with_nested_root
-    article = Article.new 
+    article = Article.new
     article_xml = load_xml_article(&quot;nested_article.xml&quot;)
     article.from_xml(article_xml.to_s)
     assert_equal &quot;Page Six&quot;, article.heading
     assert_equal &quot;Content for article&quot;, article.body
   end
 
+  def test_mapper_multiple_values
+    article = Article.new
+    article_xml = load_xml_article(&quot;article.xml&quot;)
+    article.from_xml(article_xml.to_s)
+    assert_equal &quot;Source 1 Source 2&quot;, article.sources
+  end
+
   def test_import_with_capitalized_fields
     article = Article.new
     newspaper = load_xml_article(&quot;newspaper.xml&quot;) # contains AUTHOR in all CAPS
     article.from_xml(newspaper)
     assert_equal &quot;Caps should be ignored by default&quot;, article.author
   end
-  
+
+  def test_from_xml_save
+    article = Article.new  # bind :body, :to =&gt; [&quot;content&quot;, &quot;message&quot;, &quot;post&quot;]
+    blog = load_xml_article(&quot;article.xml&quot;)
+    assert article.save!
+  end
+
+  def test_from_xml_with_multiple_nodes
+    article = articles(:magazine)
+    magazine = load_xml_article(&quot;magazine.xml&quot;)  #body = &quot;This is the content&quot;
+    article.from_xml(magazine)
+    assert_equal &quot;This is the content&quot;, article.body
+  end
+
+  def test_from_xml_with_no_root
+    article = Article.new
+    noroot = load_xml_article(&quot;noroot.xml&quot;)
+    article.from_xml(noroot) # should not cause errors
+    assert_equal nil, article.body
+  end
+
   # calling to_xml
-  
+
   def test_default_to_xml_behaviour
     article = Article.new
     xml = load_xml_article(&quot;article.xml&quot;)
@@ -85,7 +120,7 @@ class WithXmlTest &lt; Test::Unit::TestCase
     export = article.to_xml
     assert has_tag(export, '//article/heading', &quot;Page Six&quot;)
   end
-  
+
   def test_export_with_options
     article = Article.new
     xml = load_xml_article(&quot;article.xml&quot;)
@@ -93,7 +128,7 @@ class WithXmlTest &lt; Test::Unit::TestCase
     export = article.to_xml
     assert has_tag(export, '//name') == false
   end
-  
+
   def test_export_with_instruct_skipped
     article = Article.new
     xml = load_xml_article(&quot;article.xml&quot;)
@@ -110,7 +145,5 @@ class WithXmlTest &lt; Test::Unit::TestCase
     assert has_tag(export, '//name')
     assert has_tag(export, '//author') == false
   end
-  
-
 
 end</diff>
      <filename>test/with_xml_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>084151651ab20d115bb5680441b63a770f1e8627</id>
    </parent>
  </parents>
  <author>
    <name>Matt Sears</name>
    <email>matt@mattsears.com</email>
  </author>
  <url>http://github.com/mattsears/with_xml/commit/ebed07243a863910cb5586fd9f942fb1dff44068</url>
  <id>ebed07243a863910cb5586fd9f942fb1dff44068</id>
  <committed-date>2009-03-28T18:10:24-07:00</committed-date>
  <authored-date>2009-03-28T18:10:24-07:00</authored-date>
  <message>Switch xml parsers from rexml to libxml and added a few more test cases</message>
  <tree>9054bc0cd2717edd7209bc6ab5a9acf10ab02ba5</tree>
  <committer>
    <name>Matt Sears</name>
    <email>matt@mattsears.com</email>
  </committer>
</commit>
