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.
@@ -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.
@@ -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/TODO ADDED
@@ -0,0 +1 @@
1
+ * Refactor so as to not do things recursively
@@ -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'))
@@ -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,10 @@
1
+ require 'jrexml'
2
+ require File.join(File.dirname(__FILE__), 'rexml')
3
+
4
+ module XMLObject::Adapters
5
+ JREXML = REXML
6
+ end
7
+
8
+ def XMLObject.adapter # :nodoc:
9
+ XMLObject::Adapters::JREXML
10
+ 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,10 @@
1
+ class XMLObject::BlankishSlate # :nodoc:
2
+
3
+ instance_methods.each do |m|
4
+ undef_method m unless m =~ /^__/ ||
5
+ m =~ /respond_to/ ||
6
+ m =~ /extend/ ||
7
+ m =~ /object_id/ ||
8
+ m =~ /^instance_/
9
+ end
10
+ 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,3 @@
1
+ require 'core_ext/hash'
2
+ require 'core_ext/nil_class'
3
+ require 'core_ext/string'
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def blank?
3
+ size.zero?
4
+ end unless {}.respond_to? :blank?
5
+ end
@@ -0,0 +1,5 @@
1
+ class NilClass
2
+ def blank?
3
+ true
4
+ end unless nil.respond_to? :blank?
5
+ 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
@@ -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
+