Permalink
Browse files

Merge remote-tracking branch 'origin/master' into HEAD

* gemspec is now using the unhappymapper version save for teh name
* README should contain most of the same text save for the installation
  instructions from unhappymapper
* Gemfile should have versions specified like in unhappymapper

Conflicts:
	.gitignore
	Gemfile
	README.md
	nokogiri-happymapper.gemspec
  • Loading branch information...
2 parents 830ce1f + 4a7d7cf commit d951d167c9d885986c2d95d05846b2fdd9baa851 @burtlo burtlo committed Apr 9, 2012
View
@@ -6,3 +6,4 @@ tags
Gemfile.lock
*.swp
*.swo
+.idea
View
11 Gemfile
@@ -1,8 +1,11 @@
-source :rubygems
+source "http://rubygems.org"
gemspec
+gem "nokogiri"
+
group :development do
- gem "rake"
- gem "rspec", "~> 2.5.0"
-end
+ gem "rake", "0.9.2"
+ gem "rspec", "2.8"
+ gem "yard"
+end
View
@@ -0,0 +1,24 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ diff-lcs (1.1.3)
+ nokogiri (1.5.0)
+ rake (0.9.2)
+ rspec (2.8.0)
+ rspec-core (~> 2.8.0)
+ rspec-expectations (~> 2.8.0)
+ rspec-mocks (~> 2.8.0)
+ rspec-core (2.8.0)
+ rspec-expectations (2.8.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.8.0)
+ yard (0.7.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ nokogiri
+ rake (= 0.9.2)
+ rspec (= 2.8)
+ yard
View
@@ -1,46 +0,0 @@
-History
-License
-Manifest
-README
-Rakefile
-TODO
-examples/amazon.rb
-examples/current_weather.rb
-examples/dashed_elements.rb
-examples/family_tree.rb
-examples/post.rb
-examples/twitter.rb
-lib/happymapper.rb
-lib/happymapper/attribute.rb
-lib/happymapper/element.rb
-lib/happymapper/item.rb
-lib/happymapper/text_node.rb
-lib/happymapper/version.rb
-nokogiri-happymapper.gemspec
-spec/fixtures/address.xml
-spec/fixtures/ambigous_items.xml
-spec/fixtures/analytics.xml
-spec/fixtures/commit.xml
-spec/fixtures/current_weather.xml
-spec/fixtures/dictionary.xml
-spec/fixtures/family_tree.xml
-spec/fixtures/lastfm.xml
-spec/fixtures/multiple_namespaces.xml
-spec/fixtures/multiple_primitives.xml
-spec/fixtures/pita.xml
-spec/fixtures/posts.xml
-spec/fixtures/product_default_namespace.xml
-spec/fixtures/product_no_namespace.xml
-spec/fixtures/product_single_namespace.xml
-spec/fixtures/quarters.xml
-spec/fixtures/radar.xml
-spec/fixtures/statuses.xml
-spec/happymapper_attribute_spec.rb
-spec/happymapper_element_spec.rb
-spec/happymapper_item_spec.rb
-spec/happymapper_spec.rb
-spec/happymapper_text_node_spec.rb
-spec/spec.opts
-spec/spec_helper.rb
-website/css/common.css
-website/index.html
View
177 README.md
@@ -1,27 +1,12 @@
HappyMapper
===========
-Object to XML mapping library. I have included examples to help get you going. The specs
-should also point you in the right direction.
+Happymapper allows you to parse XML data and convert it quickly and easily into ruby data structures.
-This is a custom version of HappyMapper, available there:
-http://github.com/dam5s/happymapper/
+This project is a fork of the great work done first by
+[jnunemaker](https://github.com/jnunemaker/happymapper).
-
-Installation
-------------
-
-*Via Rubygems:*
-
- $ gem install nokogiri-happymapper
-
-*Via [Bundler](http://gembundler.com/), add it to your Gemfile and then `bundle install`*
-
- gem 'nokogiri-happymapper', :require => 'happymapper'
-
-
-Differences
------------
+## Major Differences
* [Nokogiri](http://nokogiri.org/) support
* Text nodes parsing
@@ -30,11 +15,22 @@ Differences
* Fixes for [namespaces when using composition of classes](https://github.com/burtlo/happymapper/commit/fd1e898c70f7289d2d2618d629b56f2f6623785c)
* Fixes for instances of XML where a [namespace is defined but no elements with that namespace are found](https://github.com/burtlo/happymapper/commit/9614221a80ff3bda18ff859aa751dff29cf52fd3).
+## Installation
-Examples
---------
+### [Rubygems](https://rubygems.org/gems/nokogiri-happymapper)
-## Element Mapping
+ $ gem install nokogiri-happymapper
+
+### [Bundler](http://gembundler.com/)
+Add the unhappymapper gem to your project's `Gemfile`.
+
+ gem 'nokogiri-happymapper', :require => 'happymapper'
+
+Run the bundler command to install the gem:
+
+ $ bundle install
+
+# Examples
Let's start with a simple example to get our feet wet. Here we have a simple example of XML that defines some address information:
@@ -115,14 +111,14 @@ What if our address XML was a little different, perhaps we allowed multiple stre
Similar to `element` or `has_one`, the declaration for when you have multiple elements you simply use:
- has_many :street, String, :tag => 'street'
+ has_many :streets, String, :tag => 'street'
-Your resulting `street` method will now return an array.
+Your resulting `streets` method will now return an array.
address = Address.parse(ADDRESS_XML_DATA, :single => true)
- puts address.street.join('\n')
+ puts address.streets.join('\n')
-Imagine that you have to write `street.join('\n')` for the rest of eternity throughout your code. It would be a nightmare and one that you could avoid by creating your own convenience method.
+Imagine that you have to write `streets.join('\n')` for the rest of eternity throughout your code. It would be a nightmare and one that you could avoid by creating your own convenience method.
require 'happymapper'
@@ -131,10 +127,10 @@ Imagine that you have to write `street.join('\n')` for the rest of eternity thro
tag 'address'
- has_many :street, String
+ has_many :streets, String
- def street
- @street.join('\n')
+ def streets
+ @streets.join('\n')
end
element :postcode, String, :tag => 'postcode'
@@ -143,7 +139,7 @@ Imagine that you have to write `street.join('\n')` for the rest of eternity thro
element :country, String, :tag => 'country'
end
-Now when we call the method `street` we get a single value, but we still have the instance variable `@street` if we ever need to the values as an array.
+Now when we call the method `streets` we get a single value, but we still have the instance variable `@streets` if we ever need to the values as an array.
## Attribute Mapping
@@ -164,6 +160,40 @@ Attributes are absolutely the same as `element` or `has_many`
Again, you can omit the tag if the attribute accessor symbol matches the name of the attribute.
+### Attributes On Empty Child Elements
+
+ <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
+ <id>tag:all-the-episodes.heroku.com,2005:/tv_shows</id>
+ <link rel="alternate" type="text/html" href="http://all-the-episodes.heroku.com"/>
+ <link rel="self" type="application/atom+xml" href="http://all-the-episodes.heroku.com/tv_shows.atom"/>
+ <title>TV Shows</title>
+ <updated>2011-07-10T06:52:27Z</updated>
+ </feed>
+
+In this case you would need to map an element to a new `Link` class just to access `<link>`s attributes, except that there is an alternate syntax. Instead of
+
+ class Feed
+ # ....
+ has_many :links, Link, :tag => 'link', :xpath => '.'
+ end
+
+ class Link
+ include HappyMapper
+
+ attribute :rel, String
+ attribute :type, String
+ attribute :href, String
+ end
+
+You can drop the `Link` class and simply replace the `has_many` on `Feed` with
+
+ element :link, String, :single => false, :attributes => { :rel => String, :type => String, :href => String }
+
+As there is no content, the type given for `:link` (`String` above) is irrelevant, but `nil` won't work and other types may try to perform typecasting and fail. You can omit the :single => false for elements that only occur once within their parent.
+
+This syntax is most appropriate for elements that (a) have attributes but no content and (b) only occur at only one level of the heirarchy. If `<feed>` contained another element that also contained a `<link>` (as atom feeds generally do) it would be DRY-er to use the first syntax, i.e. with a separate `Link` class.
+
+
## Class composition (and Text Node)
Our address has a country and that country element has a code. Up until this point we neglected it as we declared a `country` as being a `String`.
@@ -185,12 +215,12 @@ Well if we only going to parse country, on it's own, we would likely create a cl
tag 'country'
attribute :code, String
- text_node :name, String
+ content :name, String
end
-We are utilizing an `attribute` declaration and a new declaration called `text_node`.
+We are utilizing an `attribute` declaration and a new declaration called `content`.
-* `text_node` is used when you want the text contained within the element
+* `content` is used when you want the text contained within the element
Awesome, now if we were to redeclare our `Address` class we would use our new `Country` class.
@@ -199,12 +229,12 @@ Awesome, now if we were to redeclare our `Address` class we would use our new `C
tag 'address'
- has_many :street, String
+ has_many :streets, String, :tag => 'street'
- def street
- @street.join('\n')
+ def streets
+ @streets.join('\n')
end
-
+
element :postcode, String, :tag => 'postcode'
element :housenumber, String, :tag => 'housenumber'
element :city, String, :tag => 'city'
@@ -218,7 +248,39 @@ Instead of `String`, `Boolean`, or `Integer` we say that it is a `Country` and H
A quick note, in the above example we used the constant `Country`. We could have used `'Country'`. The nice part of using the latter declaration, enclosed in quotes, is that you do not have to define your class before this class. So Country and Address can live in separate files and as long as both constants are available when it comes time to parse you are golden.
-## Inheritance (it doesn't work!)
+## Custom XPATH
+
+### Has One, Has Many
+
+Getting to elements deep down within your XML can be a little more work if you did not have xpath support. Consider the following example:
+
+ <media>
+ <gallery>
+ <title href="htttp://fishlovers.org/friends">Friends Who Like Fish</title>
+ <picture>
+ <name>Burtie Sanchez</name>
+ <img>burtie01.png</img>
+ </picture>
+ </gallery>
+ <picture>
+ <name>Unsorted Photo</name>
+ <img>bestfriends.png</img>
+ </picture>
+ </media>
+
+You may want to map the sub-elements contained buried in the 'gallery' as top level items in the media. Traditionally you could use class composition to accomplish this task, however, using the xpath attribute you have the ability to shortcut some of that work.
+
+ class Media
+ include HappyMapper
+
+ has_one :title, String, :xpath => 'gallery/title'
+ has_one :link, String, :xpath => 'gallery/title/@href'
+ end
+
+
+## Subclasses
+
+### Inheritance (it doesn't work!)
While mapping XML to objects you may arrive at a point where you have two or more very similar structures.
@@ -274,13 +336,9 @@ You can however, use some module mixin power to save you those keystrokes and im
module Content
def self.included(content)
- content.instance_eval do
-
- has_one :title, String
- has_one :author, String
- has_one :published, Time
-
- end
+ content.has_one :title, String
+ content.has_one :author, String
+ content.has_one :published, Time
end
def published_time
@@ -307,7 +365,8 @@ You can however, use some module mixin power to save you those keystrokes and im
Here, when we include `Content` in both of these classes the module method `#included` is called and our class is given as a parameter. So we take that opportunity to do some surgery and define our happymapper elements as well as any other methods that may rely on those instance variables that come along in the package.
-## Subclasses
+## Filtering with XPATH
+
I ran into a case where I wanted to capture all the pictures that were directly under media, but not the ones contained within a gallery.
<media>
@@ -348,7 +407,7 @@ I was mistaken and that is because, by default the mappings are assigned XPATH '
## Namespaces
-Obviously your XML and these trivial examples are going to easy to map and parse because they lack the treacherous namespaces that befall most XML files.
+Obviously your XML and these trivial examples are easy to map and parse because they lack the treacherous namespaces that befall most XML files.
Perhaps our `address` XML is really swarming with namespaces:
@@ -361,9 +420,15 @@ Perhaps our `address` XML is really swarming with namespaces:
<prefix:country code="de">Germany</prefix:country>
</prefix:address>
-Here again is our address example with a made up namespace called `prefix` that comes direct to use from unicornland, a very magical place indeed. Well we are going to have to do some work on our class definition and that simply adding this one liner to `Address`:
+Here again is our address example with a made up namespace called `prefix` that comes direct to use from unicornland, a very magical place indeed. Well we are going to have to do some work on our class definition and that simply adding this one liner to the `Address` class:
- namespace 'prefix'
+ class Address
+ include HappyMapper
+
+ tag 'address'
+ namespace 'prefix'
+ # ... rest of the code ...
+ end
Of course, if that is too easy for you, you can append a `:namespace => 'prefix` to every one of the elements that you defined.
@@ -378,7 +443,7 @@ I definitely recommend the former, as it saves you a whole hell of lot of typing
Imagine that our `country` actually belonged to a completely different namespace.
<prefix:address location='home' xmlns:prefix="http://www.unicornland.com/prefix"
- xmlns:different="http://www.trollcountry.com/different">
+ xmlns:prefix="http://www.trollcountry.com/different">
<prefix:street>Milchstrasse</prefix:street>
<prefix:street>Another Street</prefix:street>
<prefix:housenumber>23</prefix:housenumber>
@@ -393,6 +458,16 @@ Well we would need to specify that namespace:
With that we should be able to parse as we once did.
+## Large Datasets (in_groups_of)
+
+When dealing with large sets of XML that simply cannot or should not be placed into memory the objects can be handled in groups through the `:in_groups_of` parameter.
+
+ Address.parse(LARGE_ADDRESS_XML_DATA,:in_groups_of => 5) do |group|
+ puts address.streets
+ end
+
+This trivial block will parse the large set of XML data and in groups of 5 addresses at a time display the streets.
+
## Saving to XML
Saving a class to XML is as easy as calling `#to_xml`. The end result will be the current state of your object represented as xml. Let's cover some details that are sometimes necessary and features present to make your life easier.
@@ -443,4 +518,4 @@ While parsing the XML only required you to simply specify the prefix of the name
element :city, String
element :country, Country, :tag => 'country', :namespace => 'different'
- end
+ end
Oops, something went wrong.

0 comments on commit d951d16

Please sign in to comment.