nokogiri-happymapper 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/History +59 -0
  2. data/License +20 -0
  3. data/Manifest +45 -0
  4. data/README +61 -0
  5. data/Rakefile +28 -0
  6. data/TODO +0 -0
  7. data/examples/amazon.rb +34 -0
  8. data/examples/current_weather.rb +21 -0
  9. data/examples/dashed_elements.rb +20 -0
  10. data/examples/family_tree.rb +48 -0
  11. data/examples/post.rb +19 -0
  12. data/examples/twitter.rb +37 -0
  13. data/lib/happymapper.rb +157 -0
  14. data/lib/happymapper/attribute.rb +3 -0
  15. data/lib/happymapper/element.rb +3 -0
  16. data/lib/happymapper/item.rb +198 -0
  17. data/lib/happymapper/text_node.rb +3 -0
  18. data/lib/happymapper/version.rb +3 -0
  19. data/nokogiri-happymapper.gemspec +34 -0
  20. data/spec/fixtures/address.xml +8 -0
  21. data/spec/fixtures/analytics.xml +61 -0
  22. data/spec/fixtures/commit.xml +52 -0
  23. data/spec/fixtures/current_weather.xml +89 -0
  24. data/spec/fixtures/dictionary.xml +20 -0
  25. data/spec/fixtures/family_tree.xml +21 -0
  26. data/spec/fixtures/lastfm.xml +355 -0
  27. data/spec/fixtures/multiple_namespaces.xml +170 -0
  28. data/spec/fixtures/multiple_primitives.xml +5 -0
  29. data/spec/fixtures/pita.xml +133 -0
  30. data/spec/fixtures/posts.xml +23 -0
  31. data/spec/fixtures/product_default_namespace.xml +17 -0
  32. data/spec/fixtures/product_no_namespace.xml +10 -0
  33. data/spec/fixtures/product_single_namespace.xml +10 -0
  34. data/spec/fixtures/quarters.xml +19 -0
  35. data/spec/fixtures/radar.xml +21 -0
  36. data/spec/fixtures/statuses.xml +422 -0
  37. data/spec/happymapper_attribute_spec.rb +21 -0
  38. data/spec/happymapper_element_spec.rb +21 -0
  39. data/spec/happymapper_item_spec.rb +115 -0
  40. data/spec/happymapper_spec.rb +735 -0
  41. data/spec/happymapper_text_node_spec.rb +21 -0
  42. data/spec/spec.opts +1 -0
  43. data/spec/spec_helper.rb +13 -0
  44. data/website/css/common.css +47 -0
  45. data/website/index.html +98 -0
  46. metadata +120 -0
data/History ADDED
@@ -0,0 +1,59 @@
1
+ == 0.2.5
2
+ * 1 minor tweak
3
+ * Classes can now be strings instead of constants so you don't have to worry about class definition order (this was all for technicalpickles, enjoy!)
4
+
5
+ == 0.2.4
6
+ * 1 minor tweak
7
+ * Added a patch that allows even crazy namespaces to work
8
+
9
+ == 0.2.3
10
+ * 1 minor tweak
11
+ * bumped the version of libxml-ruby to 1.1.3
12
+
13
+ == 0.2.2
14
+ * 2 minor tweaks
15
+ * removed GC.start (libxml recommended this) as setting nodes to nil should be enough, specs run 3-4x faster (Brandon Keepers)
16
+ * renamed get_tag_name to tag_name (Brandon Keepers)
17
+ * removed libxml helpers as they are no longer needed
18
+
19
+ == 0.2.1
20
+ * 1 minor fix, 3 major enhancements
21
+ * fixed warnings about using XML::Parser (mojodna)
22
+ * Improved namespace support, now handles multiple namespaces and allows namespaces to be set item wide or on a per element basis (mojodna)
23
+ * Auto detect root nodes (mojodna)
24
+ * Type coercion (mojodna)
25
+
26
+ == 0.2.0
27
+ * 1 major enhancement, 2 minor ehancements
28
+ * Automatic handling of namespaces (part by Robert Lowrey and rest by John Nunemaker)
29
+ * Added :root option to tag method. This allows setting an object as the root element, which sets xpath to use / and sets single to true
30
+ * Now defaulting tag names for classes in modules to last constant downcased
31
+
32
+ == 0.1.7 2009-01-29
33
+ * 1 minor enhancement
34
+ * Support dashes in elements (Josh Nichols)
35
+
36
+ == 0.1.6 2009-01-17
37
+ * 1 minor enhancement:
38
+ * added support for nested collection elements (Justin Marney)
39
+
40
+ == 0.1.5 2009-01-05
41
+ * 1 major enhancement:
42
+ * Updated to latest version of libxml-ruby (lightningdb)
43
+
44
+ == 0.1.4 2009-01-05
45
+ * 1 major enhancement:
46
+ * Fixed parsing when the object is the root node. (Garret Alfert)
47
+
48
+ == 0.1.3 2008-12-31
49
+ * 1 major enhancement:
50
+ * Added parsing of attributes of elements that are also mapped, see current_weather.rb for example (jeremyf)
51
+
52
+ == 0.1.2 2008-12-12
53
+ * 1 major enhancement:
54
+ * Fixed that :deep only worked for first item (dvrensk)
55
+
56
+ == 0.1.0 2008-11-16
57
+
58
+ * 1 major enhancement:
59
+ * Initial release
data/License ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 John Nunemaker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,45 @@
1
+ History
2
+ License
3
+ Manifest
4
+ README
5
+ Rakefile
6
+ TODO
7
+ examples/amazon.rb
8
+ examples/current_weather.rb
9
+ examples/dashed_elements.rb
10
+ examples/family_tree.rb
11
+ examples/post.rb
12
+ examples/twitter.rb
13
+ lib/happymapper.rb
14
+ lib/happymapper/attribute.rb
15
+ lib/happymapper/element.rb
16
+ lib/happymapper/item.rb
17
+ lib/happymapper/text_node.rb
18
+ lib/happymapper/version.rb
19
+ nokogiri-happymapper.gemspec
20
+ spec/fixtures/address.xml
21
+ spec/fixtures/analytics.xml
22
+ spec/fixtures/commit.xml
23
+ spec/fixtures/current_weather.xml
24
+ spec/fixtures/dictionary.xml
25
+ spec/fixtures/family_tree.xml
26
+ spec/fixtures/lastfm.xml
27
+ spec/fixtures/multiple_namespaces.xml
28
+ spec/fixtures/multiple_primitives.xml
29
+ spec/fixtures/pita.xml
30
+ spec/fixtures/posts.xml
31
+ spec/fixtures/product_default_namespace.xml
32
+ spec/fixtures/product_no_namespace.xml
33
+ spec/fixtures/product_single_namespace.xml
34
+ spec/fixtures/quarters.xml
35
+ spec/fixtures/radar.xml
36
+ spec/fixtures/statuses.xml
37
+ spec/happymapper_attribute_spec.rb
38
+ spec/happymapper_element_spec.rb
39
+ spec/happymapper_item_spec.rb
40
+ spec/happymapper_spec.rb
41
+ spec/happymapper_text_node_spec.rb
42
+ spec/spec.opts
43
+ spec/spec_helper.rb
44
+ website/css/common.css
45
+ website/index.html
data/README ADDED
@@ -0,0 +1,61 @@
1
+ = happymapper
2
+
3
+ == DESCRIPTION:
4
+
5
+ Object to xml mapping library. I have included examples to help get you going. The specs
6
+ should also point you in the right direction.
7
+
8
+ This is a custom version of HappyMapper, available there:
9
+ http://github.com/dam5s/happymapper/
10
+
11
+ == FEATURES:
12
+
13
+ * Easy to define xml attributes and elements for an object
14
+ * Fast because it uses nokogiri under the hood
15
+ * Automatic conversion of xml to defined objects
16
+ * Reusable classes via a node finding mechanism that searches by 1. specified tag,
17
+ 2. name of element, 3. class name. (gemspec was upgraded to 0.3.0 for this change)
18
+
19
+ == EXAMPLES:
20
+
21
+ Here is a simple example that maps Twitter statuses and users.
22
+
23
+ class User
24
+ include HappyMapper
25
+
26
+ element :id, Integer
27
+ element :name, String
28
+ element :screen_name, String
29
+ element :location, String
30
+ element :description, String
31
+ element :profile_image_url, String
32
+ element :url, String
33
+ element :protected, Boolean
34
+ element :followers_count, Integer
35
+ end
36
+
37
+ class Status
38
+ include HappyMapper
39
+
40
+ element :id, Integer
41
+ element :text, String
42
+ element :created_at, Time
43
+ element :source, String
44
+ element :truncated, Boolean
45
+ element :in_reply_to_status_id, Integer
46
+ element :in_reply_to_user_id, Integer
47
+ element :favorited, Boolean
48
+ has_one :user, User
49
+ end
50
+
51
+ See examples directory in the gem for more examples.
52
+
53
+ http://github.com/dam5s/happymapper/tree/master/examples/
54
+
55
+ == INSTALL:
56
+
57
+ * sudo gem install nokogiri-happymapper -s http://gemcutter.org
58
+
59
+ == TICKETS:
60
+
61
+ http://github.com/dam5s/happymapper/issues/
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+ require 'spec/rake/spectask'
5
+ require "lib/happymapper/version"
6
+
7
+ Echoe.new('nokogiri-happymapper', HappyMapper::Version) do |p|
8
+ p.description = "object to xml mapping library, using nokogiri (fork from John Nunemaker's Happymapper)"
9
+ p.install_message = "May you have many happy mappings!"
10
+ p.url = "http://github.com/dam5s/happymapper"
11
+ p.author = "Damien Le Berrigaud, John Nunemaker, David Bolton, Roland Swingler"
12
+ p.email = "damien@meliondesign.com"
13
+ p.extra_deps = ['nokogiri >=1.4.0']
14
+ p.need_tar_gz = false
15
+ end
16
+
17
+ desc 'Preps the gem for a new release'
18
+ task :prepare do
19
+ %w[manifest build_gemspec].each do |task|
20
+ Rake::Task[task].invoke
21
+ end
22
+ end
23
+
24
+ Rake::Task[:default].prerequisites.clear
25
+ task :default => :spec
26
+ Spec::Rake::SpecTask.new do |t|
27
+ t.spec_files = FileList["spec/**/*_spec.rb"]
28
+ end
data/TODO ADDED
File without changes
@@ -0,0 +1,34 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/pita.xml')
5
+
6
+ # The document `pita.xml` contains both a default namespace and the 'georss'
7
+ # namespace (for the 'point' element).
8
+ module PITA
9
+ class Item
10
+ include HappyMapper
11
+
12
+ tag 'Item' # if you put class in module you need tag
13
+ element :asin, String, :tag => 'ASIN'
14
+ element :detail_page_url, String, :tag => 'DetailPageURL'
15
+ element :manufacturer, String, :tag => 'Manufacturer', :deep => true
16
+ # this is the only element that exists in a different namespace, so it
17
+ # must be explicitly specified
18
+ element :point, String, :tag => 'point', :namespace => 'georss'
19
+ end
20
+
21
+ class Items
22
+ include HappyMapper
23
+
24
+ tag 'Items' # if you put class in module you need tag
25
+ element :total_results, Integer, :tag => 'TotalResults'
26
+ element :total_pages, Integer, :tag => 'TotalPages'
27
+ has_many :items, Item
28
+ end
29
+ end
30
+
31
+ item = PITA::Items.parse(file_contents, :single => true)
32
+ item.items.each do |i|
33
+ puts i.asin, i.detail_page_url, i.manufacturer, ''
34
+ end
@@ -0,0 +1,21 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/current_weather.xml')
5
+
6
+ class CurrentWeather
7
+ include HappyMapper
8
+
9
+ tag 'ob'
10
+ namespace 'aws'
11
+ element :temperature, Integer, :tag => 'temp'
12
+ element :feels_like, Integer, :tag => 'feels-like'
13
+ element :current_condition, String, :tag => 'current-condition', :attributes => {:icon => String}
14
+ end
15
+
16
+ CurrentWeather.parse(file_contents).each do |current_weather|
17
+ puts "temperature: #{current_weather.temperature}"
18
+ puts "feels_like: #{current_weather.feels_like}"
19
+ puts "current_condition: #{current_weather.current_condition}"
20
+ puts "current_condition.icon: #{current_weather.current_condition.icon}"
21
+ end
@@ -0,0 +1,20 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/commit.xml')
5
+
6
+ module GitHub
7
+ class Commit
8
+ include HappyMapper
9
+
10
+ tag "commit"
11
+ element :url, String
12
+ element :tree, String
13
+ element :message, String
14
+ element :id, String
15
+ element :'committed-date', Date
16
+ end
17
+ end
18
+
19
+ commit = GitHub::Commit.parse(file_contents)
20
+ puts commit.committed_date, commit.url, commit.id
@@ -0,0 +1,48 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/family_tree.xml')
5
+
6
+ module FamilySearch
7
+ class AlternateIds
8
+ include HappyMapper
9
+
10
+ tag 'alternateIds'
11
+ has_many :ids, String, :tag => 'id'
12
+ end
13
+
14
+ class Information
15
+ include HappyMapper
16
+
17
+ has_one :alternateIds, AlternateIds
18
+ end
19
+
20
+ class Person
21
+ include HappyMapper
22
+
23
+ attribute :version, String
24
+ attribute :modified, Time
25
+ attribute :id, String
26
+ has_one :information, Information
27
+ end
28
+
29
+ class Persons
30
+ include HappyMapper
31
+ has_many :person, Person
32
+ end
33
+
34
+ class FamilyTree
35
+ include HappyMapper
36
+
37
+ tag 'familytree'
38
+ attribute :version, String
39
+ attribute :status_message, String, :tag => 'statusMessage'
40
+ attribute :status_code, String, :tag => 'statusCode'
41
+ has_one :persons, Persons
42
+ end
43
+ end
44
+
45
+ familytree = FamilySearch::FamilyTree.parse(file_contents)
46
+ familytree.persons.person.each do |p|
47
+ puts p.id, p.information.alternateIds.ids, ''
48
+ end
data/examples/post.rb ADDED
@@ -0,0 +1,19 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/posts.xml')
5
+
6
+ class Post
7
+ include HappyMapper
8
+
9
+ attribute :href, String
10
+ attribute :hash, String
11
+ attribute :description, String
12
+ attribute :tag, String
13
+ attribute :time, DateTime
14
+ attribute :others, Integer
15
+ attribute :extended, String
16
+ end
17
+
18
+ posts = Post.parse(file_contents)
19
+ posts.each { |post| puts post.description, post.href, post.extended, '' }
@@ -0,0 +1,37 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/statuses.xml')
5
+
6
+ class User
7
+ include HappyMapper
8
+
9
+ element :id, Integer
10
+ element :name, String
11
+ element :screen_name, String
12
+ element :location, String
13
+ element :description, String
14
+ element :profile_image_url, String
15
+ element :url, String
16
+ element :protected, Boolean
17
+ element :followers_count, Integer
18
+ end
19
+
20
+ class Status
21
+ include HappyMapper
22
+
23
+ element :id, Integer
24
+ element :text, String
25
+ element :created_at, Time
26
+ element :source, String
27
+ element :truncated, Boolean
28
+ element :in_reply_to_status_id, Integer
29
+ element :in_reply_to_user_id, Integer
30
+ element :favorited, Boolean
31
+ has_one :user, User
32
+ end
33
+
34
+ statuses = Status.parse(file_contents)
35
+ statuses.each do |status|
36
+ puts status.user.name, status.user.screen_name, status.text, status.source, ''
37
+ end
@@ -0,0 +1,157 @@
1
+ dir = File.dirname(__FILE__)
2
+
3
+ require 'date'
4
+ require 'time'
5
+ require 'rubygems'
6
+ require 'nokogiri'
7
+
8
+ class Boolean; end
9
+ class XmlContent; end
10
+
11
+ module HappyMapper
12
+
13
+ DEFAULT_NS = "happymapper"
14
+
15
+ def self.included(base)
16
+ base.instance_variable_set("@attributes", {})
17
+ base.instance_variable_set("@elements", {})
18
+ base.extend ClassMethods
19
+ end
20
+
21
+ module ClassMethods
22
+ def attribute(name, type, options={})
23
+ attribute = Attribute.new(name, type, options)
24
+ @attributes[to_s] ||= []
25
+ @attributes[to_s] << attribute
26
+ attr_accessor attribute.method_name.intern
27
+ end
28
+
29
+ def attributes
30
+ @attributes[to_s] || []
31
+ end
32
+
33
+ def element(name, type, options={})
34
+ element = Element.new(name, type, options)
35
+ @elements[to_s] ||= []
36
+ @elements[to_s] << element
37
+ attr_accessor element.method_name.intern
38
+ end
39
+
40
+ def elements
41
+ @elements[to_s] || []
42
+ end
43
+
44
+ def text_node(name, type, options={})
45
+ @text_node = TextNode.new(name, type, options)
46
+ attr_accessor @text_node.method_name.intern
47
+ end
48
+
49
+ def has_xml_content
50
+ attr_accessor :xml_content
51
+ end
52
+
53
+ def has_one(name, type, options={})
54
+ element name, type, {:single => true}.merge(options)
55
+ end
56
+
57
+ def has_many(name, type, options={})
58
+ element name, type, {:single => false}.merge(options)
59
+ end
60
+
61
+ # Specify a namespace if a node and all its children are all namespaced
62
+ # elements. This is simpler than passing the :namespace option to each
63
+ # defined element.
64
+ def namespace(namespace = nil)
65
+ @namespace = namespace if namespace
66
+ @namespace
67
+ end
68
+
69
+ def tag(new_tag_name)
70
+ @tag_name = new_tag_name.to_s unless new_tag_name.nil? || new_tag_name.to_s.empty?
71
+ end
72
+
73
+ def tag_name
74
+ @tag_name ||= to_s.split('::')[-1].downcase
75
+ end
76
+
77
+ def parse(xml, options = {})
78
+ # locally scoped copy of namespace for this parse run
79
+ namespace = @namespace
80
+
81
+ if xml.is_a?(Nokogiri::XML::Node)
82
+ node = xml
83
+ else
84
+ if xml.is_a?(Nokogiri::XML::Document)
85
+ node = xml.root
86
+ else
87
+ xml = Nokogiri::XML(xml)
88
+ node = xml.root
89
+ end
90
+
91
+ root = node.name == tag_name
92
+ end
93
+
94
+ # This is the entry point into the parsing pipeline, so the default
95
+ # namespace prefix registered here will propagate down
96
+ namespaces = options[:namespaces] || xml.namespaces
97
+ if namespaces.has_key?("xmlns")
98
+ namespace ||= DEFAULT_NS
99
+ namespaces[namespace] = namespaces.delete("xmlns")
100
+ elsif namespaces.has_key?(DEFAULT_NS)
101
+ namespace ||= DEFAULT_NS
102
+ end
103
+
104
+ xpath = root ? '/' : './/'
105
+ xpath += "#{namespace}:" if namespace
106
+ #puts "parse: #{xpath}"
107
+
108
+ nodes = []
109
+ # when finding nodes, do it in this order:
110
+ # 1. specified tag
111
+ # 2. name of element
112
+ # 3. tag_name (derived from class name by default)
113
+ [options[:tag], options[:name], tag_name].compact.each do |xpath_ext|
114
+ nodes = node.xpath(xpath + xpath_ext.to_s, namespaces)
115
+ break if nodes && nodes.size > 0
116
+ end
117
+
118
+ collection = nodes.collect do |n|
119
+ obj = new
120
+
121
+ attributes.each do |attr|
122
+ obj.send("#{attr.method_name}=",
123
+ attr.from_xml_node(n, namespace, namespaces))
124
+ end
125
+
126
+ elements.each do |elem|
127
+ obj.send("#{elem.method_name}=",
128
+ elem.from_xml_node(n, namespace, namespaces))
129
+ end
130
+
131
+ obj.send("#{@text_node.method_name}=",
132
+ @text_node.from_xml_node(n, namespace, namespaces)) if @text_node
133
+
134
+ if obj.respond_to?('xml_content=')
135
+ n = n.children if n.respond_to?(:children)
136
+ obj.xml_content = n.to_xml
137
+ end
138
+
139
+ obj
140
+ end
141
+
142
+ # per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
143
+ nodes = nil
144
+
145
+ if options[:single] || root
146
+ collection.first
147
+ else
148
+ collection
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ require File.join(dir, 'happymapper/item')
155
+ require File.join(dir, 'happymapper/attribute')
156
+ require File.join(dir, 'happymapper/element')
157
+ require File.join(dir, 'happymapper/text_node')