ox-bundlecachetest 2.14.23
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +751 -0
- data/LICENSE +21 -0
- data/README.md +351 -0
- data/ext/ox/attr.h +78 -0
- data/ext/ox/base64.c +105 -0
- data/ext/ox/base64.h +18 -0
- data/ext/ox/buf.h +162 -0
- data/ext/ox/builder.c +948 -0
- data/ext/ox/cache.c +351 -0
- data/ext/ox/cache.h +21 -0
- data/ext/ox/cache8.c +106 -0
- data/ext/ox/cache8.h +23 -0
- data/ext/ox/dump.c +1260 -0
- data/ext/ox/err.c +46 -0
- data/ext/ox/err.h +36 -0
- data/ext/ox/extconf.rb +47 -0
- data/ext/ox/gen_load.c +342 -0
- data/ext/ox/hash_load.c +309 -0
- data/ext/ox/helper.h +84 -0
- data/ext/ox/intern.c +157 -0
- data/ext/ox/intern.h +25 -0
- data/ext/ox/obj_load.c +809 -0
- data/ext/ox/ox.c +1649 -0
- data/ext/ox/ox.h +245 -0
- data/ext/ox/parse.c +1197 -0
- data/ext/ox/sax.c +1570 -0
- data/ext/ox/sax.h +69 -0
- data/ext/ox/sax_as.c +270 -0
- data/ext/ox/sax_buf.c +209 -0
- data/ext/ox/sax_buf.h +204 -0
- data/ext/ox/sax_hint.c +207 -0
- data/ext/ox/sax_hint.h +40 -0
- data/ext/ox/sax_stack.h +113 -0
- data/ext/ox/slotcache.c +158 -0
- data/ext/ox/slotcache.h +19 -0
- data/ext/ox/special.c +390 -0
- data/ext/ox/special.h +14 -0
- data/ext/ox/type.h +39 -0
- data/lib/ox/bag.rb +103 -0
- data/lib/ox/cdata.rb +10 -0
- data/lib/ox/comment.rb +11 -0
- data/lib/ox/doctype.rb +11 -0
- data/lib/ox/document.rb +28 -0
- data/lib/ox/element.rb +464 -0
- data/lib/ox/error.rb +25 -0
- data/lib/ox/hasattrs.rb +54 -0
- data/lib/ox/instruct.rb +34 -0
- data/lib/ox/node.rb +23 -0
- data/lib/ox/raw.rb +12 -0
- data/lib/ox/sax.rb +97 -0
- data/lib/ox/version.rb +4 -0
- data/lib/ox/xmlrpc_adapter.rb +33 -0
- data/lib/ox.rb +79 -0
- metadata +128 -0
data/lib/ox/bag.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module Ox
|
|
2
|
+
# A generic class that is used only for storing attributes. It is the base
|
|
3
|
+
# Class for auto-generated classes in the storage system. Instance variables
|
|
4
|
+
# are added using the instance_variable_set() method. All instance variables
|
|
5
|
+
# can be accessed using the variable name (without the @ prefix). No setters
|
|
6
|
+
# are provided as the Class is intended for reading only.
|
|
7
|
+
class Bag
|
|
8
|
+
# The initializer can take multiple arguments in the form of key values
|
|
9
|
+
# where the key is the variable name and the value is the variable
|
|
10
|
+
# value. This is intended for testing purposes only.
|
|
11
|
+
# - +args+ [Hash] instance variable symbols and their values
|
|
12
|
+
#
|
|
13
|
+
# *Example*
|
|
14
|
+
#
|
|
15
|
+
# Ox::Bag.new(:@x => 42, :@y => 57)
|
|
16
|
+
#
|
|
17
|
+
def initialize(args={})
|
|
18
|
+
args.each do |k, v|
|
|
19
|
+
instance_variable_set(k, v)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Replaces the Object.respond_to?() method.
|
|
24
|
+
# - +m+ [Symbol] method symbol
|
|
25
|
+
# *return* [Boolean] true for any method that matches an instance variable
|
|
26
|
+
# reader, otherwise false.
|
|
27
|
+
def respond_to?(m)
|
|
28
|
+
return true if super
|
|
29
|
+
|
|
30
|
+
at_m = ('@' + m.to_s).to_sym
|
|
31
|
+
instance_variables.include?(at_m)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Handles requests for variable values. Others cause an Exception to be
|
|
35
|
+
# raised.
|
|
36
|
+
# - +m+ (Symbol) method symbol
|
|
37
|
+
# *return* [Boolean] the value of the specified instance variable.
|
|
38
|
+
#
|
|
39
|
+
# _raise_ [ArgumentError] if an argument is given. Zero arguments expected.
|
|
40
|
+
#
|
|
41
|
+
# _raise_ [NoMethodError] if the instance variable is not defined.
|
|
42
|
+
def method_missing(m, *args, &block)
|
|
43
|
+
unless args.nil? or args.empty?
|
|
44
|
+
raise ArgumentError.new("wrong number of arguments (#{args.size} for 0) to method #{m}")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
at_m = ('@' + m.to_s).to_sym
|
|
48
|
+
raise NoMethodError.new("undefined method #{m}", m) unless instance_variable_defined?(at_m)
|
|
49
|
+
|
|
50
|
+
instance_variable_get(at_m)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Replaces eql?() with something more reasonable for this Class.
|
|
54
|
+
# - +other+ [Object] Object to compare self to
|
|
55
|
+
# *return* [Boolean] true if each variable and value are the same, otherwise false.
|
|
56
|
+
def eql?(other)
|
|
57
|
+
return false if (other.nil? or self.class != other.class)
|
|
58
|
+
|
|
59
|
+
ova = other.instance_variables
|
|
60
|
+
iv = instance_variables
|
|
61
|
+
return false if ova.size != iv.size
|
|
62
|
+
|
|
63
|
+
iv.each do |vid|
|
|
64
|
+
return false if instance_variable_get(vid) != other.instance_variable_get(vid)
|
|
65
|
+
end
|
|
66
|
+
true
|
|
67
|
+
end
|
|
68
|
+
alias == eql?
|
|
69
|
+
|
|
70
|
+
# Define a new class based on the Ox::Bag class. This is used internally in
|
|
71
|
+
# the Ox module and is available to service wrappers that receive XML
|
|
72
|
+
# requests that include Objects of Classes not defined in the storage
|
|
73
|
+
# process.
|
|
74
|
+
# - +classname+ (String) Class name or symbol that includes Module names.
|
|
75
|
+
# *return* [Object] an instance of the specified Class.
|
|
76
|
+
#
|
|
77
|
+
# _raise_ [NameError] if the classname is invalid.
|
|
78
|
+
def self.define_class(classname)
|
|
79
|
+
classname = classname.to_s unless classname.is_a?(String)
|
|
80
|
+
tokens = classname.split('::').map { |n| n.to_sym }
|
|
81
|
+
raise NameError.new("Invalid classname '#{classname}") if tokens.empty?
|
|
82
|
+
|
|
83
|
+
m = Object
|
|
84
|
+
tokens[0..-2].each do |sym|
|
|
85
|
+
if m.const_defined?(sym)
|
|
86
|
+
m = m.const_get(sym)
|
|
87
|
+
else
|
|
88
|
+
c = Module.new
|
|
89
|
+
m.const_set(sym, c)
|
|
90
|
+
m = c
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
sym = tokens[-1]
|
|
94
|
+
if m.const_defined?(sym)
|
|
95
|
+
c = m.const_get(sym)
|
|
96
|
+
else
|
|
97
|
+
c = Class.new(Ox::Bag)
|
|
98
|
+
m.const_set(sym, c)
|
|
99
|
+
end
|
|
100
|
+
c
|
|
101
|
+
end
|
|
102
|
+
end # Bag
|
|
103
|
+
end # Ox
|
data/lib/ox/cdata.rb
ADDED
data/lib/ox/comment.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Ox
|
|
2
|
+
# Comments represent XML comments in an XML document. A comment has a value
|
|
3
|
+
# attribute only.
|
|
4
|
+
class Comment < Node
|
|
5
|
+
# Creates a new Comment with the specified value.
|
|
6
|
+
# - +value+ [String] string value for the comment
|
|
7
|
+
def initialize(value)
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
end # Comment
|
|
11
|
+
end # Ox
|
data/lib/ox/doctype.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Ox
|
|
2
|
+
# Represents a DOCTYPE in an XML document.
|
|
3
|
+
class DocType < Node
|
|
4
|
+
# Creates a DOCTYPE elements with the content as a string specified in the
|
|
5
|
+
# value parameter.
|
|
6
|
+
# - +value+ [String] string value for the element
|
|
7
|
+
def initialize(value)
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
end # DocType
|
|
11
|
+
end # Ox
|
data/lib/ox/document.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Ox
|
|
2
|
+
# Represents an XML document. It has a fixed set of attributes which form
|
|
3
|
+
# the XML prolog. A Document includes Elements.
|
|
4
|
+
class Document < Element
|
|
5
|
+
# Create a new Document.
|
|
6
|
+
# - +prolog+ [Hash] prolog attributes
|
|
7
|
+
# - _:version_ [String] version, typically '1.0' or '1.1'
|
|
8
|
+
# - _:encoding_ [String] encoding for the document, currently included but ignored
|
|
9
|
+
# - _:standalone_ [String] indicates the document is standalone
|
|
10
|
+
def initialize(prolog={})
|
|
11
|
+
super(nil)
|
|
12
|
+
@attributes = {}
|
|
13
|
+
@attributes[:version] = prolog[:version] unless prolog[:version].nil?
|
|
14
|
+
@attributes[:encoding] = prolog[:encoding] unless prolog[:encoding].nil?
|
|
15
|
+
@attributes[:standalone] = prolog[:standalone] unless prolog[:standalone].nil?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns the first Element in the document.
|
|
19
|
+
def root
|
|
20
|
+
unless !instance_variable_defined?(:@nodes) || @nodes.nil?
|
|
21
|
+
@nodes.each do |n|
|
|
22
|
+
return n if n.is_a?(::Ox::Element)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
end # Document
|
|
28
|
+
end # Ox
|
data/lib/ox/element.rb
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
module Ox
|
|
2
|
+
# An Element represents a element of an XML document. It has a name,
|
|
3
|
+
# attributes, and sub-nodes.
|
|
4
|
+
#
|
|
5
|
+
# To access the child elements or attributes there are several options. One
|
|
6
|
+
# is to walk the nodes and attributes. Another is to use the locate()
|
|
7
|
+
# method. The easiest for simple regularly formatted XML is to reference the
|
|
8
|
+
# sub elements or attributes simply by name. Repeating elements with the
|
|
9
|
+
# same name can be referenced with an element count as well. A few examples
|
|
10
|
+
# should explain the 'easy' API more clearly.
|
|
11
|
+
#
|
|
12
|
+
# *Example*
|
|
13
|
+
#
|
|
14
|
+
# doc = Ox.parse(%{
|
|
15
|
+
# <?xml?>
|
|
16
|
+
# <People>
|
|
17
|
+
# <Person age="58">
|
|
18
|
+
# <given>Peter</given>
|
|
19
|
+
# <surname>Ohler</surname>
|
|
20
|
+
# </Person>
|
|
21
|
+
# <Person>
|
|
22
|
+
# <given>Makie</given>
|
|
23
|
+
# <surname>Ohler</surname>
|
|
24
|
+
# </Person>
|
|
25
|
+
# </People>
|
|
26
|
+
# })
|
|
27
|
+
#
|
|
28
|
+
# doc.People.Person.given.text
|
|
29
|
+
# => "Peter"
|
|
30
|
+
# doc.People.Person(1).given.text
|
|
31
|
+
# => "Makie"
|
|
32
|
+
# doc.People.Person.age
|
|
33
|
+
# => "58"
|
|
34
|
+
class Element < Node
|
|
35
|
+
include HasAttrs
|
|
36
|
+
|
|
37
|
+
# Creates a new Element with the specified name.
|
|
38
|
+
# - +name+ [String] name of the Element
|
|
39
|
+
def initialize(name)
|
|
40
|
+
super
|
|
41
|
+
@attributes = {}
|
|
42
|
+
@nodes = []
|
|
43
|
+
end
|
|
44
|
+
alias name value
|
|
45
|
+
alias name= value=
|
|
46
|
+
|
|
47
|
+
# Returns the Element's nodes array. These are the sub-elements of this
|
|
48
|
+
# Element.
|
|
49
|
+
# *return* [Array] all child Nodes.
|
|
50
|
+
def nodes
|
|
51
|
+
@nodes = [] if !instance_variable_defined?(:@nodes) or @nodes.nil?
|
|
52
|
+
@nodes
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Appends a Node to the Element's nodes array. Returns the element itself
|
|
56
|
+
# so multiple appends can be chained together.
|
|
57
|
+
# - +node+ [Node] Node to append to the nodes array
|
|
58
|
+
def <<(node)
|
|
59
|
+
raise 'argument to << must be a String or Ox::Node.' unless node.is_a?(String) or node.is_a?(Node)
|
|
60
|
+
|
|
61
|
+
@nodes = [] if !instance_variable_defined?(:@nodes) or @nodes.nil?
|
|
62
|
+
@nodes << node
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Prepend a Node to the Element's nodes array. Returns the element itself
|
|
67
|
+
# so multiple appends can be chained together.
|
|
68
|
+
# - +node+ [Node] Node to prepend to the nodes array
|
|
69
|
+
def prepend_child(node)
|
|
70
|
+
raise 'argument to << must be a String or Ox::Node.' unless node.is_a?(String) or node.is_a?(Node)
|
|
71
|
+
|
|
72
|
+
@nodes = [] if !instance_variable_defined?(:@nodes) or @nodes.nil?
|
|
73
|
+
@nodes.unshift(node)
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Returns true if this Object and other are of the same type and have the
|
|
78
|
+
# equivalent value and the equivalent elements otherwise false is returned.
|
|
79
|
+
# - +other+ [Object] Object compare _self_ to.
|
|
80
|
+
# *return* [Boolean] true if both Objects are equivalent, otherwise false.
|
|
81
|
+
def eql?(other)
|
|
82
|
+
return false unless super
|
|
83
|
+
return false unless attributes == other.attributes
|
|
84
|
+
return false unless nodes == other.nodes
|
|
85
|
+
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
alias == eql?
|
|
89
|
+
|
|
90
|
+
# Returns the first String in the elements nodes array or nil if there is
|
|
91
|
+
# no String node.
|
|
92
|
+
def text
|
|
93
|
+
nodes.each { |n| return n if n.is_a?(String) }
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Clears any child nodes of an element and replaces those with a single Text
|
|
98
|
+
# (String) node. Note the existing nodes array is modified and not replaced.
|
|
99
|
+
# - +txt+ [String] to become the only element of the nodes array
|
|
100
|
+
def replace_text(txt)
|
|
101
|
+
raise 'the argument to replace_text() must be a String' unless txt.is_a?(String)
|
|
102
|
+
|
|
103
|
+
if !instance_variable_defined?(:@nodes) or @nodes.nil?
|
|
104
|
+
@node = []
|
|
105
|
+
else
|
|
106
|
+
@nodes.clear
|
|
107
|
+
end
|
|
108
|
+
@nodes << txt
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Return true if all the key-value pairs in the cond Hash match the
|
|
112
|
+
# @attributes key-values.
|
|
113
|
+
def attr_match(cond)
|
|
114
|
+
cond.each_pair { |k, v| return false unless v == @attributes[k.to_sym] || v == @attributes[k.to_s] }
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Iterate over each child of the instance yielding according to the cond
|
|
119
|
+
# argument value. If the cond argument is nil then all child nodes are
|
|
120
|
+
# yielded to. If cond is a string then only the child Elements with a
|
|
121
|
+
# matching name will be yielded to. If the cond is a Hash then the
|
|
122
|
+
# keys-value pairs in the cond must match the child attribute values with
|
|
123
|
+
# the same keys. Any other cond type will yield to nothing.
|
|
124
|
+
def each(cond=nil, &block)
|
|
125
|
+
build_enumerator(cond).each(&block)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Returns an array of Nodes or Strings that correspond to the locations
|
|
129
|
+
# specified by the path parameter. The path parameter describes the path
|
|
130
|
+
# to the return values which can be either nodes in the XML or
|
|
131
|
+
# attributes. The path is a relative description. There are similarities
|
|
132
|
+
# between the locate() method and XPath but locate does not follow the
|
|
133
|
+
# same rules as XPath. The syntax is meant to be simpler and more Ruby
|
|
134
|
+
# like.
|
|
135
|
+
#
|
|
136
|
+
# Like XPath the path delimiters are the slash (/) character. The path is
|
|
137
|
+
# split on the delimiter and each element of the path then describes the
|
|
138
|
+
# child of the current Element to traverse.
|
|
139
|
+
#
|
|
140
|
+
# Attributes are specified with an @ prefix.
|
|
141
|
+
#
|
|
142
|
+
# Each element name in the path can be followed by a bracket expression
|
|
143
|
+
# that narrows the paths to traverse. Supported expressions are numbers
|
|
144
|
+
# with a preceeding qualifier. Qualifiers are -, +, <, and >. The +
|
|
145
|
+
# qualifier is the default. A - qualifier indicates the index begins at
|
|
146
|
+
# the end of the children just like for Ruby Arrays. The < and >
|
|
147
|
+
# qualifiers indicates all elements either less than or greater than
|
|
148
|
+
# should be matched. Note that unlike XPath, the element index starts at 0
|
|
149
|
+
# similar to Ruby be contrary to XPath.
|
|
150
|
+
#
|
|
151
|
+
# Element names can also be wildcard characters. A * indicates any decendent should be followed. A ? indicates any
|
|
152
|
+
# single Element can match the wildcard. A ^ character followed by the name of a Class will match any node of the
|
|
153
|
+
# specified class. Valid class names are Element, Comment, String (or Text), CData, DocType.
|
|
154
|
+
#
|
|
155
|
+
# Examples are:
|
|
156
|
+
# * <code>element.locate("Family/Pete/*")</code> returns all children of the Pete Element.
|
|
157
|
+
# * <code>element.locate("Family/?[1]")</code> returns the first element in the Family Element.
|
|
158
|
+
# * <code>element.locate("Family/?[<3]")</code> returns the first 3 elements in the Family Element.
|
|
159
|
+
# * <code>element.locate("Family/?[@age]")</code> returns the elements with an age attribute defined in the Family Element.
|
|
160
|
+
# * <code>element.locate("Family/Kid[@age]")</code> returns the Kid elements with an age attribute defined in the Family Element.
|
|
161
|
+
# * <code>element.locate("Family/?[@age=32]")</code> returns the elements with an age attribute equal to 32 in the Family Element.
|
|
162
|
+
# * <code>element.locate("Family/Kid[@age=32]")</code> returns the Kid elements with an age attribute equal to 32 in the Family Element.
|
|
163
|
+
# * <code>element.locate("Family/?/@age")</code> returns the arg attribute for each child in the Family Element.
|
|
164
|
+
# * <code>element.locate("Family/*/@type")</code> returns the type attribute value for decendents of the Family.
|
|
165
|
+
# * <code>element.locate("Family/^Comment")</code> returns any comments that are a child of Family.
|
|
166
|
+
#
|
|
167
|
+
# - +path+ [String] path to the Nodes to locate
|
|
168
|
+
def locate(path)
|
|
169
|
+
return [self] if path.nil?
|
|
170
|
+
|
|
171
|
+
found = []
|
|
172
|
+
pa = path.split('/')
|
|
173
|
+
if '*' == path[0]
|
|
174
|
+
# a bit of a hack but it allows self to be checked as well
|
|
175
|
+
e = Element.new('')
|
|
176
|
+
e << self
|
|
177
|
+
e.alocate(pa, found)
|
|
178
|
+
else
|
|
179
|
+
alocate(pa, found)
|
|
180
|
+
end
|
|
181
|
+
found
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Remove all the children matching the path provided
|
|
185
|
+
#
|
|
186
|
+
# Examples are:
|
|
187
|
+
# * <code>element.remove_children(Ox:Element)</code> removes the element passed as argument if child of the element.
|
|
188
|
+
# * <code>element.remove_children(Ox:Element, Ox:Element)</code> removes the list of elements passed as argument if children of the element.
|
|
189
|
+
#
|
|
190
|
+
# - +children+ [Array] array of OX
|
|
191
|
+
def remove_children(*children)
|
|
192
|
+
return self if children.compact.empty?
|
|
193
|
+
|
|
194
|
+
recursive_children_removal(children.compact.map { |c| c.object_id })
|
|
195
|
+
self
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Remove all the children matching the path provided
|
|
199
|
+
#
|
|
200
|
+
# Examples are:
|
|
201
|
+
# * <code>element.remove_children_by_path("*")</code> removes all children attributes.
|
|
202
|
+
# * <code>element.remove_children_by_path("Family/Kid[@age=32]")</code> removes the Kid elements with an age attribute equal to 32 in the Family Element.
|
|
203
|
+
#
|
|
204
|
+
# - +path+ [String] path to the Nodes to locate
|
|
205
|
+
def remove_children_by_path(path)
|
|
206
|
+
del_locate(path.split('/')) unless path.nil?
|
|
207
|
+
self
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Handles the 'easy' API that allows navigating a simple XML by
|
|
211
|
+
# referencing elements and attributes by name.
|
|
212
|
+
# - +id+ [Symbol] element or attribute name
|
|
213
|
+
# *return* [Element|Node|String|nil] the element, attribute value, or Node identifed by the name
|
|
214
|
+
#
|
|
215
|
+
# _raise_ [NoMethodError] if no match is found
|
|
216
|
+
def method_missing(id, *args, &block)
|
|
217
|
+
has_some = false
|
|
218
|
+
ids = id.to_s
|
|
219
|
+
i = args[0].to_i # will be 0 if no arg or parsing fails
|
|
220
|
+
nodes.each do |n|
|
|
221
|
+
unless (n.is_a?(Element) || n.is_a?(Instruct)) && (n.value == id || n.value == ids || name_matchs?(n.value, ids))
|
|
222
|
+
next
|
|
223
|
+
end
|
|
224
|
+
return n if 0 == i
|
|
225
|
+
|
|
226
|
+
has_some = true
|
|
227
|
+
i -= 1
|
|
228
|
+
end
|
|
229
|
+
if instance_variable_defined?(:@attributes)
|
|
230
|
+
return @attributes[id] if @attributes.has_key?(id)
|
|
231
|
+
return @attributes[ids] if @attributes.has_key?(ids)
|
|
232
|
+
end
|
|
233
|
+
return nil if has_some
|
|
234
|
+
|
|
235
|
+
raise NoMethodError.new("#{ids} not found", name)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# - +id+ [String|Symbol] identifer of the attribute or method
|
|
239
|
+
# - +ignored+ inc_all [Boolean]
|
|
240
|
+
# *return* true if the element has a member that matches the provided name.
|
|
241
|
+
def respond_to?(id, inc_all=false)
|
|
242
|
+
return true if super
|
|
243
|
+
|
|
244
|
+
id_str = id.to_s
|
|
245
|
+
id_sym = id.to_sym
|
|
246
|
+
nodes.each do |n|
|
|
247
|
+
next if n.is_a?(String)
|
|
248
|
+
return true if n.value == id_str || n.value == id_sym || name_matchs?(n.value, id_str)
|
|
249
|
+
end
|
|
250
|
+
if instance_variable_defined?(:@attributes) && !@attributes.nil?
|
|
251
|
+
return true if @attributes.has_key?(id_str)
|
|
252
|
+
return true if @attributes.has_key?(id_sym)
|
|
253
|
+
end
|
|
254
|
+
false
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# - +path+ [Array] array of steps in a path
|
|
258
|
+
# - +found+ [Array] matching nodes
|
|
259
|
+
def alocate(path, found)
|
|
260
|
+
step = path[0]
|
|
261
|
+
if step.start_with?('@') # attribute
|
|
262
|
+
raise InvalidPath.new(path) unless 1 == path.size
|
|
263
|
+
|
|
264
|
+
if instance_variable_defined?(:@attributes)
|
|
265
|
+
step = step[1..-1]
|
|
266
|
+
sym_step = step.to_sym
|
|
267
|
+
@attributes.each do |k, v|
|
|
268
|
+
found << v if ('?' == step or k == step or k == sym_step)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
else # element name
|
|
272
|
+
if (i = step.index('[')).nil? # just name
|
|
273
|
+
name = step
|
|
274
|
+
qual = nil
|
|
275
|
+
else
|
|
276
|
+
name = step[0..i-1]
|
|
277
|
+
raise InvalidPath.new(path) unless step.end_with?(']')
|
|
278
|
+
|
|
279
|
+
i += 1
|
|
280
|
+
qual = step[i..i] # step[i] would be better but some rubies (jruby, ree, rbx) take that as a Fixnum.
|
|
281
|
+
if qual.between?('0', '9')
|
|
282
|
+
qual = '+'
|
|
283
|
+
else
|
|
284
|
+
i += 1
|
|
285
|
+
end
|
|
286
|
+
index = step[i..-2].to_i
|
|
287
|
+
end
|
|
288
|
+
if ['?', '*'].include?(name)
|
|
289
|
+
match = nodes
|
|
290
|
+
elsif '^' == name[0..0] # 1.8.7 thinks name[0] is a fixnum
|
|
291
|
+
case name[1..-1]
|
|
292
|
+
when 'Element'
|
|
293
|
+
match = nodes.select { |e| e.is_a?(Element) }
|
|
294
|
+
when 'String', 'Text'
|
|
295
|
+
match = nodes.select { |e| e.is_a?(String) }
|
|
296
|
+
when 'Comment'
|
|
297
|
+
match = nodes.select { |e| e.is_a?(Comment) }
|
|
298
|
+
when 'CData'
|
|
299
|
+
match = nodes.select { |e| e.is_a?(CData) }
|
|
300
|
+
when 'DocType'
|
|
301
|
+
match = nodes.select { |e| e.is_a?(DocType) }
|
|
302
|
+
else
|
|
303
|
+
# puts "*** no match on #{name}"
|
|
304
|
+
match = []
|
|
305
|
+
end
|
|
306
|
+
else
|
|
307
|
+
match = nodes.select { |e| e.is_a?(Element) and name == e.name }
|
|
308
|
+
end
|
|
309
|
+
unless qual.nil? or match.empty?
|
|
310
|
+
case qual
|
|
311
|
+
when '+'
|
|
312
|
+
match = index < match.size ? [match[index]] : []
|
|
313
|
+
when '-'
|
|
314
|
+
match = index <= match.size ? [match[-index]] : []
|
|
315
|
+
when '<'
|
|
316
|
+
match = 0 < index ? match[0..index - 1] : []
|
|
317
|
+
when '>'
|
|
318
|
+
match = index <= match.size ? match[index + 1..-1] : []
|
|
319
|
+
when '@'
|
|
320
|
+
k, v = step[i..-2].split('=')
|
|
321
|
+
if v
|
|
322
|
+
match = match.select { |n| n.is_a?(Element) && (v == n.attributes[k.to_sym] || v == n.attributes[k]) }
|
|
323
|
+
else
|
|
324
|
+
match = match.select { |n| n.is_a?(Element) && (n.attributes[k.to_sym] || n.attributes[k]) }
|
|
325
|
+
end
|
|
326
|
+
else
|
|
327
|
+
raise InvalidPath.new(path)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
if (1 == path.size)
|
|
331
|
+
match.each { |n| found << n }
|
|
332
|
+
elsif '*' == name
|
|
333
|
+
match.each { |n| n.alocate(path, found) if n.is_a?(Element) }
|
|
334
|
+
match.each { |n| n.alocate(path[1..-1], found) if n.is_a?(Element) }
|
|
335
|
+
else
|
|
336
|
+
match.each { |n| n.alocate(path[1..-1], found) if n.is_a?(Element) }
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# - +path+ [Array] array of steps in a path
|
|
342
|
+
def del_locate(path)
|
|
343
|
+
step = path[0]
|
|
344
|
+
if step.start_with?('@') # attribute
|
|
345
|
+
raise InvalidPath.new(path) unless 1 == path.size
|
|
346
|
+
|
|
347
|
+
if instance_variable_defined?(:@attributes)
|
|
348
|
+
step = step[1..-1]
|
|
349
|
+
sym_step = step.to_sym
|
|
350
|
+
@attributes.delete_if { |k, v| '?' == step || k.to_sym == sym_step }
|
|
351
|
+
end
|
|
352
|
+
else # element name
|
|
353
|
+
if (i = step.index('[')).nil? # just name
|
|
354
|
+
name = step
|
|
355
|
+
qual = nil
|
|
356
|
+
else
|
|
357
|
+
name = step[0..i-1]
|
|
358
|
+
raise InvalidPath.new(path) unless step.end_with?(']')
|
|
359
|
+
|
|
360
|
+
i += 1
|
|
361
|
+
qual = step[i..i] # step[i] would be better but some rubies (jruby, ree, rbx) take that as a Fixnum.
|
|
362
|
+
if qual.between?('0', '9')
|
|
363
|
+
qual = '+'
|
|
364
|
+
else
|
|
365
|
+
i += 1
|
|
366
|
+
end
|
|
367
|
+
index = step[i..-2].to_i
|
|
368
|
+
end
|
|
369
|
+
if ['?', '*'].include?(name)
|
|
370
|
+
match = nodes
|
|
371
|
+
elsif '^' == name[0..0] # 1.8.7 thinks name[0] is a fixnum
|
|
372
|
+
case name[1..-1]
|
|
373
|
+
when 'Element'
|
|
374
|
+
match = nodes.select { |e| e.is_a?(Element) }
|
|
375
|
+
when 'String', 'Text'
|
|
376
|
+
match = nodes.select { |e| e.is_a?(String) }
|
|
377
|
+
when 'Comment'
|
|
378
|
+
match = nodes.select { |e| e.is_a?(Comment) }
|
|
379
|
+
when 'CData'
|
|
380
|
+
match = nodes.select { |e| e.is_a?(CData) }
|
|
381
|
+
when 'DocType'
|
|
382
|
+
match = nodes.select { |e| e.is_a?(DocType) }
|
|
383
|
+
else
|
|
384
|
+
# puts "*** no match on #{name}"
|
|
385
|
+
match = []
|
|
386
|
+
end
|
|
387
|
+
else
|
|
388
|
+
match = nodes.select { |e| e.is_a?(Element) and name == e.name }
|
|
389
|
+
end
|
|
390
|
+
unless qual.nil? or match.empty?
|
|
391
|
+
case qual
|
|
392
|
+
when '+'
|
|
393
|
+
match = index < match.size ? [match[index]] : []
|
|
394
|
+
when '-'
|
|
395
|
+
match = index <= match.size ? [match[-index]] : []
|
|
396
|
+
when '<'
|
|
397
|
+
match = 0 < index ? match[0..index - 1] : []
|
|
398
|
+
when '>'
|
|
399
|
+
match = index <= match.size ? match[index + 1..-1] : []
|
|
400
|
+
when '@'
|
|
401
|
+
k, v = step[i..-2].split('=')
|
|
402
|
+
if v
|
|
403
|
+
match = match.select { |n| n.is_a?(Element) && (v == n.attributes[k.to_sym] || v == n.attributes[k]) }
|
|
404
|
+
else
|
|
405
|
+
match = match.select { |n| n.is_a?(Element) && (n.attributes[k.to_sym] || n.attributes[k]) }
|
|
406
|
+
end
|
|
407
|
+
else
|
|
408
|
+
raise InvalidPath.new(path)
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
if (1 == path.size)
|
|
412
|
+
nodes.delete_if { |n| match.include?(n) }
|
|
413
|
+
elsif '*' == name
|
|
414
|
+
match.each { |n| n.del_locate(path) if n.is_a?(Element) }
|
|
415
|
+
match.each { |n| n.del_locate(path[1..-1]) if n.is_a?(Element) }
|
|
416
|
+
else
|
|
417
|
+
match.each { |n| n.del_locate(path[1..-1]) if n.is_a?(Element) }
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
private
|
|
423
|
+
|
|
424
|
+
# Builds an enumerator for use in `#each` call
|
|
425
|
+
#
|
|
426
|
+
# - +cond+ [Hash, String, nil] an element filter
|
|
427
|
+
def build_enumerator(cond)
|
|
428
|
+
if cond.nil?
|
|
429
|
+
nodes.each
|
|
430
|
+
else
|
|
431
|
+
cond = cond.to_s if cond.is_a?(Symbol)
|
|
432
|
+
Enumerator.new do |yielder|
|
|
433
|
+
if cond.is_a?(String)
|
|
434
|
+
nodes.each { |n| yielder.yield(n) if n.is_a?(Element) && cond == n.name }
|
|
435
|
+
elsif cond.is_a?(Hash)
|
|
436
|
+
nodes.each { |n| yielder.yield(n) if n.is_a?(Element) && n.attr_match(cond) }
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Removes recursively children for nodes and sub_nodes
|
|
443
|
+
#
|
|
444
|
+
# - +found+ [Array] An array of Ox::Element
|
|
445
|
+
def recursive_children_removal(found)
|
|
446
|
+
return if found.empty?
|
|
447
|
+
|
|
448
|
+
nodes.tap do |ns|
|
|
449
|
+
# found.delete(n.object_id) stops looking for an already found object_id
|
|
450
|
+
ns.delete_if { |n| found.include?(n.object_id) ? found.delete(n.object_id) : false }
|
|
451
|
+
nodes.each do |n|
|
|
452
|
+
n.send(:recursive_children_removal, found) if n.is_a?(Ox::Element)
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def name_matchs?(pat, id)
|
|
458
|
+
return false unless pat.length == id.length
|
|
459
|
+
|
|
460
|
+
pat.length.times { |i| return false unless '_' == id[i] || pat[i] == id[i] }
|
|
461
|
+
true
|
|
462
|
+
end
|
|
463
|
+
end # Element
|
|
464
|
+
end # Ox
|
data/lib/ox/error.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Ox
|
|
2
|
+
# Base error class for Ox errors.
|
|
3
|
+
class Error < StandardError
|
|
4
|
+
end # Error
|
|
5
|
+
|
|
6
|
+
# An Exception that is raised as a result of a parse error while parsing a XML document.
|
|
7
|
+
class ParseError < Error
|
|
8
|
+
end # ParseError
|
|
9
|
+
|
|
10
|
+
# An Exception that is raised as a result of an invalid argument.
|
|
11
|
+
class ArgError < Error
|
|
12
|
+
end # ArgError
|
|
13
|
+
|
|
14
|
+
# An Exception that is raised as a result of invalid XML syntax.
|
|
15
|
+
class SyntaxError < Error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# An Exception raised if a path is not valid.
|
|
19
|
+
class InvalidPath < Error
|
|
20
|
+
# Create a new instance with the +path+ specified.
|
|
21
|
+
def initialize(path)
|
|
22
|
+
super("#{path.join('/')} is not a valid location.")
|
|
23
|
+
end
|
|
24
|
+
end # InvalidPath
|
|
25
|
+
end # Ox
|