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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +751 -0
  3. data/LICENSE +21 -0
  4. data/README.md +351 -0
  5. data/ext/ox/attr.h +78 -0
  6. data/ext/ox/base64.c +105 -0
  7. data/ext/ox/base64.h +18 -0
  8. data/ext/ox/buf.h +162 -0
  9. data/ext/ox/builder.c +948 -0
  10. data/ext/ox/cache.c +351 -0
  11. data/ext/ox/cache.h +21 -0
  12. data/ext/ox/cache8.c +106 -0
  13. data/ext/ox/cache8.h +23 -0
  14. data/ext/ox/dump.c +1260 -0
  15. data/ext/ox/err.c +46 -0
  16. data/ext/ox/err.h +36 -0
  17. data/ext/ox/extconf.rb +47 -0
  18. data/ext/ox/gen_load.c +342 -0
  19. data/ext/ox/hash_load.c +309 -0
  20. data/ext/ox/helper.h +84 -0
  21. data/ext/ox/intern.c +157 -0
  22. data/ext/ox/intern.h +25 -0
  23. data/ext/ox/obj_load.c +809 -0
  24. data/ext/ox/ox.c +1649 -0
  25. data/ext/ox/ox.h +245 -0
  26. data/ext/ox/parse.c +1197 -0
  27. data/ext/ox/sax.c +1570 -0
  28. data/ext/ox/sax.h +69 -0
  29. data/ext/ox/sax_as.c +270 -0
  30. data/ext/ox/sax_buf.c +209 -0
  31. data/ext/ox/sax_buf.h +204 -0
  32. data/ext/ox/sax_hint.c +207 -0
  33. data/ext/ox/sax_hint.h +40 -0
  34. data/ext/ox/sax_stack.h +113 -0
  35. data/ext/ox/slotcache.c +158 -0
  36. data/ext/ox/slotcache.h +19 -0
  37. data/ext/ox/special.c +390 -0
  38. data/ext/ox/special.h +14 -0
  39. data/ext/ox/type.h +39 -0
  40. data/lib/ox/bag.rb +103 -0
  41. data/lib/ox/cdata.rb +10 -0
  42. data/lib/ox/comment.rb +11 -0
  43. data/lib/ox/doctype.rb +11 -0
  44. data/lib/ox/document.rb +28 -0
  45. data/lib/ox/element.rb +464 -0
  46. data/lib/ox/error.rb +25 -0
  47. data/lib/ox/hasattrs.rb +54 -0
  48. data/lib/ox/instruct.rb +34 -0
  49. data/lib/ox/node.rb +23 -0
  50. data/lib/ox/raw.rb +12 -0
  51. data/lib/ox/sax.rb +97 -0
  52. data/lib/ox/version.rb +4 -0
  53. data/lib/ox/xmlrpc_adapter.rb +33 -0
  54. data/lib/ox.rb +79 -0
  55. 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
@@ -0,0 +1,10 @@
1
+ module Ox
2
+ # CData represents a CDATA element in an XML document.
3
+ class CData < Node
4
+ # Creates a CDATA element.
5
+ # - +value+ [String] value for the CDATA contents
6
+ def initialize(value)
7
+ super
8
+ end
9
+ end # CData
10
+ end # Ox
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
@@ -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