lzell-mapricot 0.0.1
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.
- data/History.txt +3 -0
- data/License.txt +19 -0
- data/README.rdoc +118 -0
- data/examples/lastfm_api.rb +41 -0
- data/examples/lastfm_api_no_request.rb +126 -0
- data/examples/natural_inputs.rb +22 -0
- data/examples/readme_examples.rb +82 -0
- data/lib/mapricot.rb +174 -0
- data/mapricot.gemspec +28 -0
- data/test/mapricot_spec.rb +118 -0
- data/test/mapricot_tests.rb +109 -0
- metadata +76 -0
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Louis Zell <lzell11@gmail.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
== Mapricot
|
2
|
+
|
3
|
+
Makes working with XML stupid easy. XML to object mapper with an interface similar to ActiveRecord associations.
|
4
|
+
|
5
|
+
|
6
|
+
== Install
|
7
|
+
|
8
|
+
Do some stuff...
|
9
|
+
|
10
|
+
== Usage
|
11
|
+
|
12
|
+
Your classes should inherit from Mapricot::Base, which provides the class methods
|
13
|
+
* has_one(name, type, opts = {})
|
14
|
+
* has_many(name, type, opts = {})
|
15
|
+
* has_attribute(name)
|
16
|
+
|
17
|
+
When you instantiate a Mapricot::Base object, you can pass it a string of xml:
|
18
|
+
class MapMe < Mapricot::Base
|
19
|
+
end
|
20
|
+
|
21
|
+
MapMe.new :xml => "<some_stuff>...</some_stuff>"
|
22
|
+
|
23
|
+
Or you can pass it a url:
|
24
|
+
class MapMeToo < Mapricot::Base
|
25
|
+
end
|
26
|
+
|
27
|
+
MapMeToo.new :url => "http://some_url"
|
28
|
+
|
29
|
+
== Examples
|
30
|
+
|
31
|
+
==== Super Simple
|
32
|
+
|
33
|
+
simple_xml = %(
|
34
|
+
<user>
|
35
|
+
<id>1</name>
|
36
|
+
<name>Bob</name>
|
37
|
+
<pet>cat</pet>
|
38
|
+
<pet>dog</pet>
|
39
|
+
</user>
|
40
|
+
)
|
41
|
+
|
42
|
+
class User < Mapricot::Base
|
43
|
+
has_one :id, :integer
|
44
|
+
has_one :name, :string
|
45
|
+
has_many :pets, :string
|
46
|
+
end
|
47
|
+
|
48
|
+
user = User.new(:xml => simple_xml)
|
49
|
+
puts user.id # => 1
|
50
|
+
puts user.id.class # => Fixnum
|
51
|
+
puts user.name # => Bob
|
52
|
+
puts user.pets.class # => Array
|
53
|
+
puts user.pets.join(", ") # => cat, dog
|
54
|
+
|
55
|
+
|
56
|
+
==== A little more realistic
|
57
|
+
|
58
|
+
xml = %(
|
59
|
+
<user>
|
60
|
+
<id>2</name>
|
61
|
+
<name>Sally</name>
|
62
|
+
<location code="ny123">
|
63
|
+
<city>New York</city>
|
64
|
+
<state>NY</state>
|
65
|
+
</location>
|
66
|
+
<hobby>
|
67
|
+
<description>Skiing</description>
|
68
|
+
<number_of_years>2</number_of_years>
|
69
|
+
</hobby>
|
70
|
+
<hobby>
|
71
|
+
<description>Hiking</description>
|
72
|
+
<number_of_years>3</number_of_years>
|
73
|
+
</hobby>
|
74
|
+
</user>
|
75
|
+
)
|
76
|
+
|
77
|
+
|
78
|
+
class User < Mapricot::Base
|
79
|
+
has_one :id, :integer
|
80
|
+
has_one :name # Tag type will default to :string
|
81
|
+
has_many :pets
|
82
|
+
has_one :location, :xml
|
83
|
+
has_many :hobbies, :xml
|
84
|
+
end
|
85
|
+
|
86
|
+
class Location < Mapricot::Base
|
87
|
+
has_attribute :code
|
88
|
+
has_one :city
|
89
|
+
has_one :state
|
90
|
+
end
|
91
|
+
|
92
|
+
class Hobby < Mapricot::Base
|
93
|
+
has_one :description, :string
|
94
|
+
has_one :number_of_years, :integer
|
95
|
+
end
|
96
|
+
|
97
|
+
user = User.new(:xml => xml)
|
98
|
+
puts user.name # => Sally
|
99
|
+
puts user.pets.inspect # => []
|
100
|
+
puts user.location.class # => Location
|
101
|
+
puts user.location.city # => New York
|
102
|
+
puts user.location.state # => NY
|
103
|
+
puts user.location.code # => ny123
|
104
|
+
puts user.hobbies.class # => Array
|
105
|
+
puts user.hobbies.first.class # => Hobby
|
106
|
+
|
107
|
+
user.hobbies.each do |hobby|
|
108
|
+
puts "#{hobby.description} for #{hobby.number_of_years} years"
|
109
|
+
end
|
110
|
+
# => Skiing for 2 years
|
111
|
+
# => Hiking for 3 years
|
112
|
+
|
113
|
+
== Credits
|
114
|
+
|
115
|
+
All to why the luck stiff for Hpricot.
|
116
|
+
|
117
|
+
|
118
|
+
Copyright (c) 2009 Lou Zell, released under the MIT license
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Get events in nyc with:
|
2
|
+
# http://ws.audioscrobbler.com/2.0/?method=geo.getevents&city=new+york&api_key=YOUR_API_KEY_HERE
|
3
|
+
|
4
|
+
# The point of this is to demonstrate mapping xml from a real api response.
|
5
|
+
# The only difference is you instantiate with:
|
6
|
+
# Response.new(:url => "http://someurl")
|
7
|
+
# instead of:
|
8
|
+
# Response.new(:xml => "some string of xml")
|
9
|
+
|
10
|
+
# If you do not have an API key, see examples/lastfm_api_no_request.rb
|
11
|
+
require '../lib/mapricot'
|
12
|
+
|
13
|
+
class Response < Mapricot::Base
|
14
|
+
has_many :events, :xml
|
15
|
+
end
|
16
|
+
|
17
|
+
class Event < Mapricot::Base
|
18
|
+
has_one :title
|
19
|
+
has_one :venue, :xml
|
20
|
+
end
|
21
|
+
|
22
|
+
class Venue < Mapricot::Base
|
23
|
+
has_one :name
|
24
|
+
has_one :location, :xml
|
25
|
+
end
|
26
|
+
|
27
|
+
class Location < Mapricot::Base
|
28
|
+
has_one :city
|
29
|
+
has_one :country
|
30
|
+
has_one :street
|
31
|
+
has_one :postalcode, :integer
|
32
|
+
end
|
33
|
+
|
34
|
+
response = Response.new(:url => "http://ws.audioscrobbler.com/2.0/?method=geo.getevents&city=new+york&api_key=#{YOUR_API_KEY_HERE}")
|
35
|
+
|
36
|
+
response.events.each do |event|
|
37
|
+
puts "-------------"
|
38
|
+
puts event.title
|
39
|
+
puts event.venue.name
|
40
|
+
puts event.venue.location.city
|
41
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require '../lib/mapricot'
|
2
|
+
|
3
|
+
|
4
|
+
last_fm_example = %(
|
5
|
+
<?xml version="1.0" encoding="utf-8"?>
|
6
|
+
<lfm status="ok">
|
7
|
+
<events location="New York, United States" page="1" totalpages="92" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" total="917">
|
8
|
+
<event>
|
9
|
+
<id>895664</id>
|
10
|
+
<title>Lydia</title>
|
11
|
+
<artists>
|
12
|
+
<artist>Lydia</artist>
|
13
|
+
<artist>Black Gold</artist>
|
14
|
+
|
15
|
+
<headliner>Lydia</headliner>
|
16
|
+
</artists>
|
17
|
+
<venue>
|
18
|
+
<name>Mercury Lounge</name>
|
19
|
+
<location>
|
20
|
+
<city>New York</city>
|
21
|
+
<country>United States</country>
|
22
|
+
|
23
|
+
<street>217 East Houston Street</street>
|
24
|
+
<postalcode>10002</postalcode>
|
25
|
+
<geo:point>
|
26
|
+
<geo:lat>40.722024</geo:lat>
|
27
|
+
<geo:long>-73.98682</geo:long>
|
28
|
+
</geo:point>
|
29
|
+
<timezone>EST</timezone>
|
30
|
+
|
31
|
+
</location>
|
32
|
+
<url>http://www.last.fm/venue/8899833</url>
|
33
|
+
</venue>
|
34
|
+
<startDate>Thu, 05 Mar 2009</startDate>
|
35
|
+
<startTime>18:30</startTime>
|
36
|
+
<description><![CDATA[<div class="bbcode">On Sale Fri 1/16 at Noon<br />
|
37
|
+
21+<br />
|
38
|
+
$10<br />
|
39
|
+
Doors 6:30pm/ Show 7:30pm</div>]]></description>
|
40
|
+
<image size="small">http://userserve-ak.last.fm/serve/34/9158303.jpg</image>
|
41
|
+
|
42
|
+
<image size="medium">http://userserve-ak.last.fm/serve/64/9158303.jpg</image>
|
43
|
+
<image size="large">http://userserve-ak.last.fm/serve/126/9158303.jpg</image>
|
44
|
+
<attendance>7</attendance>
|
45
|
+
<reviews>0</reviews>
|
46
|
+
<tag>lastfm:event=895664</tag>
|
47
|
+
<url>http://www.last.fm/event/895664</url>
|
48
|
+
|
49
|
+
</event>
|
50
|
+
<event>
|
51
|
+
<id>924763</id>
|
52
|
+
<title>Stars Like Fleas</title>
|
53
|
+
<artists>
|
54
|
+
<artist>Stars Like Fleas</artist>
|
55
|
+
|
56
|
+
<artist>Frances</artist>
|
57
|
+
<artist>twi the humble feather</artist>
|
58
|
+
<artist>La Strada</artist>
|
59
|
+
<headliner>Stars Like Fleas</headliner>
|
60
|
+
</artists>
|
61
|
+
<venue>
|
62
|
+
<name>Music Hall of Williamsburg</name>
|
63
|
+
|
64
|
+
<location>
|
65
|
+
<city>Brooklyn, NY</city>
|
66
|
+
<country>United States</country>
|
67
|
+
<street>66 North 6th Street</street>
|
68
|
+
<postalcode>11211</postalcode>
|
69
|
+
<geo:point>
|
70
|
+
<geo:lat>40.719308</geo:lat>
|
71
|
+
|
72
|
+
<geo:long>-73.961607</geo:long>
|
73
|
+
</geo:point>
|
74
|
+
<timezone>EST</timezone>
|
75
|
+
</location>
|
76
|
+
<url>http://www.last.fm/venue/8851989</url>
|
77
|
+
</venue>
|
78
|
+
<startDate>Thu, 05 Mar 2009</startDate>
|
79
|
+
|
80
|
+
<startTime>19:00</startTime>
|
81
|
+
<description><![CDATA[<div class="bbcode">Doors 7 p.m. / Show 8 p.m.<br />
|
82
|
+
$10 advance / $12 day of show<br />
|
83
|
+
18+<br />
|
84
|
+
<br />
|
85
|
+
On sale Wed. 2/4 at noon</div>]]></description>
|
86
|
+
<image size="small">http://userserve-ak.last.fm/serve/34/3598785.jpg</image>
|
87
|
+
<image size="medium">http://userserve-ak.last.fm/serve/64/3598785.jpg</image>
|
88
|
+
<image size="large">http://userserve-ak.last.fm/serve/126/3598785.jpg</image>
|
89
|
+
<attendance>1</attendance>
|
90
|
+
|
91
|
+
<reviews>0</reviews>
|
92
|
+
<tag>lastfm:event=924763</tag>
|
93
|
+
<url>http://www.last.fm/event/924763</url>
|
94
|
+
</event>
|
95
|
+
</events></lfm>
|
96
|
+
|
97
|
+
)
|
98
|
+
|
99
|
+
|
100
|
+
class Response < Mapricot::Base
|
101
|
+
has_many :events, :xml
|
102
|
+
end
|
103
|
+
|
104
|
+
class Event < Mapricot::Base
|
105
|
+
has_one :id, :integer
|
106
|
+
has_one :title, :string
|
107
|
+
has_one :artist_group, :xml, :tag_name => "artists"
|
108
|
+
has_one :venue, :xml
|
109
|
+
end
|
110
|
+
|
111
|
+
class ArtistGroup < Mapricot::Base
|
112
|
+
has_many :artists, :string
|
113
|
+
has_one :headliner, :string
|
114
|
+
end
|
115
|
+
|
116
|
+
class Venue < Mapricot::Base
|
117
|
+
has_one :name, :string
|
118
|
+
end
|
119
|
+
|
120
|
+
lfm = Response.new(:xml => last_fm_example)
|
121
|
+
lfm.events.each do |event|
|
122
|
+
puts "-------------------------------"
|
123
|
+
puts event.title
|
124
|
+
puts event.artist_group.artists.inspect
|
125
|
+
puts event.venue.name
|
126
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Coming soon!
|
2
|
+
# class NaturalInputs < Mapricot
|
3
|
+
# has_one :message, :string
|
4
|
+
# has_many :occurrences, :xml
|
5
|
+
# end
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# class Occurrence < Mapricot::Base
|
9
|
+
# has_one :start_date
|
10
|
+
# has_one :end_date
|
11
|
+
# has_one :interval
|
12
|
+
# has_one :day_of_week
|
13
|
+
# has_one :week_of_month
|
14
|
+
# has_one :start_time
|
15
|
+
# has_one :end_time
|
16
|
+
# has_one :date_of_month
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
#
|
20
|
+
# n = NaturalInputs.new("http://localhost:3000/query?q=lunch+this+friday+with+maria")
|
21
|
+
# puts n.message # => "lunch with maria"
|
22
|
+
# puts n.occurrences.first.start_date # => "2009-01-16"
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require '../lib/mapricot'
|
2
|
+
|
3
|
+
# super simple
|
4
|
+
simple_xml = %(
|
5
|
+
<user>
|
6
|
+
<id>1</name>
|
7
|
+
<name>Bob</name>
|
8
|
+
<pet>cat</pet>
|
9
|
+
<pet>dog</pet>
|
10
|
+
</user>
|
11
|
+
)
|
12
|
+
|
13
|
+
class User < Mapricot::Base
|
14
|
+
has_one :id, :integer
|
15
|
+
has_one :name, :string
|
16
|
+
has_many :pets, :string
|
17
|
+
end
|
18
|
+
|
19
|
+
user = User.new(:xml => simple_xml)
|
20
|
+
puts user.id # => 1
|
21
|
+
puts user.id.class # => Fixnum
|
22
|
+
puts user.name # => Bob
|
23
|
+
puts user.pets.class # => Array
|
24
|
+
puts user.pets.join(", ") # => cat, dog
|
25
|
+
|
26
|
+
puts "-------------------"
|
27
|
+
|
28
|
+
# A little more realistic
|
29
|
+
xml = %(
|
30
|
+
<user>
|
31
|
+
<id>2</name>
|
32
|
+
<name>Sally</name>
|
33
|
+
<location code="ny123">
|
34
|
+
<city>New York</city>
|
35
|
+
<state>NY</state>
|
36
|
+
</location>
|
37
|
+
<hobby>
|
38
|
+
<description>Skiing</description>
|
39
|
+
<number_of_years>2</number_of_years>
|
40
|
+
</hobby>
|
41
|
+
<hobby>
|
42
|
+
<description>Hiking</description>
|
43
|
+
<number_of_years>3</number_of_years>
|
44
|
+
</hobby>
|
45
|
+
</user>
|
46
|
+
)
|
47
|
+
|
48
|
+
|
49
|
+
class User < Mapricot::Base
|
50
|
+
has_one :id, :integer
|
51
|
+
has_one :name # Tag type will default to :string
|
52
|
+
has_many :pets
|
53
|
+
has_one :location, :xml
|
54
|
+
has_many :hobbies, :xml
|
55
|
+
end
|
56
|
+
|
57
|
+
class Location < Mapricot::Base
|
58
|
+
has_attribute :code
|
59
|
+
has_one :city
|
60
|
+
has_one :state
|
61
|
+
end
|
62
|
+
|
63
|
+
class Hobby < Mapricot::Base
|
64
|
+
has_one :description, :string
|
65
|
+
has_one :number_of_years, :integer
|
66
|
+
end
|
67
|
+
|
68
|
+
user = User.new(:xml => xml)
|
69
|
+
puts user.name # => Sally
|
70
|
+
puts user.pets.inspect # => []
|
71
|
+
puts user.location.class # => Location
|
72
|
+
puts user.location.city # => New York
|
73
|
+
puts user.location.state # => NY
|
74
|
+
puts user.location.code # => ny123
|
75
|
+
puts user.hobbies.class # => Array
|
76
|
+
puts user.hobbies.first.class # => Hobby
|
77
|
+
|
78
|
+
user.hobbies.each do |hobby|
|
79
|
+
puts "#{hobby.description} for #{hobby.number_of_years} years"
|
80
|
+
end
|
81
|
+
# => Skiing for 2 years
|
82
|
+
# => Hiking for 3 years
|
data/lib/mapricot.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
begin
|
3
|
+
require 'hpricot'
|
4
|
+
rescue LoadError
|
5
|
+
require 'rubygems'
|
6
|
+
require 'hpricot'
|
7
|
+
end
|
8
|
+
# singularize, constantize, camelize, classify; doc here: http://api.rubyonrails.com/classes/Inflector.html
|
9
|
+
require 'active_support/inflector'
|
10
|
+
|
11
|
+
module Mapricot
|
12
|
+
|
13
|
+
# Inherit from base, e.g. class Animal < Mapricot::Base
|
14
|
+
# Use either a string of xml or a url to initialize
|
15
|
+
class Base
|
16
|
+
class << self
|
17
|
+
# @associations is used to initialize instance variables
|
18
|
+
# creates a new HasOneAssociation and appends it to the @associations list
|
19
|
+
def has_one(name, type = :string, opts = {})
|
20
|
+
ass = HasOneAssociation.new(name, type, opts)
|
21
|
+
self.name.match(/::/) && ass.namespace = self.name.match(/(.*)::[^:]+$/)[1]
|
22
|
+
associations << ass
|
23
|
+
class_eval "attr_reader :#{name}", __FILE__, __LINE__
|
24
|
+
end
|
25
|
+
# creates a new HasManyAssociation and appends it to the @associations list
|
26
|
+
def has_many(name, type = :string, opts = {})
|
27
|
+
ass = HasManyAssociation.new(name, type, opts)
|
28
|
+
self.name.match(/::/) && ass.namespace = self.name.match(/(.*)::[^:]+$/)[1]
|
29
|
+
associations << ass
|
30
|
+
class_eval "attr_reader :#{name}", __FILE__, __LINE__
|
31
|
+
end
|
32
|
+
def has_attribute(name)
|
33
|
+
attributes << name
|
34
|
+
class_eval "attr_reader :#{name}", __FILE__, __LINE__
|
35
|
+
end
|
36
|
+
def associations
|
37
|
+
@associations ||= []
|
38
|
+
end
|
39
|
+
def attributes
|
40
|
+
@attributes ||= []
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# class SomeClass < Mapricot::Base; end;
|
45
|
+
# SomeClass.new :url => "http://some_url"
|
46
|
+
# SomeClass.new :xml => %(<hi></hi>)
|
47
|
+
def initialize(opts)
|
48
|
+
@xml = Hpricot::XML(open(opts[:url])) if opts[:url]
|
49
|
+
@xml = Hpricot::XML(opts[:xml]) if opts[:xml]
|
50
|
+
load_associations
|
51
|
+
load_attributes
|
52
|
+
end
|
53
|
+
|
54
|
+
# searches xml for a tag with association.name, sets association.value to the inner html of this tag and typecasts it
|
55
|
+
def load_has_one(has_one_association)
|
56
|
+
has_one_association.search(@xml)
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_has_many(has_many_association)
|
60
|
+
has_many_association.search(@xml)
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_associations
|
64
|
+
# loop through the class's instance variable @attributes, which holds all of our associations
|
65
|
+
association_list = self.class.instance_variable_get(:@associations)
|
66
|
+
association_list && association_list.each do |ass|
|
67
|
+
load_has_one(ass) if ass.is_a?(HasOneAssociation)
|
68
|
+
load_has_many(ass) if ass.is_a?(HasManyAssociation)
|
69
|
+
# set instance variables and create accessors for each
|
70
|
+
instance_variable_set("@#{ass.name}", ass.value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def load_attributes
|
75
|
+
attr_list = self.class.instance_variable_get(:@attributes)
|
76
|
+
attr_list && attr_list.each do |att|
|
77
|
+
val = (@xml/self.class.name.downcase.match(/[^:]+$/)[0]).first.attributes[att.to_s]
|
78
|
+
instance_variable_set("@#{att}", val)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Abstract class; used to subclass HasOneAssociation and HasManyAssociation
|
84
|
+
class Association
|
85
|
+
VALID_TYPES = [:integer, :time, :xml, :string]
|
86
|
+
|
87
|
+
attr_accessor :name, :type, :value
|
88
|
+
attr_accessor :namespace
|
89
|
+
def initialize(name, type, opts = {})
|
90
|
+
raise "Don't instantiate me" if abstract_class?
|
91
|
+
@name, @type, @opts = name, type, opts
|
92
|
+
@namespace = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def typecast
|
96
|
+
raise "association type is invalid" unless VALID_TYPES.include?(@type)
|
97
|
+
if [:integer, :time].include?(@type)
|
98
|
+
@value = self.send("typecast_#{@type}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def typecast_integer
|
103
|
+
@value.is_a?(Array) ? @value.collect {|v| v.to_i} : @value.to_i
|
104
|
+
end
|
105
|
+
|
106
|
+
def typecast_time
|
107
|
+
if @value.is_a?(Array)
|
108
|
+
@value.collect {|v| Time.parse(v) }
|
109
|
+
else
|
110
|
+
Time.parse(@value)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
def abstract_class?
|
116
|
+
self.class == Association
|
117
|
+
end
|
118
|
+
|
119
|
+
def singular_name
|
120
|
+
@name.to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
def class_from_name
|
124
|
+
# ok, first we have to find how the class that inherited from Mapricot::Base is namespaced
|
125
|
+
# the class will an @associations class instance var, that will hold an instance of
|
126
|
+
if @namespace
|
127
|
+
"#{@namespace}::#{singular_name.classify}".constantize
|
128
|
+
else
|
129
|
+
singular_name.classify.constantize
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
class HasOneAssociation < Association
|
136
|
+
# searches xml for tag name, sets the inner xml as association value and typecasts it
|
137
|
+
def search(xml)
|
138
|
+
# if tag_name option was passed, use it:
|
139
|
+
element = (xml/"#{ @opts[:tag_name] || @name }").first # class Hpricot::Elements
|
140
|
+
if element
|
141
|
+
if @type == :xml
|
142
|
+
@value = class_from_name.new(:xml => element.to_s) # we want to include the tag, not just the inner_html
|
143
|
+
else
|
144
|
+
@value = element.inner_html
|
145
|
+
end
|
146
|
+
self.typecast
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
class HasManyAssociation < Association
|
153
|
+
|
154
|
+
def singular_name
|
155
|
+
# @name.to_s[0..-2]
|
156
|
+
"#{@name}".singularize
|
157
|
+
end
|
158
|
+
|
159
|
+
# searches xml for all occurrences of self.singular_name, the inner xml from each tag is stored in an array and set as the association value
|
160
|
+
# finally, each element in the array is typecast
|
161
|
+
def search(xml)
|
162
|
+
@value = []
|
163
|
+
(xml/"#{@opts[:tag_name] || self.singular_name}").each do |tag|
|
164
|
+
if @type == :xml
|
165
|
+
@value << class_from_name.new(:xml => tag.to_s) # a bit of recursion if the inner xml is more xml
|
166
|
+
else
|
167
|
+
@value << tag.inner_html # in the case of a string, integer, etc.
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
data/mapricot.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "mapricot"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.date = "2009-03-06"
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.summary = "XML to object mapper"
|
7
|
+
s.email = "lzell11@gmail.com"
|
8
|
+
s.homepage = ""
|
9
|
+
s.description = "Makes working with XML stupid easy. XML to object mapper with an interface similar to ActiveRecord associations."
|
10
|
+
s.has_rdoc = true
|
11
|
+
s.authors = ["Lou Zell"]
|
12
|
+
s.files = ["README.rdoc",
|
13
|
+
"History.txt",
|
14
|
+
"License.txt",
|
15
|
+
"mapricot.gemspec",
|
16
|
+
"examples/lastfm_api.rb",
|
17
|
+
"examples/lastfm_api_no_request.rb",
|
18
|
+
"examples/natural_inputs.rb",
|
19
|
+
"examples/readme_examples.rb",
|
20
|
+
"test/mapricot_tests.rb",
|
21
|
+
"test/mapricot_spec.rb",
|
22
|
+
"lib/mapricot.rb"]
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
s.rdoc_options = ["--main", "README.rdoc", "--inline-source", "--title", "Mapricot"]
|
25
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
26
|
+
s.add_dependency("hpricot")
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require '../lib/mapricot'
|
2
|
+
require 'spec'
|
3
|
+
|
4
|
+
# Using a Facebook example:
|
5
|
+
XML = %(
|
6
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
7
|
+
<users_getInfo_response xmlns="http://api.facebook.com/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://api.facebook.com/1.0/ http://api.facebook.com/1.0/facebook.xsd" list="true">
|
8
|
+
<user>
|
9
|
+
<uid>8055</uid>
|
10
|
+
<about_me>This field perpetuates the glorification of the ego. Also, it has a character limit.</about_me>
|
11
|
+
<activities>Here: facebook, etc. There: Glee Club, a capella, teaching.</activities>
|
12
|
+
<affiliations list="true">
|
13
|
+
<affiliation>
|
14
|
+
<nid>50453093</nid>
|
15
|
+
<name>Facebook Developers</name>
|
16
|
+
<type>work</type>
|
17
|
+
<status/>
|
18
|
+
<year/>
|
19
|
+
</affiliation>
|
20
|
+
</affiliations>
|
21
|
+
<birthday>November 3</birthday>
|
22
|
+
<books>The Brothers K, GEB, Ken Wilber, Zen and the Art, Fitzgerald, The Emporer's New Mind, The Wonderful Story of Henry Sugar</books>
|
23
|
+
<current_location>
|
24
|
+
<city>Palo Alto</city>
|
25
|
+
<state>CA</state>
|
26
|
+
<country>United States</country>
|
27
|
+
<zip>94303</zip>
|
28
|
+
</current_location>
|
29
|
+
</user>
|
30
|
+
</users_getInfo_reponse>
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
class UsersGetInfoResponse < Mapricot::Base
|
35
|
+
has_many :users, :xml
|
36
|
+
end
|
37
|
+
|
38
|
+
class User < Mapricot::Base
|
39
|
+
has_one :uid, :integer
|
40
|
+
has_one :about_me, :string
|
41
|
+
has_one :activities, :string
|
42
|
+
has_one :affiliation_list, :xml, :tag_name => "affiliations" # this is cumbersome, do something about this
|
43
|
+
has_one :birthday, :string
|
44
|
+
has_one :current_location, :xml
|
45
|
+
end
|
46
|
+
|
47
|
+
class AffiliationList < Mapricot::Base
|
48
|
+
has_many :affiliations, :xml
|
49
|
+
end
|
50
|
+
|
51
|
+
class Affiliation < Mapricot::Base
|
52
|
+
has_one :nid, :integer
|
53
|
+
has_one :name, :string
|
54
|
+
has_one :type, :string
|
55
|
+
has_one :status, :string
|
56
|
+
has_one :year, :integer
|
57
|
+
end
|
58
|
+
|
59
|
+
class CurrentLocation < Mapricot::Base
|
60
|
+
has_one :city, :string
|
61
|
+
has_one :state, :string
|
62
|
+
has_one :country, :string
|
63
|
+
has_one :zip, :integer
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
describe UsersGetInfoResponse do
|
68
|
+
before(:all) { @response = UsersGetInfoResponse.new(:xml => XML) }
|
69
|
+
|
70
|
+
it "should respond to users" do
|
71
|
+
@response.should respond_to(:users)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return an array of size 1 when sent users" do
|
75
|
+
@response.users.class.should equal(Array)
|
76
|
+
@response.users.size.should equal(1)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "response.users.first" do
|
81
|
+
before(:all) do
|
82
|
+
response = UsersGetInfoResponse.new(:xml => XML)
|
83
|
+
@first_user = response.users.first
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should be of class User" do
|
87
|
+
@first_user.class.should equal(User)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should respond to activities" do
|
91
|
+
@first_user.should respond_to(:activities)
|
92
|
+
@first_user.activities.should == "Here: facebook, etc. There: Glee Club, a capella, teaching."
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should respond to current_location" do
|
96
|
+
@first_user.should respond_to(:current_location)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
describe "response.users.first.current_location" do
|
102
|
+
before(:all) do
|
103
|
+
response = UsersGetInfoResponse.new(:xml => XML)
|
104
|
+
@current_location = response.users.first.current_location
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should have class CurrentLocation" do
|
108
|
+
@current_location.class.should equal(CurrentLocation)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should respond to city, state, country, and zip" do
|
112
|
+
@current_location.city.should == 'Palo Alto'
|
113
|
+
@current_location.state.should == 'CA'
|
114
|
+
@current_location.country.should == 'United States'
|
115
|
+
@current_location.zip.should == 94303
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require '../lib/mapricot'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
|
5
|
+
# -------------------------- Test Example 1 of README.txt ------------------ #
|
6
|
+
SIMPLE_USER_XML = %(
|
7
|
+
<user>
|
8
|
+
<id>1</name>
|
9
|
+
<name>Bob</name>
|
10
|
+
<pet>cat</pet>
|
11
|
+
<pet>dog</pet>
|
12
|
+
</user>
|
13
|
+
)
|
14
|
+
|
15
|
+
class SimpleUser < Mapricot::Base
|
16
|
+
has_one :id, :integer
|
17
|
+
has_one :name, :string
|
18
|
+
has_many :pets, :string
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
class TestSimpleUser < Test::Unit::TestCase
|
23
|
+
def setup
|
24
|
+
@simple_user = SimpleUser.new(:xml => SIMPLE_USER_XML)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_everything
|
28
|
+
assert_equal @simple_user.id, 1
|
29
|
+
assert_equal @simple_user.name, "Bob"
|
30
|
+
assert_equal @simple_user.pets, ["cat", "dog"]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
# -------------------------------------------------------------------------- #
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
# -------------------------- Test Example 2 of README.txt ------------------ #
|
40
|
+
USER_XML = %(
|
41
|
+
<user>
|
42
|
+
<id>2</name>
|
43
|
+
<name>Sally</name>
|
44
|
+
<location code="ny123">
|
45
|
+
<city>New York</city>
|
46
|
+
<state>NY</state>
|
47
|
+
</location>
|
48
|
+
<hobby>
|
49
|
+
<description>Skiing</description>
|
50
|
+
<number_of_years>2</number_of_years>
|
51
|
+
</hobby>
|
52
|
+
<hobby>
|
53
|
+
<description>Hiking</description>
|
54
|
+
<number_of_years>3</number_of_years>
|
55
|
+
</hobby>
|
56
|
+
</user>
|
57
|
+
)
|
58
|
+
|
59
|
+
|
60
|
+
class User < Mapricot::Base
|
61
|
+
has_one :id, :integer
|
62
|
+
has_one :name # Tag type will default to :string
|
63
|
+
has_many :pets
|
64
|
+
has_one :location, :xml
|
65
|
+
has_many :hobbies, :xml
|
66
|
+
end
|
67
|
+
|
68
|
+
class Location < Mapricot::Base
|
69
|
+
has_attribute :code
|
70
|
+
has_one :city
|
71
|
+
has_one :state
|
72
|
+
end
|
73
|
+
|
74
|
+
class Hobby < Mapricot::Base
|
75
|
+
has_one :description, :string
|
76
|
+
has_one :number_of_years, :integer
|
77
|
+
end
|
78
|
+
|
79
|
+
class TestUser < Test::Unit::TestCase
|
80
|
+
def setup
|
81
|
+
@user = User.new(:xml => USER_XML)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_id_and_name
|
85
|
+
assert_equal @user.id, 2
|
86
|
+
assert_equal @user.name, "Sally"
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_location
|
90
|
+
assert @user.location.is_a?(Location)
|
91
|
+
assert_equal @user.location.city, "New York"
|
92
|
+
assert_equal @user.location.state, "NY"
|
93
|
+
assert_equal @user.location.code, "ny123"
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_hobbies
|
97
|
+
assert @user.hobbies.is_a?(Array)
|
98
|
+
assert_equal @user.hobbies.size, 2
|
99
|
+
assert_equal @user.hobbies[0].description, "Skiing"
|
100
|
+
assert_equal @user.hobbies[0].number_of_years, 2
|
101
|
+
assert_equal @user.hobbies[1].description, "Hiking"
|
102
|
+
assert_equal @user.hobbies[1].number_of_years, 3
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_pets
|
106
|
+
assert(@user.pets.empty?) # Sally has no pets
|
107
|
+
end
|
108
|
+
end
|
109
|
+
# -------------------------------------------------------------------------- #
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lzell-mapricot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lou Zell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-06 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hpricot
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Makes working with XML stupid easy. XML to object mapper with an interface similar to ActiveRecord associations.
|
26
|
+
email: lzell11@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
files:
|
34
|
+
- README.rdoc
|
35
|
+
- History.txt
|
36
|
+
- License.txt
|
37
|
+
- mapricot.gemspec
|
38
|
+
- examples/lastfm_api.rb
|
39
|
+
- examples/lastfm_api_no_request.rb
|
40
|
+
- examples/natural_inputs.rb
|
41
|
+
- examples/readme_examples.rb
|
42
|
+
- test/mapricot_tests.rb
|
43
|
+
- test/mapricot_spec.rb
|
44
|
+
- lib/mapricot.rb
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: ""
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --main
|
50
|
+
- README.rdoc
|
51
|
+
- --inline-source
|
52
|
+
- --title
|
53
|
+
- Mapricot
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.2.0
|
72
|
+
signing_key:
|
73
|
+
specification_version: 2
|
74
|
+
summary: XML to object mapper
|
75
|
+
test_files: []
|
76
|
+
|