nokogiri-happymapper 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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')