jordi-xml_struct 0.2.1 → 0.9.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/README.rdoc +152 -0
- data/WHATSNEW +10 -0
- data/lib/xml_struct/adapters/hpricot.rb +47 -0
- data/lib/xml_struct/adapters/rexml.rb +34 -0
- data/lib/xml_struct/array_notation.rb +44 -0
- data/lib/xml_struct/blankish_slate.rb +1 -1
- data/lib/xml_struct/collection_proxy.rb +7 -3
- data/lib/xml_struct/default_adapter.rb +15 -0
- data/lib/xml_struct/method_missing_dispatchers.rb +43 -0
- data/lib/xml_struct/string.rb +6 -2
- data/lib/xml_struct.rb +31 -52
- data/xml_struct.gemspec +38 -0
- metadata +17 -49
- data/README.markdown +0 -151
- data/Rakefile +0 -36
- data/lib/xml_struct/common_behaviours.rb +0 -53
- data/test/samples/lorem.xml +0 -63
- data/test/samples/recipe.xml +0 -16
- data/test/samples/weird_characters.xml +0 -2
- data/test/test_helper.rb +0 -39
- data/test/vendor/test-spec/README +0 -378
- data/test/vendor/test-spec/ROADMAP +0 -1
- data/test/vendor/test-spec/Rakefile +0 -146
- data/test/vendor/test-spec/SPECS +0 -161
- data/test/vendor/test-spec/TODO +0 -2
- data/test/vendor/test-spec/bin/specrb +0 -107
- data/test/vendor/test-spec/examples/stack.rb +0 -38
- data/test/vendor/test-spec/examples/stack_spec.rb +0 -119
- data/test/vendor/test-spec/lib/test/spec/dox.rb +0 -148
- data/test/vendor/test-spec/lib/test/spec/rdox.rb +0 -25
- data/test/vendor/test-spec/lib/test/spec/should-output.rb +0 -49
- data/test/vendor/test-spec/lib/test/spec/version.rb +0 -8
- data/test/vendor/test-spec/lib/test/spec.rb +0 -660
- data/test/vendor/test-spec/test/spec_dox.rb +0 -39
- data/test/vendor/test-spec/test/spec_flexmock.rb +0 -209
- data/test/vendor/test-spec/test/spec_mocha.rb +0 -104
- data/test/vendor/test-spec/test/spec_nestedcontexts.rb +0 -26
- data/test/vendor/test-spec/test/spec_new_style.rb +0 -80
- data/test/vendor/test-spec/test/spec_should-output.rb +0 -26
- data/test/vendor/test-spec/test/spec_testspec.rb +0 -699
- data/test/vendor/test-spec/test/spec_testspec_order.rb +0 -26
- data/test/vendor/test-spec/test/test_testunit.rb +0 -22
- data/test/xml_struct_test.rb +0 -185
data/README.rdoc
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
= XMLStruct
|
2
|
+
|
3
|
+
(This is inspired by Python's +xml_objectify+)
|
4
|
+
|
5
|
+
XMLStruct attempts to make the accessing of small, well-formed XML structures
|
6
|
+
convenient, by using dot notation to represent both attributes and child
|
7
|
+
elements whenever possible.
|
8
|
+
|
9
|
+
XML parsing libraries (in general) have interfaces that are useful when
|
10
|
+
one is using XML for its intended purpose, but cumbersome when one always
|
11
|
+
sends the same XML structure, and always process all of it in the same
|
12
|
+
way. This one aims to be a bit different.
|
13
|
+
|
14
|
+
== Example usage
|
15
|
+
|
16
|
+
<recipe name="bread" prep_time="5 mins" cook_time="3 hours">
|
17
|
+
<title>Basic bread</title>
|
18
|
+
<ingredient amount="8" unit="dL">Flour</ingredient>
|
19
|
+
<ingredient amount="10" unit="grams">Yeast</ingredient>
|
20
|
+
<ingredient amount="4" unit="dL" state="warm">Water</ingredient>
|
21
|
+
<ingredient amount="1" unit="teaspoon">Salt</ingredient>
|
22
|
+
<instructions easy="yes" hard="false">
|
23
|
+
<step>Mix all ingredients together.</step>
|
24
|
+
<step>Knead thoroughly.</step>
|
25
|
+
<step>Cover with a cloth, and leave for one hour in warm room.</step>
|
26
|
+
<step>Knead again.</step>
|
27
|
+
<step>Place in a bread baking tin.</step>
|
28
|
+
<step>Cover with a cloth, and leave for one hour in warm room.</step>
|
29
|
+
<step>Bake in the oven at 180(degrees)C for 30 minutes.</step>
|
30
|
+
</instructions>
|
31
|
+
</recipe>
|
32
|
+
|
33
|
+
require 'xml_struct'
|
34
|
+
recipe = XMLStruct.new io_with_recipe_xml_shown_above
|
35
|
+
|
36
|
+
recipe.name => "bread"
|
37
|
+
recipe.title => "Basic bread"
|
38
|
+
|
39
|
+
recipe.ingredients.is_a?(Array) => true
|
40
|
+
recipe.ingredients.first.amount => "8" # Not a Fixnum. Too hard. :(
|
41
|
+
|
42
|
+
recipe.instructions.easy? => true
|
43
|
+
|
44
|
+
recipe.instructions.first.upcase => "MIX ALL INGREDIENTS TOGETHER."
|
45
|
+
recipe.instructions.steps.size => 7
|
46
|
+
|
47
|
+
== Installation instructions
|
48
|
+
|
49
|
+
sudo gem install jordi-xml_struct --source http://gems.github.com
|
50
|
+
|
51
|
+
== Motivation
|
52
|
+
|
53
|
+
XML is an *extensible* markup language. It is extensible because it is
|
54
|
+
meant to define markup languages for *any* type of document, so new tags
|
55
|
+
are needed depending on the problem domain.
|
56
|
+
|
57
|
+
Sometimes, however, XML ends up being used to solve a much simpler problem:
|
58
|
+
the issue of passing a data-structure over the network, and/or between two
|
59
|
+
different languages. Tools like +JSON+ or +YAML+ are a much better fit for
|
60
|
+
this kind of job, but one doesn't always have that luxury.
|
61
|
+
|
62
|
+
== Caveats
|
63
|
+
|
64
|
+
The dot notation is used as follows. For the given file:
|
65
|
+
|
66
|
+
<outer id="root" name="foo">
|
67
|
+
<name>Outer Element</name>
|
68
|
+
</outer>
|
69
|
+
|
70
|
+
+outer.name+ is the +name+ *element*. Child elements are always looked up
|
71
|
+
first, then attributes. To access the attribute in the case of ambiguity,
|
72
|
+
use outer[:attr => 'name'].
|
73
|
+
|
74
|
+
+outer.id+ is really Object#id, because all of the object methods are
|
75
|
+
preserved (this is on purpose). To access the attribute +id+, use
|
76
|
+
outer[:attr => 'id'], or outer['id'] since there's no element/attribute
|
77
|
+
ambiguity.
|
78
|
+
|
79
|
+
== Features & Problems
|
80
|
+
|
81
|
+
=== Collection auto-folding
|
82
|
+
|
83
|
+
Similar to XmlSimple, XMLStruct folds same named elements at the same
|
84
|
+
level. For example:
|
85
|
+
|
86
|
+
<student>
|
87
|
+
<name>Bob</name>
|
88
|
+
<course>Math</course>
|
89
|
+
<course>Biology</course>
|
90
|
+
</student>
|
91
|
+
|
92
|
+
student = XMLStruct.new(xml_file)
|
93
|
+
|
94
|
+
student.course.is_a? Array => true
|
95
|
+
student.course.first == 'Math' => true
|
96
|
+
student.course.last == 'Biology => true
|
97
|
+
|
98
|
+
=== Collection pluralization
|
99
|
+
|
100
|
+
With the same file from the +Collection auto-folding+ section above, you
|
101
|
+
also get this (courtesy of +ActiveSupport+'s +Inflector+):
|
102
|
+
|
103
|
+
student.courses.first == student.course.first => true
|
104
|
+
|
105
|
+
=== Collection proxy
|
106
|
+
|
107
|
+
Sometimes, collections are expressed with a container element in XML:
|
108
|
+
|
109
|
+
<student>
|
110
|
+
<name>Bob</name>
|
111
|
+
<courses>
|
112
|
+
<course>Math</course>
|
113
|
+
<course>Biology</course>
|
114
|
+
</courses>
|
115
|
+
</student>
|
116
|
+
|
117
|
+
In this case, since the container element +courses+ has no text element
|
118
|
+
of its own, and it only has elements of one name under it, it delegates
|
119
|
+
all methods it doesn't contain to the collection below, so you get:
|
120
|
+
|
121
|
+
student.courses.collect { |c| c.downcase.to_sym } => [:math, :biology]
|
122
|
+
|
123
|
+
=== Question mark notation
|
124
|
+
|
125
|
+
Strings that look like booleans are "booleanized" if called by their
|
126
|
+
question mark names (such as +enabled?+)
|
127
|
+
|
128
|
+
=== Adapters
|
129
|
+
|
130
|
+
XMLStruct supports different adapters to do the actual XML parsing. It ships
|
131
|
+
with +REXML+ and +Hpricot+ adapters. If +Hpricot+ is detected it gets used,
|
132
|
+
otherwise +REXML+ is used as a fallback.
|
133
|
+
|
134
|
+
=== Recursive
|
135
|
+
|
136
|
+
The design of the adapters assumes parsing of the objects recursively. Deep
|
137
|
+
files are bound to throw +SystemStackError+, but for the kinds of files I
|
138
|
+
need to read, things are working fine so far. In any case, stream parsing
|
139
|
+
is on the TODO list.
|
140
|
+
|
141
|
+
=== Incomplete
|
142
|
+
|
143
|
+
It most likely doesn't work with a ton of features of complex XML files. I
|
144
|
+
will always try to accomodate those, as long as they don't make the basic
|
145
|
+
usage more complex. As usual, patches welcome.
|
146
|
+
|
147
|
+
== Legal
|
148
|
+
|
149
|
+
Copyright (c) 2008 Jordi Bunster, released under the MIT license
|
150
|
+
|
151
|
+
|
152
|
+
|
data/WHATSNEW
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
* 0.9.0 (2008-10-15):
|
2
|
+
- Added support for plug-able adapters
|
3
|
+
- Backported REXML code as an adapter, added Hpricot adapter
|
4
|
+
- Performance: XMLStruct now decorates objects lazily
|
5
|
+
- Performance: XMLStruct uses the Hpricot adapter if possible, otherwise
|
6
|
+
REXML as a fallback
|
7
|
+
- API Change: XMLStruct.new is mostly delegated to the adapter, and both
|
8
|
+
included adapters behave the same: a String is considered to be
|
9
|
+
XML data, anything else is probed for #read and then #to_s
|
10
|
+
|
1
11
|
* 0.2.1 (2008-10-13):
|
2
12
|
- Fixed a bug where attributes with dashes would crash the party
|
3
13
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module XMLStruct::Adapters::Hpricot
|
2
|
+
|
3
|
+
# Can take a String of XML data, or anything that responds to
|
4
|
+
# either +read+ or +to_s+.
|
5
|
+
def self.new(duck)
|
6
|
+
case
|
7
|
+
when duck.is_a?(::Hpricot::Elem) : Element.new(duck)
|
8
|
+
when duck.is_a?(::String) : new(::Hpricot::XML(duck).root)
|
9
|
+
when duck.respond_to?(:read) : new(duck.read)
|
10
|
+
when duck.respond_to?(:to_s) : new(duck.to_s)
|
11
|
+
else raise "Don't know how to deal with '#{duck.class}' object"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private ##################################################################
|
16
|
+
|
17
|
+
class Element # :nodoc:
|
18
|
+
attr_reader :raw, :name, :value, :attributes, :children
|
19
|
+
|
20
|
+
def text_value(raw)
|
21
|
+
raw.children.select do |e|
|
22
|
+
(e.class == ::Hpricot::Text) && !e.to_s.blank?
|
23
|
+
end.join.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def cdata_value(raw)
|
27
|
+
raw.children.select do |e|
|
28
|
+
(e.class == ::Hpricot::CData) && !e.to_s.blank?
|
29
|
+
end.first.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(xml)
|
33
|
+
@raw, @name, @attributes, @children = xml, xml.name, {}, []
|
34
|
+
|
35
|
+
@attributes = xml.attributes
|
36
|
+
xml.children.select { |e| e.elem? }.each do |e|
|
37
|
+
@children << self.class.new(e)
|
38
|
+
end
|
39
|
+
|
40
|
+
@value = case
|
41
|
+
when (not text_value(@raw).blank?) : text_value(@raw)
|
42
|
+
when (not cdata_value(@raw).blank?) : cdata_value(@raw)
|
43
|
+
else ''
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module XMLStruct::Adapters::REXML
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
# Can take a String of XML data, or anything that responds to
|
5
|
+
# either +read+ or +to_s+.
|
6
|
+
def self.new(duck)
|
7
|
+
case
|
8
|
+
when duck.is_a?(::REXML::Element) : Element.new(duck)
|
9
|
+
when duck.is_a?(::String) : new(::REXML::Document.new(duck).root)
|
10
|
+
when duck.respond_to?(:read) : new(duck.read)
|
11
|
+
when duck.respond_to?(:to_s) : new(duck.to_s)
|
12
|
+
else raise "Don't know how to deal with '#{duck.class}' object"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private ##################################################################
|
17
|
+
|
18
|
+
class Element # :nodoc:
|
19
|
+
attr_reader :raw, :name, :value, :attributes, :children
|
20
|
+
|
21
|
+
def initialize(xml)
|
22
|
+
@raw, @name, @attributes, @children = xml, xml.name, {}, []
|
23
|
+
|
24
|
+
@attributes = xml.attributes
|
25
|
+
xml.each_element { |e| @children << self.class.new(e) }
|
26
|
+
|
27
|
+
@value = case
|
28
|
+
when (not xml.text.blank?) : xml.text.to_s
|
29
|
+
when (xml.cdatas.size >= 1) : xml.cdatas.first.to_s
|
30
|
+
else ''
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module XMLStruct::ArrayNotation
|
2
|
+
# Array-bracket (+[]+) notation access to elements and attributes. Use
|
3
|
+
# when the element or attribute you need to reach is not reachable via dot
|
4
|
+
# notation (because it's not a valid method name, or because the method
|
5
|
+
# exists, such as +id+ or +class+).
|
6
|
+
#
|
7
|
+
# It also supports a hash key, which is used to reach attributes named
|
8
|
+
# the same as elements in the same depth level (which otherwise go first)
|
9
|
+
#
|
10
|
+
# All of this is a lot easier to explain by example:
|
11
|
+
#
|
12
|
+
# <article id="main_article" author="j-random">
|
13
|
+
# <author>J. Random Hacker</author>
|
14
|
+
# </article>
|
15
|
+
#
|
16
|
+
# article.id => 9314390 # Object#id
|
17
|
+
# article[:id] => "main_article" # id attribute
|
18
|
+
# article[:author] => "J. Random Hacker" # <author> element
|
19
|
+
# article[:attr => 'author'] => "j-random" # author attribute
|
20
|
+
#
|
21
|
+
# Valid keys for the hash notation in the example above are +:attr+,
|
22
|
+
# +:attribute+, +:child+, and +:element+.
|
23
|
+
def [](name)
|
24
|
+
return @__target[name] if @__target && name.is_a?(Numeric)
|
25
|
+
|
26
|
+
unless name.is_a? Hash
|
27
|
+
key = name.to_sym
|
28
|
+
|
29
|
+
return @__children[key] if @__children.has_key?(key)
|
30
|
+
return @__attributes[key] if @__attributes.has_key?(key)
|
31
|
+
end
|
32
|
+
|
33
|
+
raise 'one and only one key allowed' if name.size != 1
|
34
|
+
|
35
|
+
case (param = name.keys.first.to_sym)
|
36
|
+
when :element : @__children[name.values.first.to_sym]
|
37
|
+
when :child : @__children[name.values.first.to_sym]
|
38
|
+
when :attr : @__attributes[name.values.first.to_sym]
|
39
|
+
when :attribute : @__attributes[name.values.first.to_sym]
|
40
|
+
else raise %{ Invalid key :#{param.to_s}.
|
41
|
+
Use one of :element, :child, :attr, or :attribute }.squish!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,10 +1,14 @@
|
|
1
|
-
class XMLStruct::CollectionProxy < XMLStruct::BlankishSlate
|
1
|
+
class XMLStruct::CollectionProxy < XMLStruct::BlankishSlate # :nodoc:
|
2
2
|
def initialize(target)
|
3
3
|
@__children, @__attributes, @__target = {}, {}, target
|
4
4
|
end
|
5
5
|
|
6
|
+
private ##################################################################
|
7
|
+
|
6
8
|
def method_missing(m, *a, &b) # :nodoc:
|
7
|
-
|
8
|
-
|
9
|
+
dp = __question_dispatch(m, *a, &b)
|
10
|
+
dp = __dot_notation_dispatch(m, *a, &b) if dp.nil?
|
11
|
+
dp = @__target.__send__(m, *a, &b) if @__target.respond_to?(m) && dp.nil?
|
12
|
+
dp
|
9
13
|
end
|
10
14
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module XMLStruct # :nodoc:
|
2
|
+
module Adapters # :nodoc:
|
3
|
+
ADAPTERS_PATH = File.join(File.dirname(__FILE__), 'adapters')
|
4
|
+
|
5
|
+
Default = begin
|
6
|
+
require 'hpricot'
|
7
|
+
require File.join(ADAPTERS_PATH, 'hpricot')
|
8
|
+
Hpricot
|
9
|
+
rescue LoadError
|
10
|
+
require File.join(ADAPTERS_PATH, 'rexml')
|
11
|
+
REXML
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module XMLStruct::MethodMissingDispatchers # :nodoc:
|
2
|
+
|
3
|
+
private ##################################################################
|
4
|
+
|
5
|
+
def __question_dispatch(meth, *args, &block)
|
6
|
+
return unless meth.to_s.match(/\?$/) && args.empty? && block.nil?
|
7
|
+
|
8
|
+
method_sans_question = meth.to_s.chomp('?').to_sym
|
9
|
+
|
10
|
+
if boolish = __send__(method_sans_question).downcase
|
11
|
+
bool = case
|
12
|
+
when %w[ true yes t y ].include?(boolish) : true
|
13
|
+
when %w[ false no f n ].include?(boolish) : false
|
14
|
+
else nil
|
15
|
+
end
|
16
|
+
|
17
|
+
unless bool.nil? # Fun, eh?
|
18
|
+
instance_eval %{ def #{meth}; #{bool ? 'true' : 'false'}; end }
|
19
|
+
end
|
20
|
+
|
21
|
+
bool
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def __dot_notation_dispatch(meth, *args, &block)
|
26
|
+
return unless args.empty? && block.nil?
|
27
|
+
|
28
|
+
if @__children.has_key?(singular = meth.to_s.singularize.to_sym) &&
|
29
|
+
@__children[singular].is_a?(Array)
|
30
|
+
|
31
|
+
instance_eval %{ def #{meth}; @__children[%s|#{singular}|]; end }
|
32
|
+
@__children[singular]
|
33
|
+
|
34
|
+
elsif @__children.has_key?(meth)
|
35
|
+
instance_eval %{ def #{meth}; @__children[%s|#{meth}|]; end }
|
36
|
+
@__children[meth]
|
37
|
+
|
38
|
+
elsif @__attributes.has_key?(meth)
|
39
|
+
instance_eval %{ def #{meth}; @__attributes[%s|#{meth}|]; end }
|
40
|
+
@__attributes[meth]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/xml_struct/string.rb
CHANGED
@@ -9,7 +9,7 @@ module XMLStruct::String
|
|
9
9
|
# and returns accordingly. If not, just returns the string.
|
10
10
|
def rb
|
11
11
|
@__rb ||= case
|
12
|
-
when (self !~ /\S/) :
|
12
|
+
when (self !~ /\S/) : ''
|
13
13
|
when match(/[a-zA-Z]/) : ::String.new(self)
|
14
14
|
when match(/^[+-]?\d+$/) : self.to_i
|
15
15
|
when match(/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/) : self.to_f
|
@@ -25,7 +25,11 @@ module XMLStruct::String
|
|
25
25
|
(self !~ /\S/) && @__children.blank? && @__attributes.blank?
|
26
26
|
end
|
27
27
|
|
28
|
+
private ##################################################################
|
29
|
+
|
28
30
|
def method_missing(m, *a, &b) # :nodoc:
|
29
|
-
|
31
|
+
dp = __question_dispatch(m, *a, &b)
|
32
|
+
dp = __dot_notation_dispatch(m, *a, &b) if dp.nil?
|
33
|
+
dp
|
30
34
|
end
|
31
35
|
end
|
data/lib/xml_struct.rb
CHANGED
@@ -1,53 +1,59 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'activesupport'
|
3
|
-
require 'rexml/document'
|
4
3
|
|
5
|
-
module XMLStruct
|
4
|
+
module XMLStruct
|
5
|
+
|
6
|
+
unless defined?(BASE_DIR) # Slow call
|
7
|
+
BASE_DIR = File.join(File.dirname(__FILE__), 'xml_struct')
|
8
|
+
end
|
6
9
|
|
7
|
-
require File.join(
|
8
|
-
require File.join(
|
9
|
-
require File.join(
|
10
|
-
require File.join(
|
10
|
+
require File.join(BASE_DIR, 'default_adapter')
|
11
|
+
require File.join(BASE_DIR, 'method_missing_dispatchers')
|
12
|
+
require File.join(BASE_DIR, 'array_notation')
|
13
|
+
require File.join(BASE_DIR, 'blankish_slate')
|
14
|
+
require File.join(BASE_DIR, 'collection_proxy')
|
15
|
+
require File.join(BASE_DIR, 'string')
|
11
16
|
|
12
|
-
|
17
|
+
def self.adapter=(adapter_module)
|
18
|
+
@adapter = adapter_module
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.adapter
|
22
|
+
@adapter ||= Adapters::Default
|
23
|
+
end
|
13
24
|
|
14
25
|
# Returns a String or Array object representing the given XML, decorated
|
15
26
|
# with methods to access attributes and/or child elements.
|
16
27
|
def self.new(duck)
|
17
28
|
case duck
|
18
|
-
when ::
|
19
|
-
when
|
20
|
-
|
21
|
-
when REXML::Elements : return duck.map { |dee| new_decorated_obj(dee) }
|
22
|
-
else raise "Don't know how to start from '#{duck.class}' object."
|
29
|
+
when adapter::Element : new_decorated_obj(duck)
|
30
|
+
when Array : duck.map { |d| new_decorated_obj(d) }
|
31
|
+
else new adapter.new(duck)
|
23
32
|
end
|
24
33
|
end
|
25
34
|
|
26
|
-
|
35
|
+
private ##################################################################
|
36
|
+
|
37
|
+
# Takes any Element object, and converts it recursively into
|
27
38
|
# the corresponding tree of decorated objects.
|
28
39
|
def self.new_decorated_obj(xml)
|
29
|
-
obj = if xml.
|
30
|
-
xml.
|
40
|
+
obj = if xml.value.blank? &&
|
41
|
+
xml.children.collect { |e| e.name }.uniq.size == 1
|
31
42
|
|
32
|
-
CollectionProxy.new new(xml.
|
43
|
+
CollectionProxy.new new(xml.children)
|
33
44
|
else
|
34
|
-
|
35
|
-
when (not xml.text.blank?) : xml.text.to_s
|
36
|
-
when (xml.cdatas.size >= 1) : xml.cdatas.first.to_s
|
37
|
-
else ''
|
38
|
-
end.extend String
|
45
|
+
xml.value.extend String # Teach our string to behave like XML
|
39
46
|
end
|
40
47
|
|
41
48
|
obj.instance_variable_set :@__raw_xml, xml
|
42
49
|
|
43
|
-
xml.
|
50
|
+
xml.children.each { |child| add_child(obj, child.name, new(child)) }
|
44
51
|
xml.attributes.each { |name, value| add_attribute(obj, name, value) }
|
45
52
|
|
46
|
-
|
53
|
+
# Let's teach our object some new tricks:
|
54
|
+
obj.extend(ArrayNotation).extend(MethodMissingDispatchers)
|
47
55
|
end
|
48
56
|
|
49
|
-
private ##################################################################
|
50
|
-
|
51
57
|
# Decorates the given object 'obj' with a method 'name' that returns the
|
52
58
|
# given 'element'. If 'name' is already taken, takes care of the array
|
53
59
|
# folding behaviour.
|
@@ -57,27 +63,9 @@ module XMLStruct
|
|
57
63
|
|
58
64
|
children[key] = if children[key]
|
59
65
|
|
60
|
-
unless obj.respond_to?((plural_key = key.to_s.pluralize).to_sym)
|
61
|
-
begin
|
62
|
-
obj.instance_eval %{
|
63
|
-
def #{plural_key}; @__children[%s|#{key.to_s}|]; end }
|
64
|
-
rescue SyntaxError
|
65
|
-
nil
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
66
|
children[key] = [ children[key] ] unless children[key].is_a? Array
|
70
67
|
children[key] << element
|
71
68
|
else
|
72
|
-
unless obj.respond_to? key
|
73
|
-
begin
|
74
|
-
obj.instance_eval %{
|
75
|
-
def #{key.to_s}; @__children[%s|#{key.to_s}|]; end }
|
76
|
-
rescue SyntaxError
|
77
|
-
nil
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
69
|
element
|
82
70
|
end
|
83
71
|
|
@@ -92,15 +80,6 @@ module XMLStruct
|
|
92
80
|
attributes = obj.instance_variable_get :@__attributes
|
93
81
|
attributes[(key = name.to_sym)] = attr_value.squish.extend String
|
94
82
|
|
95
|
-
unless obj.respond_to? key
|
96
|
-
begin
|
97
|
-
obj.instance_eval %{
|
98
|
-
def #{key.to_s}; @__attributes[%s|#{key.to_s}|]; end }
|
99
|
-
rescue SyntaxError
|
100
|
-
nil
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
83
|
obj.instance_variable_set :@__attributes, attributes
|
105
84
|
attr_value
|
106
85
|
end
|