lightningdb-happymapper 0.3.0
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 +47 -0
- data/License +20 -0
- data/Manifest +37 -0
- data/README +28 -0
- data/Rakefile +43 -0
- data/TODO +0 -0
- data/examples/amazon.rb +34 -0
- data/examples/current_weather.rb +21 -0
- data/examples/dashed_elements.rb +20 -0
- data/examples/post.rb +19 -0
- data/examples/twitter.rb +37 -0
- data/happymapper.gemspec +38 -0
- data/lib/happymapper/attribute.rb +3 -0
- data/lib/happymapper/element.rb +3 -0
- data/lib/happymapper/item.rb +148 -0
- data/lib/happymapper/version.rb +3 -0
- data/lib/happymapper.rb +137 -0
- data/spec/fixtures/address.xml +8 -0
- data/spec/fixtures/commit.xml +52 -0
- data/spec/fixtures/current_weather.xml +89 -0
- data/spec/fixtures/family_tree.xml +7 -0
- data/spec/fixtures/multiple_namespaces.xml +170 -0
- data/spec/fixtures/pita.xml +133 -0
- data/spec/fixtures/posts.xml +23 -0
- data/spec/fixtures/product_default_namespace.xml +17 -0
- data/spec/fixtures/product_no_namespace.xml +10 -0
- data/spec/fixtures/product_single_namespace.xml +10 -0
- data/spec/fixtures/quarters.xml +19 -0
- data/spec/fixtures/radar.xml +21 -0
- data/spec/fixtures/statuses.xml +422 -0
- data/spec/happymapper_attribute_spec.rb +17 -0
- data/spec/happymapper_element_spec.rb +17 -0
- data/spec/happymapper_item_spec.rb +94 -0
- data/spec/happymapper_spec.rb +555 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +13 -0
- data/website/css/common.css +47 -0
- data/website/index.html +98 -0
- metadata +120 -0
data/History
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
== 0.2.2
|
2
|
+
* 2 minor tweaks
|
3
|
+
* removed GC.start (libxml recommended this) as setting nodes to nil should be enough, specs run 3-4x faster (Brandon Keepers)
|
4
|
+
* renamed get_tag_name to tag_name (Brandon Keepers)
|
5
|
+
* removed libxml helpers as they are no longer needed
|
6
|
+
|
7
|
+
== 0.2.1
|
8
|
+
* 1 minor fix, 3 major enhancements
|
9
|
+
* fixed warnings about using XML::Parser (mojodna)
|
10
|
+
* Improved namespace support, now handles multiple namespaces and allows namespaces to be set item wide or on a per element basis (mojodna)
|
11
|
+
* Auto detect root nodes (mojodna)
|
12
|
+
* Type coercion (mojodna)
|
13
|
+
|
14
|
+
== 0.2.0
|
15
|
+
* 1 major enhancement, 2 minor ehancements
|
16
|
+
* Automatic handling of namespaces (part by Robert Lowrey and rest by John Nunemaker)
|
17
|
+
* 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
|
18
|
+
* Now defaulting tag names for classes in modules to last constant downcased
|
19
|
+
|
20
|
+
== 0.1.7 2009-01-29
|
21
|
+
* 1 minor enhancement
|
22
|
+
* Support dashes in elements (Josh Nichols)
|
23
|
+
|
24
|
+
== 0.1.6 2009-01-17
|
25
|
+
* 1 minor enhancement:
|
26
|
+
* added support for nested collection elements (Justin Marney)
|
27
|
+
|
28
|
+
== 0.1.5 2009-01-05
|
29
|
+
* 1 major enhancement:
|
30
|
+
* Updated to latest version of libxml-ruby (lightningdb)
|
31
|
+
|
32
|
+
== 0.1.4 2009-01-05
|
33
|
+
* 1 major enhancement:
|
34
|
+
* Fixed parsing when the object is the root node. (Garret Alfert)
|
35
|
+
|
36
|
+
== 0.1.3 2008-12-31
|
37
|
+
* 1 major enhancement:
|
38
|
+
* Added parsing of attributes of elements that are also mapped, see current_weather.rb for example (jeremyf)
|
39
|
+
|
40
|
+
== 0.1.2 2008-12-12
|
41
|
+
* 1 major enhancement:
|
42
|
+
* Fixed that :deep only worked for first item (dvrensk)
|
43
|
+
|
44
|
+
== 0.1.0 2008-11-16
|
45
|
+
|
46
|
+
* 1 major enhancement:
|
47
|
+
* 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,37 @@
|
|
1
|
+
examples/amazon.rb
|
2
|
+
examples/current_weather.rb
|
3
|
+
examples/dashed_elements.rb
|
4
|
+
examples/post.rb
|
5
|
+
examples/twitter.rb
|
6
|
+
happymapper.gemspec
|
7
|
+
History
|
8
|
+
lib/happymapper/attribute.rb
|
9
|
+
lib/happymapper/element.rb
|
10
|
+
lib/happymapper/item.rb
|
11
|
+
lib/happymapper/version.rb
|
12
|
+
lib/happymapper.rb
|
13
|
+
License
|
14
|
+
Manifest
|
15
|
+
Rakefile
|
16
|
+
README
|
17
|
+
spec/fixtures/address.xml
|
18
|
+
spec/fixtures/commit.xml
|
19
|
+
spec/fixtures/current_weather.xml
|
20
|
+
spec/fixtures/family_tree.xml
|
21
|
+
spec/fixtures/multiple_namespaces.xml
|
22
|
+
spec/fixtures/pita.xml
|
23
|
+
spec/fixtures/posts.xml
|
24
|
+
spec/fixtures/product_default_namespace.xml
|
25
|
+
spec/fixtures/product_no_namespace.xml
|
26
|
+
spec/fixtures/product_single_namespace.xml
|
27
|
+
spec/fixtures/radar.xml
|
28
|
+
spec/fixtures/statuses.xml
|
29
|
+
spec/happymapper_attribute_spec.rb
|
30
|
+
spec/happymapper_element_spec.rb
|
31
|
+
spec/happymapper_item_spec.rb
|
32
|
+
spec/happymapper_spec.rb
|
33
|
+
spec/spec.opts
|
34
|
+
spec/spec_helper.rb
|
35
|
+
TODO
|
36
|
+
website/css/common.css
|
37
|
+
website/index.html
|
data/README
ADDED
@@ -0,0 +1,28 @@
|
|
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
|
+
== FEATURES:
|
9
|
+
|
10
|
+
* Easy to define xml attributes and elements for an object
|
11
|
+
* Fast because it uses libxml-ruby under the hood
|
12
|
+
* Automatic conversion of xml to defined objects
|
13
|
+
* Reusable classes via a node finding mechanism that searches by 1. specified tag,
|
14
|
+
2. name of element, 3. class name. (gemspec was upgraded to 0.3.0 for this change)
|
15
|
+
|
16
|
+
== SYNOPSIS:
|
17
|
+
|
18
|
+
See examples directory in the gem to get a feel for how it works.
|
19
|
+
|
20
|
+
== INSTALL:
|
21
|
+
|
22
|
+
* add github to your sources if you haven't gem sources -a http://gems.github.com
|
23
|
+
* sudo gem install jnunemaker-happymapper
|
24
|
+
* sudo gem install happymapper (when rubyforge approves and i release there)
|
25
|
+
|
26
|
+
== TICKETS:
|
27
|
+
|
28
|
+
http://jnunemaker.lighthouseapp.com/projects/20014-happy-mapper/overview
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
ProjectName = 'happymapper'
|
2
|
+
WebsitePath = "jnunemaker@rubyforge.org:/var/www/gforge-projects/#{ProjectName}"
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rake'
|
6
|
+
require 'echoe'
|
7
|
+
require 'spec/rake/spectask'
|
8
|
+
require "lib/#{ProjectName}/version"
|
9
|
+
|
10
|
+
Echoe.new(ProjectName, HappyMapper::Version) do |p|
|
11
|
+
p.description = "object to xml mapping library"
|
12
|
+
p.install_message = "May you have many happy mappings!"
|
13
|
+
p.url = "http://#{ProjectName}.rubyforge.org"
|
14
|
+
p.author = "John Nunemaker"
|
15
|
+
p.email = "nunemaker@gmail.com"
|
16
|
+
p.extra_deps = [['libxml-ruby', '= 0.9.8']]
|
17
|
+
p.need_tar_gz = false
|
18
|
+
p.docs_host = WebsitePath
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Upload website files to rubyforge'
|
22
|
+
task :website do
|
23
|
+
sh %{rsync -av website/ #{WebsitePath}}
|
24
|
+
Rake::Task['website_docs'].invoke
|
25
|
+
end
|
26
|
+
|
27
|
+
task :website_docs do
|
28
|
+
Rake::Task['redocs'].invoke
|
29
|
+
sh %{rsync -av doc/ #{WebsitePath}/docs}
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Preps the gem for a new release'
|
33
|
+
task :prepare do
|
34
|
+
%w[manifest build_gemspec].each do |task|
|
35
|
+
Rake::Task[task].invoke
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::Task[:default].prerequisites.clear
|
40
|
+
task :default => :spec
|
41
|
+
Spec::Rake::SpecTask.new do |t|
|
42
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
43
|
+
end
|
data/TODO
ADDED
File without changes
|
data/examples/amazon.rb
ADDED
@@ -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
|
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, '' }
|
data/examples/twitter.rb
ADDED
@@ -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
|
data/happymapper.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{happymapper}
|
5
|
+
s.version = "0.3.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["John Nunemaker"]
|
9
|
+
s.date = %q{2009-01-29}
|
10
|
+
s.description = %q{object to xml mapping library}
|
11
|
+
s.email = %q{nunemaker@gmail.com}
|
12
|
+
s.extra_rdoc_files = ["lib/happymapper/attribute.rb", "lib/happymapper/element.rb", "lib/happymapper/item.rb", "lib/happymapper/version.rb", "lib/happymapper.rb", "README", "TODO"]
|
13
|
+
s.files = ["examples/amazon.rb", "examples/current_weather.rb", "examples/dashed_elements.rb", "examples/post.rb", "examples/twitter.rb", "happymapper.gemspec", "History", "lib/happymapper/attribute.rb", "lib/happymapper/element.rb", "lib/happymapper/item.rb", "lib/happymapper/version.rb", "lib/happymapper.rb", "License", "Manifest", "Rakefile", "README", "spec/fixtures/address.xml", "spec/fixtures/commit.xml", "spec/fixtures/current_weather.xml", "spec/fixtures/family_tree.xml", "spec/fixtures/multiple_namespaces.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/spec.opts", "spec/spec_helper.rb", "TODO", "website/css/common.css", "website/index.html"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://happymapper.rubyforge.org}
|
16
|
+
s.post_install_message = %q{May you have many happy mappings!}
|
17
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Happymapper", "--main", "README"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.rubyforge_project = %q{happymapper}
|
20
|
+
s.rubygems_version = %q{1.3.1}
|
21
|
+
s.summary = %q{object to xml mapping library}
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 2
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_runtime_dependency(%q<libxml-ruby>, ["= 0.9.8"])
|
29
|
+
s.add_development_dependency(%q<echoe>, [">= 0"])
|
30
|
+
else
|
31
|
+
s.add_dependency(%q<libxml-ruby>, ["= 0.9.8"])
|
32
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
33
|
+
end
|
34
|
+
else
|
35
|
+
s.add_dependency(%q<libxml-ruby>, ["= 0.9.8"])
|
36
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module HappyMapper
|
2
|
+
class Item
|
3
|
+
attr_accessor :name, :type, :tag, :options, :namespace
|
4
|
+
|
5
|
+
Types = [String, Float, Time, Date, DateTime, Integer, Boolean]
|
6
|
+
|
7
|
+
# options:
|
8
|
+
# :deep => Boolean False to only parse element's children, True to include
|
9
|
+
# grandchildren and all others down the chain (// in xpath)
|
10
|
+
# :namespace => String Element's namespace if it's not the global or inherited
|
11
|
+
# default
|
12
|
+
# :parser => Symbol Class method to use for type coercion.
|
13
|
+
# :raw => Boolean Use raw node value (inc. tags) when parsing.
|
14
|
+
# :single => Boolean False if object should be collection, True for single object
|
15
|
+
# :tag => String Element name if it doesn't match the specified name.
|
16
|
+
def initialize(name, type, o={})
|
17
|
+
self.name = name.to_s
|
18
|
+
self.type = type
|
19
|
+
#self.tag = o.delete(:tag) || name.to_s
|
20
|
+
self.tag = o[:tag] || name.to_s
|
21
|
+
self.options = o.merge(:name => self.name)
|
22
|
+
|
23
|
+
@xml_type = self.class.to_s.split('::').last.downcase
|
24
|
+
end
|
25
|
+
|
26
|
+
def from_xml_node(node, namespace)
|
27
|
+
if primitive?
|
28
|
+
find(node, namespace) do |n|
|
29
|
+
if n.respond_to?(:content)
|
30
|
+
typecast(n.content)
|
31
|
+
else
|
32
|
+
typecast(n.to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
if options[:parser]
|
37
|
+
find(node, namespace) do |n|
|
38
|
+
if n.respond_to?(:content) && !options[:raw]
|
39
|
+
value = n.content
|
40
|
+
else
|
41
|
+
value = n.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
type.send(options[:parser].to_sym, value)
|
46
|
+
rescue
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
type.parse(node, options)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def xpath(namespace = self.namespace)
|
57
|
+
xpath = ''
|
58
|
+
xpath += './/' if options[:deep]
|
59
|
+
xpath += "#{namespace}:" if namespace
|
60
|
+
xpath += tag
|
61
|
+
# puts "xpath: #{xpath}"
|
62
|
+
xpath
|
63
|
+
end
|
64
|
+
|
65
|
+
def primitive?
|
66
|
+
Types.include?(type)
|
67
|
+
end
|
68
|
+
|
69
|
+
def element?
|
70
|
+
@xml_type == 'element'
|
71
|
+
end
|
72
|
+
|
73
|
+
def attribute?
|
74
|
+
!element?
|
75
|
+
end
|
76
|
+
|
77
|
+
def method_name
|
78
|
+
@method_name ||= name.tr('-', '_')
|
79
|
+
end
|
80
|
+
|
81
|
+
def typecast(value)
|
82
|
+
return value if value.kind_of?(type) || value.nil?
|
83
|
+
begin
|
84
|
+
if type == String then value.to_s
|
85
|
+
elsif type == Float then value.to_f
|
86
|
+
elsif type == Time then Time.parse(value.to_s)
|
87
|
+
elsif type == Date then Date.parse(value.to_s)
|
88
|
+
elsif type == DateTime then DateTime.parse(value.to_s)
|
89
|
+
elsif type == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
|
90
|
+
elsif type == Integer
|
91
|
+
# ganked from datamapper
|
92
|
+
value_to_i = value.to_i
|
93
|
+
if value_to_i == 0 && value != '0'
|
94
|
+
value_to_s = value.to_s
|
95
|
+
begin
|
96
|
+
Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
|
97
|
+
rescue ArgumentError
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
else
|
101
|
+
value_to_i
|
102
|
+
end
|
103
|
+
else
|
104
|
+
value
|
105
|
+
end
|
106
|
+
rescue
|
107
|
+
value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
def find(node, namespace, &block)
|
113
|
+
# this node has a custom namespace (that is present in the doc)
|
114
|
+
if self.namespace && node.namespaces.find_by_prefix(self.namespace)
|
115
|
+
# from the class definition
|
116
|
+
namespace = self.namespace
|
117
|
+
elsif options[:namespace] && node.namespaces.find_by_prefix(options[:namespace])
|
118
|
+
# from an element definition
|
119
|
+
namespace = options[:namespace]
|
120
|
+
end
|
121
|
+
|
122
|
+
if element?
|
123
|
+
result = node.find_first(xpath(namespace))
|
124
|
+
# puts "vfxn: #{xpath} #{result.inspect}"
|
125
|
+
if result
|
126
|
+
value = yield(result)
|
127
|
+
if options[:attributes].is_a?(Hash)
|
128
|
+
result.attributes.each do |xml_attribute|
|
129
|
+
if attribute_options = options[:attributes][xml_attribute.name.to_sym]
|
130
|
+
attribute_value = Attribute.new(xml_attribute.name.to_sym, *attribute_options).from_xml_node(result, namespace)
|
131
|
+
result.instance_eval <<-EOV
|
132
|
+
def value.#{xml_attribute.name}
|
133
|
+
#{attribute_value.inspect}
|
134
|
+
end
|
135
|
+
EOV
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
value
|
140
|
+
else
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
else
|
144
|
+
yield(node[tag])
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/lib/happymapper.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
$:.unshift(dir) unless $:.include?(dir) || $:.include?(File.expand_path(dir))
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
require 'time'
|
6
|
+
require 'rubygems'
|
7
|
+
gem 'libxml-ruby', '= 0.9.8'
|
8
|
+
require 'xml'
|
9
|
+
|
10
|
+
class Boolean; end
|
11
|
+
|
12
|
+
module HappyMapper
|
13
|
+
|
14
|
+
DEFAULT_NS = "happymapper"
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
base.instance_variable_set("@attributes", {})
|
18
|
+
base.instance_variable_set("@elements", {})
|
19
|
+
base.extend ClassMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def attribute(name, type, options={})
|
24
|
+
attribute = Attribute.new(name, type, options)
|
25
|
+
@attributes[to_s] ||= []
|
26
|
+
@attributes[to_s] << attribute
|
27
|
+
attr_accessor attribute.method_name.intern
|
28
|
+
end
|
29
|
+
|
30
|
+
def attributes
|
31
|
+
@attributes[to_s] || []
|
32
|
+
end
|
33
|
+
|
34
|
+
def element(name, type, options={})
|
35
|
+
element = Element.new(name, type, options)
|
36
|
+
@elements[to_s] ||= []
|
37
|
+
@elements[to_s] << element
|
38
|
+
attr_accessor element.method_name.intern
|
39
|
+
end
|
40
|
+
|
41
|
+
def elements
|
42
|
+
@elements[to_s] || []
|
43
|
+
end
|
44
|
+
|
45
|
+
def has_one(name, type, options={})
|
46
|
+
element name, type, {:single => true}.merge(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_many(name, type, options={})
|
50
|
+
element name, type, {:single => false}.merge(options)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Specify a namespace if a node and all its children are all namespaced
|
54
|
+
# elements. This is simpler than passing the :namespace option to each
|
55
|
+
# defined element.
|
56
|
+
def namespace(namespace = nil)
|
57
|
+
@namespace = namespace if namespace
|
58
|
+
@namespace
|
59
|
+
end
|
60
|
+
|
61
|
+
def tag(new_tag_name)
|
62
|
+
@tag_name = new_tag_name.to_s unless new_tag_name.nil? || new_tag_name.to_s.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
def tag_name
|
66
|
+
@tag_name ||= to_s.split('::')[-1].downcase
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse(xml, options = {})
|
70
|
+
# locally scoped copy of namespace for this parse run
|
71
|
+
namespace = @namespace
|
72
|
+
|
73
|
+
if xml.is_a?(XML::Node)
|
74
|
+
node = xml
|
75
|
+
else
|
76
|
+
if xml.is_a?(XML::Document)
|
77
|
+
node = xml.root
|
78
|
+
else
|
79
|
+
node = XML::Parser.string(xml).parse.root
|
80
|
+
end
|
81
|
+
|
82
|
+
root = node.name == tag_name
|
83
|
+
end
|
84
|
+
|
85
|
+
# This is the entry point into the parsing pipeline, so the default
|
86
|
+
# namespace prefix registered here will propagate down
|
87
|
+
namespaces = node.namespaces
|
88
|
+
if namespaces && namespaces.default
|
89
|
+
# don't assign the default_prefix if it has already been assigned
|
90
|
+
namespaces.default_prefix = DEFAULT_NS unless namespaces.find_by_prefix(DEFAULT_NS)
|
91
|
+
namespace ||= DEFAULT_NS
|
92
|
+
end
|
93
|
+
|
94
|
+
xpath = root ? '/' : './/'
|
95
|
+
xpath += "#{namespace}:" if namespace
|
96
|
+
#puts "parse: #{xpath}"
|
97
|
+
|
98
|
+
nodes = []
|
99
|
+
# when finding nodes, do it in this order:
|
100
|
+
# 1. specified tag
|
101
|
+
# 2. name of element
|
102
|
+
# 3. tag_name (derived from class name by default)
|
103
|
+
[options[:tag], options[:name], tag_name].compact.each do |xpath_ext|
|
104
|
+
nodes = node.find(xpath + xpath_ext.to_s)
|
105
|
+
break if nodes && nodes.size > 0
|
106
|
+
end
|
107
|
+
collection = nodes.collect do |n|
|
108
|
+
obj = new
|
109
|
+
|
110
|
+
attributes.each do |attr|
|
111
|
+
obj.send("#{attr.method_name}=",
|
112
|
+
attr.from_xml_node(n, namespace))
|
113
|
+
end
|
114
|
+
|
115
|
+
elements.each do |elem|
|
116
|
+
obj.send("#{elem.method_name}=",
|
117
|
+
elem.from_xml_node(n, namespace))
|
118
|
+
end
|
119
|
+
|
120
|
+
obj
|
121
|
+
end
|
122
|
+
|
123
|
+
# per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
|
124
|
+
nodes = nil
|
125
|
+
|
126
|
+
if options[:single] || root
|
127
|
+
collection.first
|
128
|
+
else
|
129
|
+
collection
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
require 'happymapper/item'
|
136
|
+
require 'happymapper/attribute'
|
137
|
+
require 'happymapper/element'
|