xml-object 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +19 -0
- data/README.rdoc +176 -0
- data/TODO +1 -0
- data/WHATSNEW +74 -0
- data/lib/jordi-xml-object.rb +1 -0
- data/lib/xml-object.rb +79 -0
- data/lib/xml-object/adapters.rb +30 -0
- data/lib/xml-object/adapters/hpricot.rb +41 -0
- data/lib/xml-object/adapters/jrexml.rb +10 -0
- data/lib/xml-object/adapters/libxml.rb +38 -0
- data/lib/xml-object/adapters/rexml.rb +40 -0
- data/lib/xml-object/array_notation.rb +44 -0
- data/lib/xml-object/blankish_slate.rb +10 -0
- data/lib/xml-object/collection_proxy.rb +14 -0
- data/lib/xml-object/core_ext.rb +3 -0
- data/lib/xml-object/core_ext/hash.rb +5 -0
- data/lib/xml-object/core_ext/nil_class.rb +5 -0
- data/lib/xml-object/core_ext/string.rb +19 -0
- data/lib/xml-object/element.rb +21 -0
- data/lib/xml-object/method_missing_dispatchers.rb +43 -0
- data/lib/xml-object/string.rb +24 -0
- data/xml-object.gemspec +50 -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,176 @@
|
|
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 one
|
10
|
+
is using XML for its intended purpose, but cumbersome when one always sends
|
11
|
+
the same XML structure, and always process all of it in the same way. This
|
12
|
+
one aims to be a bit different.
|
13
|
+
|
14
|
+
At the moment, I aim for compatibility with Ruby 1.8, 1.9, and JRuby 1.1.
|
15
|
+
|
16
|
+
== Dependencies
|
17
|
+
|
18
|
+
None outside of Ruby, though some optional gems add additional features. See
|
19
|
+
below on "Adapters" and "Collection pluralization".
|
20
|
+
|
21
|
+
== Installation instructions
|
22
|
+
|
23
|
+
gem install xml-object
|
24
|
+
|
25
|
+
Or from Github's gem server:
|
26
|
+
|
27
|
+
gem install jordi-xml-object --source http://gems.github.com
|
28
|
+
|
29
|
+
Both are the same, and are loaded the same way:
|
30
|
+
|
31
|
+
require 'xml-object'
|
32
|
+
|
33
|
+
== Example usage
|
34
|
+
|
35
|
+
<recipe name="bread" prep_time="5 mins" cook_time="3 hours">
|
36
|
+
<title>Basic bread</title>
|
37
|
+
<ingredient amount="8" unit="dL">Flour</ingredient>
|
38
|
+
<ingredient amount="10" unit="grams">Yeast</ingredient>
|
39
|
+
<ingredient amount="4" unit="dL" state="warm">Water</ingredient>
|
40
|
+
<ingredient amount="1" unit="teaspoon">Salt</ingredient>
|
41
|
+
<instructions easy="yes" hard="false">
|
42
|
+
<step>Mix all ingredients together.</step>
|
43
|
+
<step>Knead thoroughly.</step>
|
44
|
+
<step>Cover with a cloth, and leave for one hour in warm room.</step>
|
45
|
+
<step>Knead again.</step>
|
46
|
+
<step>Place in a bread baking tin.</step>
|
47
|
+
<step>Cover with a cloth, and leave for one hour in warm room.</step>
|
48
|
+
<step>Bake in the oven at 180(degrees)C for 30 minutes.</step>
|
49
|
+
</instructions>
|
50
|
+
</recipe>
|
51
|
+
|
52
|
+
require 'xml-object'
|
53
|
+
recipe = XMLObject.new io_with_recipe_xml_shown_above
|
54
|
+
|
55
|
+
recipe.name => "bread"
|
56
|
+
recipe.title => "Basic bread"
|
57
|
+
|
58
|
+
recipe.ingredients.is_a?(Array) => true
|
59
|
+
recipe.ingredients.first.amount => "8" # Not a Fixnum. Too hard. :(
|
60
|
+
|
61
|
+
recipe.instructions.easy? => true
|
62
|
+
|
63
|
+
recipe.instructions.first.upcase => "MIX ALL INGREDIENTS TOGETHER."
|
64
|
+
recipe.instructions.steps.size => 7
|
65
|
+
|
66
|
+
== Motivation
|
67
|
+
|
68
|
+
XML is an *extensible* markup language. It is extensible because it is meant
|
69
|
+
to define markup languages for *any* type of document, so new tags are needed
|
70
|
+
depending on the problem domain.
|
71
|
+
|
72
|
+
Sometimes, however, XML ends up being used to solve a much simpler problem:
|
73
|
+
the issue of passing a data-structure over the network, and/or between two
|
74
|
+
different languages. Tools like +JSON+ or +YAML+ are a much better fit for
|
75
|
+
this kind of job, but one doesn't always have that luxury.
|
76
|
+
|
77
|
+
== Caveats
|
78
|
+
|
79
|
+
The dot notation is used as follows. For the given file:
|
80
|
+
|
81
|
+
<outer id="root" name="foo">
|
82
|
+
<name>Outer Element</name>
|
83
|
+
</outer>
|
84
|
+
|
85
|
+
+outer.name+ is the +name+ *element*. Child elements are always looked up
|
86
|
+
first, then attributes. To access the attribute in the case of ambiguity, use
|
87
|
+
outer[:attr => 'name'].
|
88
|
+
|
89
|
+
+outer.id+ is really Object#id, because all of the object methods are
|
90
|
+
preserved (this is on purpose). To access the attribute +id+, use
|
91
|
+
outer[:attr => 'id'], or outer['id'] since there's no element/attribute
|
92
|
+
ambiguity.
|
93
|
+
|
94
|
+
== Features & Problems
|
95
|
+
|
96
|
+
=== Adapters
|
97
|
+
|
98
|
+
XMLObject supports different adapters to do the actual XML parsing. It ships
|
99
|
+
with +REXML+, +Hpricot+, +JREXML+, and +LibXML+ adapters. By default, the
|
100
|
+
+REXML+ adapter is used.
|
101
|
+
|
102
|
+
To use a different adapter than the +REXML+ default:
|
103
|
+
|
104
|
+
require 'xml-object' # Require XMLObject first
|
105
|
+
require 'xml-object/adapters/hpricot' # (Under MRI or JRuby)
|
106
|
+
require 'xml-object/adapters/libxml' # (Under MRI only)
|
107
|
+
require 'xml-object/adapters/jrexml' # (Under Jruby only)
|
108
|
+
|
109
|
+
=== Collection auto-folding
|
110
|
+
|
111
|
+
Similar to XmlSimple, XMLObject folds same named elements at the same level.
|
112
|
+
For example:
|
113
|
+
|
114
|
+
<student>
|
115
|
+
<name>Bob</name>
|
116
|
+
<course>Math</course>
|
117
|
+
<course>Biology</course>
|
118
|
+
</student>
|
119
|
+
|
120
|
+
student = XMLObject.new(xml_file)
|
121
|
+
|
122
|
+
student.course.is_a? Array => true
|
123
|
+
student.course.first == 'Math' => true
|
124
|
+
student.course.last == 'Biology => true
|
125
|
+
|
126
|
+
=== Collection pluralization
|
127
|
+
|
128
|
+
With the same file from the +Collection auto-folding+ section above, you also
|
129
|
+
get this:
|
130
|
+
|
131
|
+
student.courses.first == student.course.first => true
|
132
|
+
|
133
|
+
Note that the pluralization algorithm is just tacking an 's' at the end of
|
134
|
+
the singular, unless +ActiveSupport+ is installed, in which case you get
|
135
|
+
irregular plurals, as well as the ability to teach the +Inflector+ about
|
136
|
+
new ones.
|
137
|
+
|
138
|
+
=== Collection proxy
|
139
|
+
|
140
|
+
Sometimes, collections are expressed with a container element in XML:
|
141
|
+
|
142
|
+
<student>
|
143
|
+
<name>Bob</name>
|
144
|
+
<courses>
|
145
|
+
<course>Math</course>
|
146
|
+
<course>Biology</course>
|
147
|
+
</courses>
|
148
|
+
</student>
|
149
|
+
|
150
|
+
In this case, since the container element +courses+ has no text element of
|
151
|
+
its own, and it only has elements of one name under it, it delegates all
|
152
|
+
methods it doesn't contain to the collection below, so you get:
|
153
|
+
|
154
|
+
student.courses.collect { |c| c.downcase.to_sym } => [:math, :biology]
|
155
|
+
|
156
|
+
=== Question mark notation
|
157
|
+
|
158
|
+
Strings that look like booleans are "booleanized" if called by their question
|
159
|
+
mark names (such as +enabled?+)
|
160
|
+
|
161
|
+
=== Recursive
|
162
|
+
|
163
|
+
The design of the adapters assumes parsing of the objects recursively. Deep
|
164
|
+
files are bound to throw +SystemStackError+, but for the kinds of files I
|
165
|
+
need to read, things are working fine so far. In any case, stream parsing is
|
166
|
+
on the TODO list.
|
167
|
+
|
168
|
+
=== Incomplete
|
169
|
+
|
170
|
+
It most likely doesn't work with a ton of features of complex XML files. I'll
|
171
|
+
always try to accomodate those, as long as they don't make the basic usage
|
172
|
+
more complex. As usual, patches welcome.
|
173
|
+
|
174
|
+
== Legal
|
175
|
+
|
176
|
+
Copyright (c) 2008 Jordi Bunster, released under the MIT license
|
data/WHATSNEW
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
* 0.9.7 (2008-10-20):
|
2
|
+
- LibXML adapter added
|
3
|
+
- JREXML "adapter" added, as well as JREXML support during tests and
|
4
|
+
benchmark tasks, but only when running under JRuby
|
5
|
+
- Backwards incompatible change: Elements with no text and multiple CDATA
|
6
|
+
values no longer report their value as their first CDATA element. They
|
7
|
+
report all of their CDATA nodes joined together, a-la LibXML
|
8
|
+
|
9
|
+
* 0.9.6 (2008-10-19):
|
10
|
+
- Ruby 1.9 compatibility
|
11
|
+
- XMLObject no longer requires ActiveSupport. Note that proper array
|
12
|
+
pluralization still depends on ActiveSupport's Inflector, so if it's not
|
13
|
+
there, we just pluralize things by tacking 's' at the end.
|
14
|
+
- XMLObject no longer tries to load Hpricot by default (too cumbersome
|
15
|
+
to test). Use "require 'xml-object/adapters/hpricot'" to enable the
|
16
|
+
Hpricot adapter.
|
17
|
+
- Backwards incompatible change. For the following XML:
|
18
|
+
|
19
|
+
<foo>
|
20
|
+
<bar>
|
21
|
+
<bez>
|
22
|
+
</foo>
|
23
|
+
|
24
|
+
<foos>Yipes!</foos>
|
25
|
+
|
26
|
+
before:
|
27
|
+
|
28
|
+
xml.foos => # The <foo> "array"
|
29
|
+
|
30
|
+
now:
|
31
|
+
|
32
|
+
xml.foos => # The <foos> element
|
33
|
+
|
34
|
+
It proved too expensive to support that edge case, and for no good
|
35
|
+
reason.
|
36
|
+
|
37
|
+
* 0.9.5 (2008-10-15):
|
38
|
+
- Project renamed to XMLObject, to match project name at Rubyforge.
|
39
|
+
The other names were taken. :(
|
40
|
+
|
41
|
+
* 0.9.0 (2008-10-15):
|
42
|
+
- Added support for plug-able adapters
|
43
|
+
- Backported REXML code as an adapter, added Hpricot adapter
|
44
|
+
- Performance: XMLStruct now decorates objects lazily
|
45
|
+
- Performance: XMLStruct uses the Hpricot adapter if possible, otherwise
|
46
|
+
REXML as a fallback
|
47
|
+
- API Change: XMLStruct.new is mostly delegated to the adapter, and both
|
48
|
+
included adapters behave the same: a String is considered to be
|
49
|
+
XML data, anything else is probed for #read and then #to_s
|
50
|
+
|
51
|
+
* 0.2.1 (2008-10-13):
|
52
|
+
- Fixed a bug where attributes with dashes would crash the party
|
53
|
+
|
54
|
+
* 0.2.0 (2008-10-13):
|
55
|
+
- Broke backwards compatibility
|
56
|
+
- XMLStruct.new now returns decorated String or Array objects, so that
|
57
|
+
access to elements, attributes, and "collection" values is consistent
|
58
|
+
- While Strings are no longer auto-typecast to float or int, they now
|
59
|
+
have, whenever possible, a question-mark form, which attemps to
|
60
|
+
returns booleans for strings like "Yes" and "false"
|
61
|
+
- XMLStruct.new can now take a filename or a file object
|
62
|
+
- Added more tests
|
63
|
+
|
64
|
+
* 0.1.3 (2008-10-10):
|
65
|
+
- Switched tests to use test/spec
|
66
|
+
- Added XMLStruct#to_obj to return the corresponding Ruby object value
|
67
|
+
- Added XMLStruct#to_raw_xml to return the REXML object
|
68
|
+
- Added documentation on auto-typecasting behaviour caveat
|
69
|
+
|
70
|
+
* 0.1.2 (2008-10-10):
|
71
|
+
- Documentation
|
72
|
+
|
73
|
+
* 0.1.1 (2008-10-10):
|
74
|
+
- First "release" ;)
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'xml-object'))
|
data/lib/xml-object.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
begin; require 'rubygems'; rescue Exception, StandardError; nil; end
|
2
|
+
begin; require 'activesupport'; rescue Exception, StandardError; nil; end
|
3
|
+
|
4
|
+
$:.unshift File.join(File.dirname(__FILE__), 'xml-object')
|
5
|
+
|
6
|
+
require 'core_ext'
|
7
|
+
require 'adapters'
|
8
|
+
require 'adapters/rexml'
|
9
|
+
require 'array_notation'
|
10
|
+
require 'blankish_slate'
|
11
|
+
require 'collection_proxy'
|
12
|
+
require 'element'
|
13
|
+
require 'method_missing_dispatchers'
|
14
|
+
require 'string'
|
15
|
+
|
16
|
+
module XMLObject
|
17
|
+
# Returns a String or Array object representing the given XML, decorated
|
18
|
+
# with methods to access attributes and/or child elements.
|
19
|
+
def self.new(duck)
|
20
|
+
case duck
|
21
|
+
when adapter::Element then new_decorated_obj(duck)
|
22
|
+
when Array then duck.map { |d| new_decorated_obj(d) }
|
23
|
+
else new adapter.new(duck)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private ##################################################################
|
28
|
+
|
29
|
+
# Takes any Element object, and converts it recursively into
|
30
|
+
# the corresponding tree of decorated objects.
|
31
|
+
def self.new_decorated_obj(xml) # :nodoc:
|
32
|
+
obj = if xml.value.blank? &&
|
33
|
+
xml.children.collect { |e| e.name }.uniq.size == 1
|
34
|
+
|
35
|
+
CollectionProxy.new new(xml.children)
|
36
|
+
else
|
37
|
+
# Teach our string to behave like and XML Element
|
38
|
+
xml.value.extend String, Element
|
39
|
+
end
|
40
|
+
|
41
|
+
obj.instance_variable_set :@__adapted_element, xml
|
42
|
+
|
43
|
+
xml.children.each { |child| add_child(obj, child.name, new(child)) }
|
44
|
+
xml.attributes.each { |name, value| add_attribute(obj, name, value) }
|
45
|
+
|
46
|
+
# Let's teach our object some new tricks:
|
47
|
+
obj.extend ArrayNotation, MethodMissingDispatchers
|
48
|
+
end
|
49
|
+
|
50
|
+
# Decorates the given object 'obj' with a method 'name' that returns the
|
51
|
+
# given 'element'. If 'name' is already taken, takes care of the array
|
52
|
+
# folding behaviour.
|
53
|
+
def self.add_child(obj, name, element) # :nodoc:
|
54
|
+
key = name.to_sym
|
55
|
+
children = obj.instance_variable_get :@__children
|
56
|
+
|
57
|
+
children[key] = if children[key]
|
58
|
+
|
59
|
+
children[key] = [ children[key] ] unless children[key].is_a? Array
|
60
|
+
children[key] << element
|
61
|
+
else
|
62
|
+
element
|
63
|
+
end
|
64
|
+
|
65
|
+
obj.instance_variable_set :@__children, children
|
66
|
+
element
|
67
|
+
end
|
68
|
+
|
69
|
+
# Decorates the given object 'obj' with a method 'name' that returns the
|
70
|
+
# given 'attr_value'.
|
71
|
+
def self.add_attribute(obj, name, attr_value) # :nodoc:
|
72
|
+
|
73
|
+
attributes = obj.instance_variable_get :@__attributes
|
74
|
+
attributes[(key = name.to_sym)] = attr_value.squish.extend String
|
75
|
+
|
76
|
+
obj.instance_variable_set :@__attributes, attributes
|
77
|
+
attr_value
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module XMLObject # :nodoc:
|
2
|
+
module Adapters # :nodoc:
|
3
|
+
module Base # :nodoc:
|
4
|
+
class Element # :nodoc:
|
5
|
+
attr_accessor :raw, :name, :value, :attributes, :children # :nodoc:
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
|
9
|
+
@children = @element_nodes.map { |node| self.class.new(node) }
|
10
|
+
|
11
|
+
@value = case
|
12
|
+
when (not text_value.blank?) then text_value
|
13
|
+
when (not cdata_value.blank?) then cdata_value
|
14
|
+
else ''
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private ###########################################################
|
19
|
+
|
20
|
+
def text_value
|
21
|
+
@text_nodes.reject { |n| n.blank? }.join
|
22
|
+
end
|
23
|
+
|
24
|
+
def cdata_value
|
25
|
+
@cdata_nodes.join
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hpricot'
|
3
|
+
|
4
|
+
module XMLObject::Adapters::Hpricot
|
5
|
+
|
6
|
+
# Can take a String of XML data, or anything that responds to
|
7
|
+
# either +read+ or +to_s+.
|
8
|
+
def self.new(duck)
|
9
|
+
case
|
10
|
+
when duck.is_a?(::Hpricot::Elem) then Element.new(duck)
|
11
|
+
when duck.is_a?(::String) then new(::Hpricot::XML(duck).root)
|
12
|
+
when duck.respond_to?(:read) then new(duck.read)
|
13
|
+
when duck.respond_to?(:to_s) then new(duck.to_s)
|
14
|
+
else raise "Don't know how to deal with '#{duck.class}' object"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private ##################################################################
|
19
|
+
|
20
|
+
class Element < XMLObject::Adapters::Base::Element # :nodoc:
|
21
|
+
def initialize(xml)
|
22
|
+
@raw, @name, @attributes = xml, xml.name, xml.attributes
|
23
|
+
|
24
|
+
@element_nodes = xml.children.select { |c| c.elem? }
|
25
|
+
|
26
|
+
@text_nodes = xml.children.select do |c|
|
27
|
+
c.text? && !c.is_a?(::Hpricot::CData)
|
28
|
+
end.map { |c| c.to_s }
|
29
|
+
|
30
|
+
@cdata_nodes = xml.children.select do |c|
|
31
|
+
c.is_a? ::Hpricot::CData
|
32
|
+
end.map { |c| c.to_s }
|
33
|
+
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def XMLObject.adapter # :nodoc:
|
40
|
+
XMLObject::Adapters::Hpricot
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'libxml'
|
2
|
+
|
3
|
+
module XMLObject::Adapters::LibXML
|
4
|
+
|
5
|
+
# Can take a String of XML data, or anything that responds to
|
6
|
+
# either +read+ or +to_s+.
|
7
|
+
def self.new(duck)
|
8
|
+
case
|
9
|
+
when duck.is_a?(::LibXML::XML::Node) then Element.new(duck)
|
10
|
+
when duck.is_a?(::String)
|
11
|
+
then new(::LibXML::XML::Parser.string(duck).parse.root)
|
12
|
+
when duck.respond_to?(:read) then new(duck.read)
|
13
|
+
when duck.respond_to?(:to_s) then new(duck.to_s)
|
14
|
+
else raise "Don't know how to deal with '#{duck.class}' object"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private ##################################################################
|
19
|
+
|
20
|
+
class Element < XMLObject::Adapters::Base::Element # :nodoc:
|
21
|
+
def initialize(xml)
|
22
|
+
@raw, @name, @attributes = xml, xml.name, xml.attributes.to_h
|
23
|
+
|
24
|
+
@element_nodes = xml.children.select { |c| c.element? }
|
25
|
+
|
26
|
+
@text_nodes = xml.children.select { |c| c.text? }.map { |c| c.to_s }
|
27
|
+
@cdata_nodes = xml.children.select { |c| c.cdata? }.map do |c|
|
28
|
+
c.to_s.chomp(']]>').sub('<![CDATA[', '')
|
29
|
+
end
|
30
|
+
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def XMLObject.adapter # :nodoc:
|
37
|
+
XMLObject::Adapters::LibXML
|
38
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
module XMLObject::Adapters::REXML
|
4
|
+
|
5
|
+
# Can take a String of XML data, or anything that responds to
|
6
|
+
# either +read+ or +to_s+.
|
7
|
+
def self.new(duck)
|
8
|
+
case
|
9
|
+
when duck.is_a?(::REXML::Element) then Element.new(duck)
|
10
|
+
when duck.is_a?(::String) then new(::REXML::Document.new(duck).root)
|
11
|
+
when duck.respond_to?(:read) then new(duck.read)
|
12
|
+
when duck.respond_to?(:to_s) then new(duck.to_s)
|
13
|
+
else raise "Don't know how to deal with '#{duck.class}' object"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private ##################################################################
|
18
|
+
|
19
|
+
class Element < XMLObject::Adapters::Base::Element # :nodoc:
|
20
|
+
def initialize(xml)
|
21
|
+
@raw, @name, @attributes = xml, xml.name, xml.attributes
|
22
|
+
|
23
|
+
@element_nodes = xml.elements
|
24
|
+
|
25
|
+
@text_nodes = xml.children.select do |child|
|
26
|
+
child.class == ::REXML::Text
|
27
|
+
end.collect { |child| child.to_s }
|
28
|
+
|
29
|
+
@cdata_nodes = xml.children.select do |child|
|
30
|
+
child.class == ::REXML::CData
|
31
|
+
end.collect { |child| child.to_s }
|
32
|
+
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def XMLObject.adapter # :nodoc:
|
39
|
+
XMLObject::Adapters::REXML
|
40
|
+
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_s.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 then @__children[name.values.first.to_sym]
|
37
|
+
when :child then @__children[name.values.first.to_sym]
|
38
|
+
when :attr then @__attributes[name.values.first.to_sym]
|
39
|
+
when :attribute then @__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,19 @@
|
|
1
|
+
class String
|
2
|
+
def blank?
|
3
|
+
self !~ /\S/
|
4
|
+
end unless ''.respond_to? :blank?
|
5
|
+
|
6
|
+
def squish
|
7
|
+
dup.squish!
|
8
|
+
end unless ''.respond_to? :squish
|
9
|
+
|
10
|
+
def squish!
|
11
|
+
strip!
|
12
|
+
gsub! /\s+/, ' '
|
13
|
+
self
|
14
|
+
end unless ''.respond_to? :squish!
|
15
|
+
|
16
|
+
def singularize
|
17
|
+
self.chomp 's'
|
18
|
+
end unless ''.respond_to? :singularize
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module XMLObject::Element
|
2
|
+
def self.extended(obj) # :nodoc:
|
3
|
+
obj.instance_variable_set :@__children, {}
|
4
|
+
obj.instance_variable_set :@__attributes, {}
|
5
|
+
obj
|
6
|
+
end
|
7
|
+
|
8
|
+
# The raw, unadapted XML object. Whatever this is, it really depends on
|
9
|
+
# the current_adapter.
|
10
|
+
def raw_xml
|
11
|
+
@__adapted_element.raw if @__adapted_element
|
12
|
+
end
|
13
|
+
|
14
|
+
private ##################################################################
|
15
|
+
|
16
|
+
def method_missing(m, *a, &b) # :nodoc:
|
17
|
+
dp = __question_dispatch(m, *a, &b)
|
18
|
+
dp = __dot_notation_dispatch(m, *a, &b) if dp.nil?
|
19
|
+
dp
|
20
|
+
end
|
21
|
+
end
|
@@ -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)
|
11
|
+
bool = case
|
12
|
+
when %w[ true yes t y ].include?(boolish.downcase) then true
|
13
|
+
when %w[ false no f n ].include?(boolish.downcase) then 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?(meth)
|
29
|
+
instance_eval %{ def #{meth}; @__children[%s|#{meth}|]; end }
|
30
|
+
@__children[meth]
|
31
|
+
|
32
|
+
elsif @__attributes.has_key?(meth)
|
33
|
+
instance_eval %{ def #{meth}; @__attributes[%s|#{meth}|]; end }
|
34
|
+
@__attributes[meth]
|
35
|
+
|
36
|
+
elsif @__children.has_key?(singular = meth.to_s.singularize.to_sym) &&
|
37
|
+
@__children[singular].is_a?(Array)
|
38
|
+
|
39
|
+
instance_eval %{ def #{meth}; @__children[%s|#{singular}|]; end }
|
40
|
+
@__children[singular]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module XMLObject::String
|
2
|
+
|
3
|
+
# Attempts to detect wether this String is really an integer or float,
|
4
|
+
# and returns accordingly. If not, just returns the string.
|
5
|
+
def rb
|
6
|
+
result = case
|
7
|
+
when (self !~ /\S/) then ''
|
8
|
+
when match(/[a-zA-Z]/) then ::String.new(self)
|
9
|
+
when match(/^[+-]?\d+$/) then self.to_i
|
10
|
+
when match(/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/) then self.to_f
|
11
|
+
else ::String.new(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
@__rb ||= result
|
15
|
+
end
|
16
|
+
|
17
|
+
# A decorated String is blank when it has a blank value, no child
|
18
|
+
# elements, and no attributes. For example:
|
19
|
+
#
|
20
|
+
# <blank_element></blank_element>
|
21
|
+
def blank?
|
22
|
+
(self !~ /\S/) && @__children.blank? && @__attributes.blank?
|
23
|
+
end
|
24
|
+
end
|
data/xml-object.gemspec
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.rubyforge_project = 'xml-object'
|
3
|
+
|
4
|
+
gem.name = 'xml-object'
|
5
|
+
gem.version = '0.9.7'
|
6
|
+
gem.date = '2008-10-20'
|
7
|
+
gem.author = 'Jordi Bunster'
|
8
|
+
gem.email = 'jordi@bunster.org'
|
9
|
+
gem.homepage = 'http://github.com/jordi/xml-object'
|
10
|
+
|
11
|
+
gem.summary = "The Rubyista's way to do quick XML sit-ups"
|
12
|
+
gem.description = %{ XMLObject is a library for reading (not writing) XML.
|
13
|
+
It is particularly suited for cases where one is dealing with small
|
14
|
+
documents of a known structure. While not devoid of caveats, it does
|
15
|
+
have a very pleasant, idiomatic Ruby syntax. }.strip!.gsub! /\s+/, ' '
|
16
|
+
|
17
|
+
gem.files = %w[
|
18
|
+
MIT-LICENSE
|
19
|
+
README.rdoc
|
20
|
+
TODO
|
21
|
+
WHATSNEW
|
22
|
+
lib
|
23
|
+
lib/jordi-xml-object.rb
|
24
|
+
lib/xml-object
|
25
|
+
lib/xml-object/adapters
|
26
|
+
lib/xml-object/adapters/hpricot.rb
|
27
|
+
lib/xml-object/adapters/jrexml.rb
|
28
|
+
lib/xml-object/adapters/libxml.rb
|
29
|
+
lib/xml-object/adapters/rexml.rb
|
30
|
+
lib/xml-object/adapters.rb
|
31
|
+
lib/xml-object/array_notation.rb
|
32
|
+
lib/xml-object/blankish_slate.rb
|
33
|
+
lib/xml-object/collection_proxy.rb
|
34
|
+
lib/xml-object/core_ext
|
35
|
+
lib/xml-object/core_ext/hash.rb
|
36
|
+
lib/xml-object/core_ext/nil_class.rb
|
37
|
+
lib/xml-object/core_ext/string.rb
|
38
|
+
lib/xml-object/core_ext.rb
|
39
|
+
lib/xml-object/element.rb
|
40
|
+
lib/xml-object/method_missing_dispatchers.rb
|
41
|
+
lib/xml-object/string.rb
|
42
|
+
lib/xml-object.rb
|
43
|
+
xml-object.gemspec
|
44
|
+
]
|
45
|
+
|
46
|
+
gem.has_rdoc = !!(gem.extra_rdoc_files = %w[ README.rdoc ])
|
47
|
+
gem.rdoc_options << '--title' << 'XMLObject' <<
|
48
|
+
'--main' << 'README.rdoc' <<
|
49
|
+
'--inline-source'
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xml-object
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jordi Bunster
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-20 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
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.
|
17
|
+
email: jordi@bunster.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- MIT-LICENSE
|
26
|
+
- README.rdoc
|
27
|
+
- TODO
|
28
|
+
- WHATSNEW
|
29
|
+
- lib
|
30
|
+
- lib/jordi-xml-object.rb
|
31
|
+
- lib/xml-object
|
32
|
+
- lib/xml-object/adapters
|
33
|
+
- lib/xml-object/adapters/hpricot.rb
|
34
|
+
- lib/xml-object/adapters/jrexml.rb
|
35
|
+
- lib/xml-object/adapters/libxml.rb
|
36
|
+
- lib/xml-object/adapters/rexml.rb
|
37
|
+
- lib/xml-object/adapters.rb
|
38
|
+
- lib/xml-object/array_notation.rb
|
39
|
+
- lib/xml-object/blankish_slate.rb
|
40
|
+
- lib/xml-object/collection_proxy.rb
|
41
|
+
- lib/xml-object/core_ext
|
42
|
+
- lib/xml-object/core_ext/hash.rb
|
43
|
+
- lib/xml-object/core_ext/nil_class.rb
|
44
|
+
- lib/xml-object/core_ext/string.rb
|
45
|
+
- lib/xml-object/core_ext.rb
|
46
|
+
- lib/xml-object/element.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: xml-object
|
77
|
+
rubygems_version: 1.3.0
|
78
|
+
signing_key:
|
79
|
+
specification_version: 2
|
80
|
+
summary: The Rubyista's way to do quick XML sit-ups
|
81
|
+
test_files: []
|
82
|
+
|