jordi-xml_struct 0.2.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|