unhappymapper 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,39 +1,43 @@
1
- HappyMapper
2
- ===========
1
+ UnhappyMapper
2
+ =============
3
3
 
4
- Happymapper allows you to parse XML data and convert it quickly and easily into ruby data structures.
4
+ UnHappymapper allows you to parse XML data and convert it quickly and easily into ruby data structures.
5
5
 
6
- This project is a grandchild (a fork of a fork) of the great work done first by [jnunemaker](https://github.com/jnunemaker/happymapper) and then by [dam5s](http://github.com/dam5s/happymapper/).
7
- =======
6
+ This project is a grandchild (a fork of a fork) of the great work done first by [jnunemaker](https://github.com/jnunemaker/happymapper) and then by [dam5s](http://github.com/dam5s/happymapper/). I found both of these projects when I started to work on a project that had a serious case of XML and required a number of bug fixes and also new features. Both of the previous maintainers are too busy or not interested in the new functionality so I have released a new gem.
8
7
 
9
- Installation
10
- ------------
8
+ ###Major Differences
9
+
10
+ * [dam5s](http://github.com/dam5s/happymapper/)'s fork added [Nokogiri](http://nokogiri.org/) support
11
+ * `#to_xml` support utilizing the same HappyMapper tags
12
+ * Fixes for [namespaces when using composition of classes](https://github.com/burtlo/happymapper/commit/fd1e898c70f7289d2d2618d629b56f2f6623785c)
13
+ * 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).
11
14
 
12
- *Build the gem yourself:*
15
+
16
+ ## Installation
17
+
18
+ ### [Rubygems](https://rubygems.org/gems/unhappymapper)
19
+
20
+ $ gem install unhappymapper
21
+
22
+ ### [Source](https://github.com/burtlo/happymapper)
13
23
 
14
24
  $ git clone https://github.com/burtlo/happymapper
15
25
  $ cd happymapper
16
26
  $ git checkout master
17
- $ gem build nokogiri-happymapper.gemspec
18
- $ gem install --local happymapper-X.X.X.gem
27
+ $ gem build unhappymapper.gemspec
28
+ $ gem install --local unhappymapper-X.X.X.gem
19
29
 
20
- *For you [Bundler's](http://gembundler.com/) out there, you can add it to your Gemfile and then `bundle install`*
30
+ ### [Bundler](http://gembundler.com/)
21
31
 
22
- gem 'happymapper', :git => "git://github.com/burtlo/happymapper.git"
32
+ Add the unhappymapper gem to your project's `Gemfile`.
23
33
 
24
- Differences
25
- -----------
34
+ gem 'unhappymapper'
26
35
 
27
- * [dam5s](http://github.com/dam5s/happymapper/)'s fork added [Nokogiri](http://nokogiri.org/) support
28
- * `#to_xml` support utilizing the same HappyMapper tags
29
- * Fixes for [namespaces when using composition of classes](https://github.com/burtlo/happymapper/commit/fd1e898c70f7289d2d2618d629b56f2f6623785c)
30
- * 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).
36
+ Run the bundler command to install the gem:
31
37
 
38
+ $ bundle install
32
39
 
33
- Examples
34
- --------
35
-
36
- ## Element Mapping
40
+ # Examples
37
41
 
38
42
  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:
39
43
 
@@ -163,6 +167,40 @@ Attributes are absolutely the same as `element` or `has_many`
163
167
  Again, you can omit the tag if the attribute accessor symbol matches the name of the attribute.
164
168
 
165
169
 
170
+ ### Attributes On Empty Child Elements
171
+
172
+ <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
173
+ <id>tag:all-the-episodes.heroku.com,2005:/tv_shows</id>
174
+ <link rel="alternate" type="text/html" href="http://all-the-episodes.heroku.com"/>
175
+ <link rel="self" type="application/atom+xml" href="http://all-the-episodes.heroku.com/tv_shows.atom"/>
176
+ <title>TV Shows</title>
177
+ <updated>2011-07-10T06:52:27Z</updated>
178
+ </feed>
179
+
180
+ 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
181
+
182
+ class Feed
183
+ # ....
184
+ has_many :links, Link, :tag => 'link', :xpath => '.'
185
+ end
186
+
187
+ class Link
188
+ include HappyMapper
189
+
190
+ attribute :rel, String
191
+ attribute :type, String
192
+ attribute :href, String
193
+ end
194
+
195
+ You can drop the `Link` class and simply replace the `has_many` on `Feed` with
196
+
197
+ element :link, String, :single => false, :attributes => { :rel => String, :type => String, :href => String }
198
+
199
+ 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.
200
+
201
+ 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.
202
+
203
+
166
204
  ## Class composition (and Text Node)
167
205
 
168
206
  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`.
@@ -247,9 +285,9 @@ You may want to map the sub-elements contained buried in the 'gallery' as top le
247
285
  end
248
286
 
249
287
 
250
- ### Subclasses
288
+ ## Subclasses
251
289
 
252
- ## Inheritance (it doesn't work!)
290
+ ### Inheritance (it doesn't work!)
253
291
 
254
292
  While mapping XML to objects you may arrive at a point where you have two or more very similar structures.
255
293
 
@@ -334,7 +372,8 @@ You can however, use some module mixin power to save you those keystrokes and im
334
372
  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.
335
373
 
336
374
 
337
- ## Subclasses
375
+ ## Filtering with XPATH
376
+
338
377
  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.
339
378
 
340
379
  <media>
@@ -426,6 +465,16 @@ Well we would need to specify that namespace:
426
465
 
427
466
  With that we should be able to parse as we once did.
428
467
 
468
+ ## Large Datasets (in_groups_of)
469
+
470
+ 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.
471
+
472
+ Address.parse(LARGE_ADDRESS_XML_DATA,:in_groups_of => 5) do |group|
473
+ puts address.streets
474
+ end
475
+
476
+ This trivial block will parse the large set of XML data and in groups of 5 addresses at a time display the streets.
477
+
429
478
  ## Saving to XML
430
479
 
431
480
  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.
@@ -476,4 +525,4 @@ While parsing the XML only required you to simply specify the prefix of the name
476
525
  element :city, String
477
526
  element :country, Country, :tag => 'country', :namespace => 'different'
478
527
 
479
- end
528
+ end
data/lib/happymapper.rb CHANGED
@@ -289,42 +289,79 @@ module HappyMapper
289
289
  nodes
290
290
  end
291
291
 
292
+ # If the :limit option has been specified then we are going to slice
293
+ # our node results by that amount to allow us the ability to deal with
294
+ # a large result set of data.
292
295
 
293
- collection = nodes.collect do |n|
294
- obj = new
296
+ limit = options[:in_groups_of] || nodes.size
297
+
298
+ # If the limit of 0 has been specified then the user obviously wants
299
+ # none of the nodes that we are serving within this batch of nodes.
300
+
301
+ return [] if limit == 0
295
302
 
296
- attributes.each do |attr|
297
- obj.send("#{attr.method_name}=",
298
- attr.from_xml_node(n, namespace, namespaces))
299
- end
303
+ collection = []
304
+
305
+ nodes.each_slice(limit) do |slice|
306
+
307
+ part = slice.map do |n|
308
+ obj = new
300
309
 
301
- elements.each do |elem|
302
- obj.send("#{elem.method_name}=",
303
- elem.from_xml_node(n, namespace, namespaces))
304
- end
310
+ attributes.each do |attr|
311
+ obj.send("#{attr.method_name}=",attr.from_xml_node(n, namespace, namespaces))
312
+ end
313
+
314
+ elements.each do |elem|
315
+ obj.send("#{elem.method_name}=",elem.from_xml_node(n, namespace, namespaces))
316
+ end
305
317
 
306
- obj.send("#{@text_node.method_name}=",
307
- @text_node.from_xml_node(n, namespace, namespaces)) if @text_node
318
+ if @text_node
319
+ obj.send("#{@text_node.method_name}=",@text_node.from_xml_node(n, namespace, namespaces))
320
+ end
321
+
322
+ # If the HappyMapper class has the method #xml_value=,
323
+ # attr_writer :xml_value, or attr_accessor :xml_value then we want to
324
+ # assign the current xml that we just parsed to the xml_value
308
325
 
309
- if obj.respond_to?('xml_value=')
310
- n.namespaces.each {|name,path| n[name] = path }
311
- obj.xml_value = n.to_xml
312
- obj.class.class_eval { define_method(:to_xml) { @xml_value } }
313
- end
326
+ if obj.respond_to?('xml_value=')
327
+ n.namespaces.each {|name,path| n[name] = path }
328
+ obj.xml_value = n.to_xml
329
+ end
330
+
331
+ # If the HappyMapper class has the method #xml_content=,
332
+ # attr_write :xml_content, or attr_accessor :xml_content then we want to
333
+ # assign the child xml that we just parsed to the xml_content
314
334
 
315
- if obj.respond_to?('xml_content=')
316
- n = n.children if n.respond_to?(:children)
317
- obj.xml_content = n.to_xml
335
+ if obj.respond_to?('xml_content=')
336
+ n = n.children if n.respond_to?(:children)
337
+ obj.xml_content = n.to_xml
338
+ end
339
+
340
+ # collect the object that we have created
341
+
342
+ obj
318
343
  end
344
+
345
+ # If a block has been provided and the user has requested that the objects
346
+ # be handled in groups then we should yield the slice of the objects to them
347
+ # otherwise continue to lump them together
319
348
 
320
- obj
349
+ if block_given? and options[:in_groups_of]
350
+ yield part
351
+ else
352
+ collection += part
353
+ end
354
+
321
355
  end
322
356
 
323
357
  # per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
324
358
  nodes = nil
325
-
326
359
 
327
- if options[:single] || root
360
+ # If the :single option has been specified or we are at the root element
361
+ # then we are going to return the first item in the collection. Otherwise
362
+ # the return response is going to be an entire array of items.
363
+
364
+ if options[:single] or root
328
365
  collection.first
329
366
  else
330
367
  collection
@@ -229,7 +229,7 @@ module HappyMapper
229
229
 
230
230
  def handle_attributes_option(result, value, xpath_options)
231
231
  if options[:attributes].is_a?(Hash)
232
- result = result.first if result.respond_to?(:first)
232
+ result = result.first unless result.respond_to?(:attribute_nodes)
233
233
 
234
234
  result.attribute_nodes.each do |xml_attribute|
235
235
  if attribute_options = options[:attributes][xml_attribute.name.to_sym]
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
3
+ <id>tag:www.example.com,2005:/tv_shows</id>
4
+ <link rel="alternate" type="text/html" href="http://www.example.com"/>
5
+ <link rel="self" type="application/atom+xml" href="http://www.example.com/tv_shows.atom"/>
6
+ <title>TV Shows</title>
7
+ <updated>2011-07-08T13:47:01Z</updated>
8
+ <entry>
9
+ <id>tag:www.example.com,2005:TvShow/17</id>
10
+ <published>2011-07-08T13:47:01Z</published>
11
+ <updated>2011-07-08T13:47:01Z</updated>
12
+ <link rel="alternate" type="text/html" href="http://www.example.com/sources/channel-twenty-seven/tv_shows/name-goes-here.atom"/>
13
+ <title>Name goes here</title>
14
+ <content type="html">Name goes here (0 episodes)</content>
15
+ <author>
16
+ <name>Source URL goes here</name>
17
+ </author>
18
+ </entry>
19
+ </feed>
@@ -69,6 +69,24 @@ module Analytics
69
69
  end
70
70
  end
71
71
 
72
+ module Atom
73
+ class Feed
74
+ include HappyMapper
75
+ tag 'feed'
76
+
77
+ attribute :xmlns, String, :single => true
78
+ element :id, String, :single => true
79
+ element :title, String, :single => true
80
+ element :updated, DateTime, :single => true
81
+ element :link, String, :single => false, :attributes => {
82
+ :rel => String,
83
+ :type => String,
84
+ :href => String
85
+ }
86
+ # has_many :entries, Entry # nothing interesting in the entries
87
+ end
88
+ end
89
+
72
90
  class Address
73
91
  include HappyMapper
74
92
 
@@ -701,6 +719,12 @@ describe HappyMapper do
701
719
  first.current_condition.icon.should == 'http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif'
702
720
  end
703
721
 
722
+ it "parses xml with attributes of elements that aren't :single => true" do
723
+ feed = Atom::Feed.parse(fixture_file('atom.xml'))
724
+ feed.link.first.href.should == 'http://www.example.com'
725
+ feed.link.last.href.should == 'http://www.example.com/tv_shows.atom'
726
+ end
727
+
704
728
  it "should parse xml with nested elements" do
705
729
  radars = Radar.parse(fixture_file('radar.xml'))
706
730
  first = radars[0]
@@ -893,10 +917,6 @@ describe HappyMapper do
893
917
  @article.photos.first.title.should_not be_nil
894
918
  end
895
919
 
896
- it "should contain the attributes in the as_xml" do
897
- @article.to_xml.should_not be_nil
898
- end
899
-
900
920
  it "should parse the publish options for Article" do
901
921
  @article.publish_options.should_not be_nil
902
922
  end
@@ -908,19 +928,7 @@ describe HappyMapper do
908
928
  it "should only find only items at the parent level" do
909
929
  @article.photos.length.should == 1
910
930
  end
911
-
912
- it "should have as_xml" do
913
- @article.photos.first.to_xml.should_not be_nil
914
- end
915
-
916
- it "should be parseable because it has the namespaces" do
917
- lambda { Nokogiri::XML(@article.photos.first.to_xml).to_s }.should_not raise_error
918
- end
919
931
 
920
- it "should be parseable because it has the namespaces" do
921
- lambda { Nokogiri::XML(@article.photos.first.to_xml).to_s }.should_not raise_error
922
- end
923
-
924
932
  before(:all) do
925
933
  @article = Article.parse(fixture_file('subclass_namespace.xml'))
926
934
  end
@@ -937,5 +945,24 @@ describe HappyMapper do
937
945
  @article.photos.first.title.should_not be_nil
938
946
  end
939
947
  end
948
+
949
+
950
+ describe "with limit option" do
951
+ it "should return results with limited size: 6" do
952
+ sizes = []
953
+ posts = Post.parse(fixture_file('posts.xml'), :in_groups_of => 6) do |a|
954
+ sizes << a.size
955
+ end
956
+ sizes.should == [6, 6, 6, 2]
957
+ end
958
+
959
+ it "should return results with limited size: 10" do
960
+ sizes = []
961
+ posts = Post.parse(fixture_file('posts.xml'), :in_groups_of => 10) do |a|
962
+ sizes << a.size
963
+ end
964
+ sizes.should == [10, 10]
965
+ end
966
+ end
940
967
 
941
968
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: unhappymapper
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
4
5
  prerelease:
5
- version: 0.4.0
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Damien Le Berrigaud
9
9
  - John Nunemaker
10
10
  - David Bolton
@@ -14,42 +14,39 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
-
18
- date: 2011-08-22 00:00:00 -07:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
17
+ date: 2011-08-22 00:00:00.000000000Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
22
20
  name: nokogiri
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
21
+ requirement: &70134376050820 !ruby/object:Gem::Requirement
25
22
  none: false
26
- requirements:
23
+ requirements:
27
24
  - - ~>
28
- - !ruby/object:Gem::Version
25
+ - !ruby/object:Gem::Version
29
26
  version: 1.4.2
30
27
  type: :runtime
31
- version_requirements: *id001
32
- - !ruby/object:Gem::Dependency
33
- name: rspec
34
28
  prerelease: false
35
- requirement: &id002 !ruby/object:Gem::Requirement
29
+ version_requirements: *70134376050820
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: &70134376050340 !ruby/object:Gem::Requirement
36
33
  none: false
37
- requirements:
34
+ requirements:
38
35
  - - ~>
39
- - !ruby/object:Gem::Version
36
+ - !ruby/object:Gem::Version
40
37
  version: 1.3.0
41
38
  type: :development
42
- version_requirements: *id002
43
- description: Object to XML Mapping Library, using Nokogiri (fork from John Nunemaker's Happymapper)
39
+ prerelease: false
40
+ version_requirements: *70134376050340
41
+ description: Object to XML Mapping Library, using Nokogiri (fork from John Nunemaker's
42
+ Happymapper)
44
43
  email: franklin.webber@gmail.com
45
44
  executables: []
46
-
47
45
  extensions: []
48
-
49
- extra_rdoc_files:
46
+ extra_rdoc_files:
50
47
  - README.md
51
48
  - TODO
52
- files:
49
+ files:
53
50
  - lib/happymapper.rb
54
51
  - lib/happymapper/attribute.rb
55
52
  - lib/happymapper/element.rb
@@ -61,6 +58,7 @@ files:
61
58
  - spec/fixtures/ambigous_items.xml
62
59
  - spec/fixtures/analytics.xml
63
60
  - spec/fixtures/analytics_profile.xml
61
+ - spec/fixtures/atom.xml
64
62
  - spec/fixtures/commit.xml
65
63
  - spec/fixtures/current_weather.xml
66
64
  - spec/fixtures/dictionary.xml
@@ -88,39 +86,36 @@ files:
88
86
  - spec/ignay_spec.rb
89
87
  - spec/spec_helper.rb
90
88
  - spec/xpath_spec.rb
91
- has_rdoc: true
92
89
  homepage: http://github.com/burtlo/happymapper
93
90
  licenses: []
94
-
95
91
  post_install_message:
96
92
  rdoc_options: []
97
-
98
- require_paths:
93
+ require_paths:
99
94
  - lib
100
- required_ruby_version: !ruby/object:Gem::Requirement
95
+ required_ruby_version: !ruby/object:Gem::Requirement
101
96
  none: false
102
- requirements:
103
- - - ">="
104
- - !ruby/object:Gem::Version
105
- version: "0"
106
- required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
102
  none: false
108
- requirements:
109
- - - ">="
110
- - !ruby/object:Gem::Version
111
- version: "0"
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
112
107
  requirements: []
113
-
114
108
  rubyforge_project:
115
- rubygems_version: 1.6.2
109
+ rubygems_version: 1.8.6
116
110
  signing_key:
117
111
  specification_version: 3
118
112
  summary: Provides a simple way to map XML to Ruby Objects and back again.
119
- test_files:
113
+ test_files:
120
114
  - spec/fixtures/address.xml
121
115
  - spec/fixtures/ambigous_items.xml
122
116
  - spec/fixtures/analytics.xml
123
117
  - spec/fixtures/analytics_profile.xml
118
+ - spec/fixtures/atom.xml
124
119
  - spec/fixtures/commit.xml
125
120
  - spec/fixtures/current_weather.xml
126
121
  - spec/fixtures/dictionary.xml