jordi-xml-object 0.9.5
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 +19 -0
- data/README.rdoc +152 -0
- data/TODO +3 -0
- data/WHATSNEW +38 -0
- data/lib/jordi-xml-object.rb +1 -0
- data/lib/xml-object/adapters/hpricot.rb +47 -0
- data/lib/xml-object/adapters/rexml.rb +34 -0
- data/lib/xml-object/array_notation.rb +44 -0
- data/lib/xml-object/blankish_slate.rb +9 -0
- data/lib/xml-object/collection_proxy.rb +14 -0
- data/lib/xml-object/default_adapter.rb +15 -0
- data/lib/xml-object/method_missing_dispatchers.rb +43 -0
- data/lib/xml-object/string.rb +35 -0
- data/lib/xml-object.rb +86 -0
- data/xml-object.gemspec +43 -0
- metadata +82 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008 Jordi Bunster <jordi@bunster.org>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
= XMLObject
|
2
|
+
|
3
|
+
(This is inspired by Python's +xml_objectify+)
|
4
|
+
|
5
|
+
XMLObject 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-object'
|
34
|
+
recipe = XMLObject.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-object --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, XMLObject 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 = XMLObject.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
|
+
XMLObject 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/TODO
ADDED
data/WHATSNEW
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
* 0.9.5 (2008-10-15):
|
2
|
+
- Project renamed to XMLObject, to match project name at Rubyforge.
|
3
|
+
The other names were taken. :(
|
4
|
+
|
5
|
+
* 0.9.0 (2008-10-15):
|
6
|
+
- Added support for plug-able adapters
|
7
|
+
- Backported REXML code as an adapter, added Hpricot adapter
|
8
|
+
- Performance: XMLStruct now decorates objects lazily
|
9
|
+
- Performance: XMLStruct uses the Hpricot adapter if possible, otherwise
|
10
|
+
REXML as a fallback
|
11
|
+
- API Change: XMLStruct.new is mostly delegated to the adapter, and both
|
12
|
+
included adapters behave the same: a String is considered to be
|
13
|
+
XML data, anything else is probed for #read and then #to_s
|
14
|
+
|
15
|
+
* 0.2.1 (2008-10-13):
|
16
|
+
- Fixed a bug where attributes with dashes would crash the party
|
17
|
+
|
18
|
+
* 0.2.0 (2008-10-13):
|
19
|
+
- Broke backwards compatibility
|
20
|
+
- XMLStruct.new now returns decorated String or Array objects, so that
|
21
|
+
access to elements, attributes, and "collection" values is consistent
|
22
|
+
- While Strings are no longer auto-typecast to float or int, they now
|
23
|
+
have, whenever possible, a question-mark form, which attemps to
|
24
|
+
returns booleans for strings like "Yes" and "false"
|
25
|
+
- XMLStruct.new can now take a filename or a file object
|
26
|
+
- Added more tests
|
27
|
+
|
28
|
+
* 0.1.3 (2008-10-10):
|
29
|
+
- Switched tests to use test/spec
|
30
|
+
- Added XMLStruct#to_obj to return the corresponding Ruby object value
|
31
|
+
- Added XMLStruct#to_raw_xml to return the REXML object
|
32
|
+
- Added documentation on auto-typecasting behaviour caveat
|
33
|
+
|
34
|
+
* 0.1.2 (2008-10-10):
|
35
|
+
- Documentation
|
36
|
+
|
37
|
+
* 0.1.1 (2008-10-10):
|
38
|
+
- First "release" ;)
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'xml-object'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module XMLObject::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 XMLObject::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 XMLObject::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
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class XMLObject::CollectionProxy < XMLObject::BlankishSlate # :nodoc:
|
2
|
+
def initialize(target)
|
3
|
+
@__children, @__attributes, @__target = {}, {}, target
|
4
|
+
end
|
5
|
+
|
6
|
+
private ##################################################################
|
7
|
+
|
8
|
+
def method_missing(m, *a, &b) # :nodoc:
|
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
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module XMLObject # :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 XMLObject::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
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module XMLObject::String
|
2
|
+
def self.extended(obj) # :nodoc:
|
3
|
+
obj.instance_variable_set :@__children, {}
|
4
|
+
obj.instance_variable_set :@__attributes, {}
|
5
|
+
obj
|
6
|
+
end
|
7
|
+
|
8
|
+
# Attempts to detect wether this String is really an integer or float,
|
9
|
+
# and returns accordingly. If not, just returns the string.
|
10
|
+
def rb
|
11
|
+
@__rb ||= case
|
12
|
+
when (self !~ /\S/) : ''
|
13
|
+
when match(/[a-zA-Z]/) : ::String.new(self)
|
14
|
+
when match(/^[+-]?\d+$/) : self.to_i
|
15
|
+
when match(/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/) : self.to_f
|
16
|
+
else ::String.new(self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# A decorated String is blank when it has a blank value, no child
|
21
|
+
# elements, and no attributes. For example:
|
22
|
+
#
|
23
|
+
# <blank_element></blank_element>
|
24
|
+
def blank?
|
25
|
+
(self !~ /\S/) && @__children.blank? && @__attributes.blank?
|
26
|
+
end
|
27
|
+
|
28
|
+
private ##################################################################
|
29
|
+
|
30
|
+
def method_missing(m, *a, &b) # :nodoc:
|
31
|
+
dp = __question_dispatch(m, *a, &b)
|
32
|
+
dp = __dot_notation_dispatch(m, *a, &b) if dp.nil?
|
33
|
+
dp
|
34
|
+
end
|
35
|
+
end
|
data/lib/xml-object.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'activesupport'
|
3
|
+
|
4
|
+
module XMLObject
|
5
|
+
|
6
|
+
unless defined?(BASE_DIR) # Slow call
|
7
|
+
BASE_DIR = File.join(File.dirname(__FILE__), 'xml-object')
|
8
|
+
end
|
9
|
+
|
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')
|
16
|
+
|
17
|
+
def self.adapter=(adapter_module)
|
18
|
+
@adapter = adapter_module
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.adapter
|
22
|
+
@adapter ||= Adapters::Default
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a String or Array object representing the given XML, decorated
|
26
|
+
# with methods to access attributes and/or child elements.
|
27
|
+
def self.new(duck)
|
28
|
+
case duck
|
29
|
+
when adapter::Element : new_decorated_obj(duck)
|
30
|
+
when Array : duck.map { |d| new_decorated_obj(d) }
|
31
|
+
else new adapter.new(duck)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private ##################################################################
|
36
|
+
|
37
|
+
# Takes any Element object, and converts it recursively into
|
38
|
+
# the corresponding tree of decorated objects.
|
39
|
+
def self.new_decorated_obj(xml)
|
40
|
+
obj = if xml.value.blank? &&
|
41
|
+
xml.children.collect { |e| e.name }.uniq.size == 1
|
42
|
+
|
43
|
+
CollectionProxy.new new(xml.children)
|
44
|
+
else
|
45
|
+
xml.value.extend String # Teach our string to behave like XML
|
46
|
+
end
|
47
|
+
|
48
|
+
obj.instance_variable_set :@__raw_xml, xml
|
49
|
+
|
50
|
+
xml.children.each { |child| add_child(obj, child.name, new(child)) }
|
51
|
+
xml.attributes.each { |name, value| add_attribute(obj, name, value) }
|
52
|
+
|
53
|
+
# Let's teach our object some new tricks:
|
54
|
+
obj.extend(ArrayNotation).extend(MethodMissingDispatchers)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Decorates the given object 'obj' with a method 'name' that returns the
|
58
|
+
# given 'element'. If 'name' is already taken, takes care of the array
|
59
|
+
# folding behaviour.
|
60
|
+
def self.add_child(obj, name, element)
|
61
|
+
key = name.to_sym
|
62
|
+
children = obj.instance_variable_get :@__children
|
63
|
+
|
64
|
+
children[key] = if children[key]
|
65
|
+
|
66
|
+
children[key] = [ children[key] ] unless children[key].is_a? Array
|
67
|
+
children[key] << element
|
68
|
+
else
|
69
|
+
element
|
70
|
+
end
|
71
|
+
|
72
|
+
obj.instance_variable_set :@__children, children
|
73
|
+
element
|
74
|
+
end
|
75
|
+
|
76
|
+
# Decorates the given object 'obj' with a method 'name' that returns the
|
77
|
+
# given 'attr_value'.
|
78
|
+
def self.add_attribute(obj, name, attr_value) # :nodoc:
|
79
|
+
|
80
|
+
attributes = obj.instance_variable_get :@__attributes
|
81
|
+
attributes[(key = name.to_sym)] = attr_value.squish.extend String
|
82
|
+
|
83
|
+
obj.instance_variable_set :@__attributes, attributes
|
84
|
+
attr_value
|
85
|
+
end
|
86
|
+
end
|
data/xml-object.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.add_dependency 'activesupport'
|
3
|
+
|
4
|
+
gem.name = 'xml-object'
|
5
|
+
gem.version = '0.9.5'
|
6
|
+
gem.date = '2008-10-15'
|
7
|
+
|
8
|
+
gem.author = 'Jordi Bunster'
|
9
|
+
gem.email = 'jordi@bunster.org'
|
10
|
+
gem.homepage = 'http://github.com/jordi/xml-object'
|
11
|
+
|
12
|
+
gem.summary = "The Rubyista's way to do quick XML sit-ups"
|
13
|
+
gem.description = %{ XMLObject is a library for reading (not writing) XML.
|
14
|
+
It is particularly suited for cases where one is dealing with small
|
15
|
+
documents of a known structure. While not devoid of caveats, it does
|
16
|
+
have a very pleasant, idiomatic Ruby syntax. }.strip!.gsub! /\s+/, ' '
|
17
|
+
|
18
|
+
gem.files = %w[
|
19
|
+
MIT-LICENSE
|
20
|
+
README.rdoc
|
21
|
+
TODO
|
22
|
+
WHATSNEW
|
23
|
+
lib
|
24
|
+
lib/jordi-xml-object.rb
|
25
|
+
lib/xml-object
|
26
|
+
lib/xml-object/adapters
|
27
|
+
lib/xml-object/adapters/hpricot.rb
|
28
|
+
lib/xml-object/adapters/rexml.rb
|
29
|
+
lib/xml-object/array_notation.rb
|
30
|
+
lib/xml-object/blankish_slate.rb
|
31
|
+
lib/xml-object/collection_proxy.rb
|
32
|
+
lib/xml-object/default_adapter.rb
|
33
|
+
lib/xml-object/method_missing_dispatchers.rb
|
34
|
+
lib/xml-object/string.rb
|
35
|
+
lib/xml-object.rb
|
36
|
+
xml-object.gemspec
|
37
|
+
]
|
38
|
+
|
39
|
+
gem.has_rdoc = !!(gem.extra_rdoc_files = %w[ README.rdoc ])
|
40
|
+
gem.rdoc_options << '--title' << 'XMLObject' <<
|
41
|
+
'--main' << 'README.rdoc' <<
|
42
|
+
'--inline-source'
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jordi-xml-object
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jordi Bunster
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-15 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
description: XMLObject is a library for reading (not writing) XML. It is particularly suited for cases where one is dealing with small documents of a known structure. While not devoid of caveats, it does have a very pleasant, idiomatic Ruby syntax.
|
25
|
+
email: jordi@bunster.org
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README.rdoc
|
32
|
+
files:
|
33
|
+
- MIT-LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
- TODO
|
36
|
+
- WHATSNEW
|
37
|
+
- lib
|
38
|
+
- lib/jordi-xml-object.rb
|
39
|
+
- lib/xml-object
|
40
|
+
- lib/xml-object/adapters
|
41
|
+
- lib/xml-object/adapters/hpricot.rb
|
42
|
+
- lib/xml-object/adapters/rexml.rb
|
43
|
+
- lib/xml-object/array_notation.rb
|
44
|
+
- lib/xml-object/blankish_slate.rb
|
45
|
+
- lib/xml-object/collection_proxy.rb
|
46
|
+
- lib/xml-object/default_adapter.rb
|
47
|
+
- lib/xml-object/method_missing_dispatchers.rb
|
48
|
+
- lib/xml-object/string.rb
|
49
|
+
- lib/xml-object.rb
|
50
|
+
- xml-object.gemspec
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/jordi/xml-object
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --title
|
56
|
+
- XMLObject
|
57
|
+
- --main
|
58
|
+
- README.rdoc
|
59
|
+
- --inline-source
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.2.0
|
78
|
+
signing_key:
|
79
|
+
specification_version: 2
|
80
|
+
summary: The Rubyista's way to do quick XML sit-ups
|
81
|
+
test_files: []
|
82
|
+
|