pipa-xmlnuts 0.0.2
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/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
|