pipa-xmlnuts 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +5 -0
- data/lib/xmlnuts.rb +1 -0
- data/lib/xmlnuts/converters.rb +115 -0
- data/lib/xmlnuts/mappings.rb +137 -0
- data/lib/xmlnuts/nuts.rb +72 -0
- data/test/parsing_test.rb +85 -0
- metadata +62 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Igor Gunko
|
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/README
ADDED
data/lib/xmlnuts.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'xmlnuts/nuts'
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module XmlNuts
|
2
|
+
module Converters
|
3
|
+
def self.lookup(type)
|
4
|
+
lookup!(type)
|
5
|
+
rescue ArgumentError
|
6
|
+
nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.lookup!(type)
|
10
|
+
const_get("Convert_#{type}")
|
11
|
+
rescue NameError
|
12
|
+
raise ArgumentError, "converter not found for #{type}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.create(type, options)
|
16
|
+
create!(type, options)
|
17
|
+
rescue ArgumentError
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.create!(type, options)
|
22
|
+
lookup!(type).new(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
class Convert_string #:nodoc:
|
26
|
+
def initialize(options)
|
27
|
+
@whitespace = options[:whitespace] || :trim
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_xml(string)
|
31
|
+
string
|
32
|
+
end
|
33
|
+
|
34
|
+
def from_xml(string)
|
35
|
+
return nil unless string
|
36
|
+
string = case @whitespace
|
37
|
+
when :trim then string.strip
|
38
|
+
when :preserve then string
|
39
|
+
when :collapse then string.gsub(/\s+/, ' ').strip
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Convert_boolean < Convert_string #:nodoc:
|
45
|
+
def initialize(options)
|
46
|
+
super
|
47
|
+
@format = options[:format] || :truefalse
|
48
|
+
raise ArgumentError, "unrecognized format #{@format}" unless [:truefalse, :yesno, :numeric].include?(@format)
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_xml(flag)
|
52
|
+
return nil if flag.nil?
|
53
|
+
case @format
|
54
|
+
when :truefalse then flag ? 'true' : 'false'
|
55
|
+
when :yesno then flag ? 'yes' : 'no'
|
56
|
+
when :numeric then flag ? '0' : '1'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def from_xml(string)
|
61
|
+
return nil unless string
|
62
|
+
case string = super(string)
|
63
|
+
when '1', 'true', 'yes' then true
|
64
|
+
when '0', 'false', 'no' then false
|
65
|
+
else
|
66
|
+
raise ArgumentError, "invalid value for boolean: #{string.inspect}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Convert_integer < Convert_string #:nodoc:
|
72
|
+
def initialize(options)
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_xml(int)
|
77
|
+
int.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
def from_xml(string)
|
81
|
+
string && Integer(super(string))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Convert_datetime < Convert_string #:nodoc:
|
86
|
+
def initialize(options)
|
87
|
+
super
|
88
|
+
@fraction_digits = options[:fraction_digits] || 0
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_xml(time)
|
92
|
+
time && time.xmlschema(@fraction_digits)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.from_xml(string)
|
96
|
+
string && Time.parse(super(string, options))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Convert_list #:nodoc:
|
101
|
+
def initialize(options)
|
102
|
+
@item_type = options[:item_type] || :string
|
103
|
+
@item_converter = Converters.create!(@item_type, options)
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_xml(array)
|
107
|
+
array.map {|x| @item_converter.to_xml(x) } * ' '
|
108
|
+
end
|
109
|
+
|
110
|
+
def from_xml(string)
|
111
|
+
string && string.split.map! {|x| @item_converter.from_xml(x)}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module XmlNuts
|
2
|
+
class Mapping
|
3
|
+
attr_reader :name, :xmlname, :type, :options, :converter
|
4
|
+
|
5
|
+
def initialize(name, type, options)
|
6
|
+
@name, @xmlname, @type, @options = name.to_sym, (options.delete(:xmlname) || name).to_s, type, options
|
7
|
+
@setter = :"#{name}="
|
8
|
+
@converter = Converters.create(type, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_xml(nut, node)
|
12
|
+
setxml(node, toxml(get(nut)))
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_xml(nut, node)
|
16
|
+
set(nut, froxml(getxml(node)))
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def get(nut) #:doc:
|
21
|
+
nut.send(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def set(nut, value) #:doc:
|
25
|
+
nut.send(@setter, value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def toxml(value) #:doc:
|
29
|
+
value
|
30
|
+
end
|
31
|
+
|
32
|
+
def froxml(text) #:doc:
|
33
|
+
text
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class PrimitiveMapping < Mapping
|
38
|
+
def initialize(name, type, options)
|
39
|
+
if type.is_a?(Array)
|
40
|
+
raise ArgumentError, "invalid value for type: #{type}" if type.length != 1
|
41
|
+
type, options[:item_type] = :list, type.first
|
42
|
+
end
|
43
|
+
super
|
44
|
+
raise ArgumentError, "converter absent for type #{type.inspect}" unless converter
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def toxml(value) #:doc:
|
49
|
+
converter.to_xml(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
def froxml(text) #:doc:
|
53
|
+
converter.from_xml(text)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ElementMapping < PrimitiveMapping
|
58
|
+
private
|
59
|
+
def getxml(node) #:doc:
|
60
|
+
(e = node.elements[xmlname]) && e.text
|
61
|
+
end
|
62
|
+
|
63
|
+
def setxml(node, value) #:doc:
|
64
|
+
(node.elements[xmlname] ||= REXML::Element.new(xmlname)).text = value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class AttributeMapping < PrimitiveMapping
|
69
|
+
private
|
70
|
+
def getxml(node) #:doc:
|
71
|
+
node.attributes[xmlname]
|
72
|
+
end
|
73
|
+
|
74
|
+
def setxml(node, value) #:doc:
|
75
|
+
node.add_attribute(xmlname, value)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
module NestedMany
|
80
|
+
private
|
81
|
+
def toxml(nested_nuts) #:doc:
|
82
|
+
nested_nuts && nested_nuts.map {|x| super(x) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def froxml(nested_nodes) #:doc:
|
86
|
+
nested_nodes && nested_nodes.map {|x| super(x) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class ElementsMapping < PrimitiveMapping
|
91
|
+
include NestedMany
|
92
|
+
|
93
|
+
private
|
94
|
+
def getxml(node) #:doc:
|
95
|
+
(e = node.get_elements(xmlname)) && e.map {|x| x.text }
|
96
|
+
end
|
97
|
+
|
98
|
+
def setxml(node, values) #:doc:
|
99
|
+
values.each {|x| node.add_element(xmlname).text = x } if values
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class NestedMapping < Mapping
|
104
|
+
private
|
105
|
+
def toxml(nested_nut) #:doc:
|
106
|
+
type.build_node(nested_nut, Element.new(xmlname))
|
107
|
+
end
|
108
|
+
|
109
|
+
def froxml(nested_node) #:doc:
|
110
|
+
type.parse_node(type.new, nested_node)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class NestedOneMapping < NestedMapping
|
115
|
+
private
|
116
|
+
def getxml(node) #:doc:
|
117
|
+
node.elements[xmlname]
|
118
|
+
end
|
119
|
+
|
120
|
+
def setxml(node, nested_node) #:doc:
|
121
|
+
node.elements << nested_node if nested_node
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class NestedManyMapping < NestedMapping
|
126
|
+
include NestedMany
|
127
|
+
|
128
|
+
private
|
129
|
+
def getxml(node) #:doc:
|
130
|
+
node.get_elements(xmlname)
|
131
|
+
end
|
132
|
+
|
133
|
+
def setxml(node, nested_nodes) #:doc:
|
134
|
+
nested_nodes.each {|x| node.add_element(x) }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/xmlnuts/nuts.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'time'
|
3
|
+
require 'xmlnuts/converters'
|
4
|
+
require 'xmlnuts/mappings'
|
5
|
+
|
6
|
+
module XmlNuts #:nodoc:
|
7
|
+
module Nut
|
8
|
+
def self.included(other) #:nodoc:
|
9
|
+
other.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def element(name, type = :string, options = {})
|
14
|
+
mappings << (type.is_a?(Class) ? NestedOneMapping : ElementMapping).new(name, type, options)
|
15
|
+
attr_accessor name
|
16
|
+
end
|
17
|
+
|
18
|
+
def elements(name, type = :string, options = {})
|
19
|
+
mappings << (type.is_a?(Class) ? NestedManyMapping : ElementsMapping).new(name, type, options)
|
20
|
+
attr_accessor name
|
21
|
+
end
|
22
|
+
|
23
|
+
def attribute(name, type = :string, options = {})
|
24
|
+
mappings << AttributeMapping.new(name, type, options)
|
25
|
+
attr_accessor name
|
26
|
+
end
|
27
|
+
|
28
|
+
def mappings
|
29
|
+
@mappings ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
def build(nut, destination = nil)
|
33
|
+
case destination
|
34
|
+
when nil
|
35
|
+
destination = REXML::Document.new
|
36
|
+
e = destination.add_element('root')
|
37
|
+
build_node(nut, e)
|
38
|
+
when REXML::Node
|
39
|
+
build_node(nut, destination)
|
40
|
+
end
|
41
|
+
destination
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse(source)
|
45
|
+
case source
|
46
|
+
when nil
|
47
|
+
nil
|
48
|
+
when REXML::Node
|
49
|
+
parse_node(new, source)
|
50
|
+
when String
|
51
|
+
doc = REXML::Document.new(source)
|
52
|
+
(root = doc.root) ? parse_node(new, root) : nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_node(nut, node)
|
57
|
+
callem(:to_xml, nut, node)
|
58
|
+
node
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_node(nut, node)
|
62
|
+
callem(:from_xml ,nut, node)
|
63
|
+
nut
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def callem(method, nut, node)
|
68
|
+
mappings.each {|m| m.send(method, nut, node) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'shoulda'
|
5
|
+
require 'lib/xmlnuts'
|
6
|
+
|
7
|
+
class Cheezburger
|
8
|
+
include XmlNuts::Nut
|
9
|
+
|
10
|
+
attribute :weight, :integer
|
11
|
+
end
|
12
|
+
|
13
|
+
class Pet
|
14
|
+
include XmlNuts::Nut
|
15
|
+
|
16
|
+
element :eats, [:string], :xmlname => :ration
|
17
|
+
element :species, :string, :whitespace => :collapse
|
18
|
+
elements :paws, :string, :xmlname => :paw
|
19
|
+
|
20
|
+
attribute :has_tail, :boolean, :xmlname => 'has-tail'
|
21
|
+
attribute :height, :integer
|
22
|
+
|
23
|
+
element :cheezburger, Cheezburger
|
24
|
+
end
|
25
|
+
|
26
|
+
class ParsingTest < Test::Unit::TestCase
|
27
|
+
context "Old McDonald's pet" do
|
28
|
+
setup do
|
29
|
+
@xml_fragment = <<-EOS
|
30
|
+
<mypet height=' 12 ' has-tail=' yes '>
|
31
|
+
<species> silly
|
32
|
+
mouse
|
33
|
+
</species>
|
34
|
+
<ration> tigers
|
35
|
+
lions
|
36
|
+
</ration>
|
37
|
+
<paw> one</paw>
|
38
|
+
<paw> two </paw>
|
39
|
+
<paw>three</paw>
|
40
|
+
<paw>four</paw>
|
41
|
+
<cheezburger weight='2'>
|
42
|
+
</cheezburger>
|
43
|
+
<cub age='4'>
|
44
|
+
</cub>
|
45
|
+
</mypet>
|
46
|
+
EOS
|
47
|
+
@pet = Pet.parse(@xml_fragment)
|
48
|
+
end
|
49
|
+
|
50
|
+
should 'be a silly mouse' do
|
51
|
+
assert_equal 'silly mouse', @pet.species
|
52
|
+
end
|
53
|
+
|
54
|
+
should 'eat tigers and lions' do
|
55
|
+
assert_equal ['tigers', 'lions'], @pet.eats
|
56
|
+
end
|
57
|
+
|
58
|
+
should 'be 12 meters tall' do
|
59
|
+
assert_equal 12, @pet.height
|
60
|
+
end
|
61
|
+
|
62
|
+
should 'have tail' do
|
63
|
+
assert_equal true, @pet.has_tail
|
64
|
+
end
|
65
|
+
|
66
|
+
should 'have four paws' do
|
67
|
+
assert_not_nil @pet.paws
|
68
|
+
assert_equal 4, @pet.paws.length
|
69
|
+
assert_equal %w(one two three four), @pet.paws
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'should has cheezburger' do
|
73
|
+
setup do
|
74
|
+
assert_not_nil @burger = @pet.cheezburger
|
75
|
+
assert_kind_of Cheezburger, @pet.cheezburger
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'that' do
|
79
|
+
should 'weigh 2 kg' do
|
80
|
+
assert_equal 2, @burger.weight
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pipa-xmlnuts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Igor Gunko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-14 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: XmlNuts is an XML to ruby object and back again mapping library.
|
17
|
+
email: tekmon@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- MIT-LICENSE
|
25
|
+
files:
|
26
|
+
- README
|
27
|
+
- MIT-LICENSE
|
28
|
+
- lib/xmlnuts.rb
|
29
|
+
- lib/xmlnuts/nuts.rb
|
30
|
+
- lib/xmlnuts/mappings.rb
|
31
|
+
- lib/xmlnuts/converters.rb
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://github.com/pipa/xmlnuts
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options:
|
36
|
+
- --line-numbers
|
37
|
+
- --inline-source
|
38
|
+
- --main
|
39
|
+
- README
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.2.0
|
58
|
+
signing_key:
|
59
|
+
specification_version: 2
|
60
|
+
summary: Making xml<->ruby binding easy
|
61
|
+
test_files:
|
62
|
+
- test/parsing_test.rb
|