xml-object 0.9.7
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 +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
|
+
|