Class: Ox::Element
Overview
An Element represents a element of an XML document. It has a name, attributes, and sub-nodes.
To access the child elements or attributes there are several options. One is to walk the nodes and attributes. Another is to use the locate() method. The easiest for simple regularly formatted XML is to reference the sub elements or attributes simply by name. Repeating elements with the same name can be referenced with an element count as well. A few examples should explain the ‘easy’ API more clearly.
Example
doc = Ox.parse(%{
<?xml?>
<People>
<Person age="58">
<given>Peter</given>
<surname>Ohler</surname>
</Person>
<Person>
<given>Makie</given>
<surname>Ohler</surname>
</Person>
</People>
})
doc.People.Person.given.text
=> "Peter"
doc.People.Person(1).given.text
=> "Makie"
doc.People.Person.age
=> "58"
Direct Known Subclasses
Instance Attribute Summary
Attributes inherited from Node
Instance Method Summary collapse
-
#<<(node) ⇒ Object
Appends a Node to the Element’s nodes array.
-
#alocate(path, found) ⇒ Object
-
path
[Array] array of steps in a path -found
[Array] matching nodes.
-
-
#attr_match(cond) ⇒ Object
Return true if all the key-value pairs in the cond Hash match the.
-
#del_locate(path) ⇒ Object
-
path
[Array] array of steps in a path.
-
-
#each(cond = nil, &block) ⇒ Object
Iterate over each child of the instance yielding according to the cond argument value.
-
#eql?(other) ⇒ Boolean
(also: #==)
Returns true if this Object and other are of the same type and have the equivalent value and the equivalent elements otherwise false is returned.
-
#initialize(name) ⇒ Element
constructor
Creates a new Element with the specified name.
-
#locate(path) ⇒ Object
Returns an array of Nodes or Strings that correspond to the locations specified by the path parameter.
-
#method_missing(id, *args, &block) ⇒ Object
Handles the ‘easy’ API that allows navigating a simple XML by referencing elements and attributes by name.
-
#nodes ⇒ Object
Returns the Element’s nodes array.
-
#prepend_child(node) ⇒ Object
Prepend a Node to the Element’s nodes array.
-
#remove_children(*children) ⇒ Object
Remove all the children matching the path provided.
-
#remove_children_by_path(path) ⇒ Object
Remove all the children matching the path provided.
-
#replace_text(txt) ⇒ Object
Clears any child nodes of an element and replaces those with a single Text (String) node.
-
#respond_to?(id, inc_all = false) ⇒ Boolean
-
id
[String|Symbol] identifer of the attribute or method -ignored
inc_all [Boolean] return true if the element has a member that matches the provided name.
-
-
#text ⇒ Object
Returns the first String in the elements nodes array or nil if there is no String node.
Methods included from HasAttrs
Constructor Details
#initialize(name) ⇒ Element
Creates a new Element with the specified name.
-
name
[String] name of the Element
41 42 43 44 45 |
# File 'lib/ox/element.rb', line 41 def initialize(name) super @attributes = {} @nodes = [] end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(id, *args, &block) ⇒ Object
Handles the ‘easy’ API that allows navigating a simple XML by referencing elements and attributes by name.
-
id
[Symbol] element or attribute name
return [Element|Node|String|nil] the element, attribute value, or Node identifed by the name
raise [NoMethodError] if no match is found
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/ox/element.rb', line 207 def method_missing(id, *args, &block) has_some = false ids = id.to_s i = args[0].to_i # will be 0 if no arg or parsing fails nodes.each do |n| if (n.is_a?(Element) || n.is_a?(Instruct)) && (n.value == id || n.value == ids || name_matchs?(n.value, ids)) return n if 0 == i has_some = true i -= 1 end end if instance_variable_defined?(:@attributes) return @attributes[id] if @attributes.has_key?(id) return @attributes[ids] if @attributes.has_key?(ids) end return nil if has_some raise NoMethodError.new("#{ids} not found", name) end |
Instance Method Details
#<<(node) ⇒ Object
Appends a Node to the Element’s nodes array. Returns the element itself so multiple appends can be chained together.
-
node
[Node] Node to append to the nodes array
60 61 62 63 64 65 |
# File 'lib/ox/element.rb', line 60 def <<(node) raise "argument to << must be a String or Ox::Node." unless node.is_a?(String) or node.is_a?(Node) @nodes = [] if !instance_variable_defined?(:@nodes) or @nodes.nil? @nodes << node self end |
#alocate(path, found) ⇒ Object
-
path
[Array] array of steps in a path -
found
[Array] matching nodes
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/ox/element.rb', line 246 def alocate(path, found) step = path[0] if step.start_with?('@') # attribute raise InvalidPath.new(path) unless 1 == path.size if instance_variable_defined?(:@attributes) step = step[1..-1] sym_step = step.to_sym @attributes.each do |k,v| found << v if ('?' == step or k == step or k == sym_step) end end else # element name if (i = step.index('[')).nil? # just name name = step qual = nil else name = step[0..i-1] raise InvalidPath.new(path) unless step.end_with?(']') i += 1 qual = step[i..i] # step[i] would be better but some rubies (jruby, ree, rbx) take that as a Fixnum. if '0' <= qual and qual <= '9' qual = '+' else i += 1 end index = step[i..-2].to_i end if '?' == name or '*' == name match = nodes elsif '^' == name[0..0] # 1.8.7 thinks name[0] is a fixnum case name[1..-1] when 'Element' match = nodes.select { |e| e.is_a?(Element) } when 'String', 'Text' match = nodes.select { |e| e.is_a?(String) } when 'Comment' match = nodes.select { |e| e.is_a?(Comment) } when 'CData' match = nodes.select { |e| e.is_a?(CData) } when 'DocType' match = nodes.select { |e| e.is_a?(DocType) } else #puts "*** no match on #{name}" match = [] end else match = nodes.select { |e| e.is_a?(Element) and name == e.name } end unless qual.nil? or match.empty? case qual when '+' match = index < match.size ? [match[index]] : [] when '-' match = index <= match.size ? [match[-index]] : [] when '<' match = 0 < index ? match[0..index - 1] : [] when '>' match = index <= match.size ? match[index + 1..-1] : [] when '@' k,v = step[i..-2].split('=') if v match = match.select { |n| n.is_a?(Element) && (v == n.attributes[k.to_sym] || v == n.attributes[k]) } else match = match.select { |n| n.is_a?(Element) && (n.attributes[k.to_sym] || n.attributes[k]) } end else raise InvalidPath.new(path) end end if (1 == path.size) match.each { |n| found << n } elsif '*' == name match.each { |n| n.alocate(path, found) if n.is_a?(Element) } match.each { |n| n.alocate(path[1..-1], found) if n.is_a?(Element) } else match.each { |n| n.alocate(path[1..-1], found) if n.is_a?(Element) } end end end |
#attr_match(cond) ⇒ Object
Return true if all the key-value pairs in the cond Hash match the
107 108 109 110 |
# File 'lib/ox/element.rb', line 107 def attr_match(cond) cond.each_pair { |k,v| return false unless v == @attributes[k.to_sym] || v == @attributes[k.to_s] } true end |
#del_locate(path) ⇒ Object
-
path
[Array] array of steps in a path
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 |
# File 'lib/ox/element.rb', line 327 def del_locate(path) step = path[0] if step.start_with?('@') # attribute raise InvalidPath.new(path) unless 1 == path.size if instance_variable_defined?(:@attributes) step = step[1..-1] sym_step = step.to_sym @attributes.delete_if { |k,v| '?' == step || k.to_sym == sym_step } end else # element name if (i = step.index('[')).nil? # just name name = step qual = nil else name = step[0..i-1] raise InvalidPath.new(path) unless step.end_with?(']') i += 1 qual = step[i..i] # step[i] would be better but some rubies (jruby, ree, rbx) take that as a Fixnum. if '0' <= qual and qual <= '9' qual = '+' else i += 1 end index = step[i..-2].to_i end if '?' == name or '*' == name match = nodes elsif '^' == name[0..0] # 1.8.7 thinks name[0] is a fixnum case name[1..-1] when 'Element' match = nodes.select { |e| e.is_a?(Element) } when 'String', 'Text' match = nodes.select { |e| e.is_a?(String) } when 'Comment' match = nodes.select { |e| e.is_a?(Comment) } when 'CData' match = nodes.select { |e| e.is_a?(CData) } when 'DocType' match = nodes.select { |e| e.is_a?(DocType) } else #puts "*** no match on #{name}" match = [] end else match = nodes.select { |e| e.is_a?(Element) and name == e.name } end unless qual.nil? or match.empty? case qual when '+' match = index < match.size ? [match[index]] : [] when '-' match = index <= match.size ? [match[-index]] : [] when '<' match = 0 < index ? match[0..index - 1] : [] when '>' match = index <= match.size ? match[index + 1..-1] : [] when '@' k,v = step[i..-2].split('=') if v match = match.select { |n| n.is_a?(Element) && (v == n.attributes[k.to_sym] || v == n.attributes[k]) } else match = match.select { |n| n.is_a?(Element) && (n.attributes[k.to_sym] || n.attributes[k]) } end else raise InvalidPath.new(path) end end if (1 == path.size) nodes.delete_if { |n| match.include?(n) } elsif '*' == name match.each { |n| n.del_locate(path) if n.is_a?(Element) } match.each { |n| n.del_locate(path[1..-1]) if n.is_a?(Element) } else match.each { |n| n.del_locate(path[1..-1]) if n.is_a?(Element) } end end end |
#each(cond = nil, &block) ⇒ Object
Iterate over each child of the instance yielding according to the cond argument value. If the cond argument is nil then all child nodes are yielded to. If cond is a string then only the child Elements with a matching name will be yielded to. If the cond is a Hash then the keys-value pairs in the cond must match the child attribute values with the same keys. Any other cond type will yield to nothing.
118 119 120 |
# File 'lib/ox/element.rb', line 118 def each(cond=nil, &block) build_enumerator(cond).each(&block) end |
#eql?(other) ⇒ Boolean Also known as: ==
Returns true if this Object and other are of the same type and have the equivalent value and the equivalent elements otherwise false is returned.
-
other
[Object] Object compare self to.
return [Boolean] true if both Objects are equivalent, otherwise false.
81 82 83 84 85 86 |
# File 'lib/ox/element.rb', line 81 def eql?(other) return false unless super(other) return false unless self.attributes == other.attributes return false unless self.nodes == other.nodes true end |
#locate(path) ⇒ Object
Returns an array of Nodes or Strings that correspond to the locations specified by the path parameter. The path parameter describes the path to the return values which can be either nodes in the XML or attributes. The path is a relative description. There are similarities between the locate() method and XPath but locate does not follow the same rules as XPath. The syntax is meant to be simpler and more Ruby like.
Like XPath the path delimiters are the slash (/) character. The path is split on the delimiter and each element of the path then describes the child of the current Element to traverse.
Attributes are specified with an @ prefix.
Each element name in the path can be followed by a bracket expression that narrows the paths to traverse. Supported expressions are numbers with a preceeding qualifier. Qualifiers are -, , <, and >. The qualifier is the default. A - qualifier indicates the index begins at the end of the children just like for Ruby Arrays. The < and > qualifiers indicates all elements either less than or greater than should be matched. Note that unlike XPath, the element index starts at 0 similar to Ruby be contrary to XPath.
Element names can also be wildcard characters. A * indicates any decendent should be followed. A ? indicates any single Element can match the wildcard. A ^ character followed by the name of a Class will match any node of the specified class. Valid class names are Element, Comment, String (or Text), CData, DocType.
Examples are:
-
element.locate("Family/Pete/*")
returns all children of the Pete Element. -
element.locate("Family/?[1]")
returns the first element in the Family Element. -
element.locate("Family/?[<3]")
returns the first 3 elements in the Family Element. -
element.locate("Family/?[@age]")
returns the elements with an age attribute defined in the Family Element. -
element.locate("Family/Kid[@age]")
returns the Kid elements with an age attribute defined in the Family Element. -
element.locate("Family/?[@age=32]")
returns the elements with an age attribute equal to 32 in the Family Element. -
element.locate("Family/Kid[@age=32]")
returns the Kid elements with an age attribute equal to 32 in the Family Element. -
element.locate("Family/?/@age")
returns the arg attribute for each child in the Family Element. -
element.locate("Family/*/@type")
returns the type attribute value for decendents of the Family. -
element.locate("Family/^Comment")
returns any comments that are a child of Family. -
path
[String] path to the Nodes to locate
162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/ox/element.rb', line 162 def locate(path) return [self] if path.nil? found = [] pa = path.split('/') if '*' == path[0] # a bit of a hack but it allows self to be checked as well e = Element.new('') e << self e.alocate(pa, found) else alocate(pa, found) end found end |
#nodes ⇒ Object
Returns the Element’s nodes array. These are the sub-elements of this Element. return [Array] all child Nodes.
52 53 54 55 |
# File 'lib/ox/element.rb', line 52 def nodes @nodes = [] if !instance_variable_defined?(:@nodes) or @nodes.nil? @nodes end |
#prepend_child(node) ⇒ Object
Prepend a Node to the Element’s nodes array. Returns the element itself so multiple appends can be chained together.
-
node
[Node] Node to prepend to the nodes array
70 71 72 73 74 75 |
# File 'lib/ox/element.rb', line 70 def prepend_child(node) raise "argument to << must be a String or Ox::Node." unless node.is_a?(String) or node.is_a?(Node) @nodes = [] if !instance_variable_defined?(:@nodes) or @nodes.nil? @nodes.unshift(node) self end |
#remove_children(*children) ⇒ Object
Remove all the children matching the path provided
Examples are:
-
element.remove_children(Ox:Element)
removes the element passed as argument if child of the element. -
element.remove_children(Ox:Element, Ox:Element)
removes the list of elements passed as argument if children of the element. -
children
[Array] array of OX
184 185 186 187 188 |
# File 'lib/ox/element.rb', line 184 def remove_children(*children) return self if children.compact.empty? recursive_children_removal(children.compact.map { |c| c.object_id }) self end |
#remove_children_by_path(path) ⇒ Object
Remove all the children matching the path provided
Examples are:
-
element.remove_children_by_path("*")
removes all children attributes. -
element.remove_children_by_path("Family/Kid[@age=32]")
removes the Kid elements with an age attribute equal to 32 in the Family Element. -
path
[String] path to the Nodes to locate
197 198 199 200 |
# File 'lib/ox/element.rb', line 197 def remove_children_by_path(path) del_locate(path.split('/')) unless path.nil? self end |
#replace_text(txt) ⇒ Object
Clears any child nodes of an element and replaces those with a single Text (String) node. Note the existing nodes array is modified and not replaced.
-
txt
[String] to become the only element of the nodes array
99 100 101 102 103 |
# File 'lib/ox/element.rb', line 99 def replace_text(txt) raise "the argument to replace_text() must be a String" unless txt.is_a?(String) @nodes.clear() @nodes << txt end |
#respond_to?(id, inc_all = false) ⇒ Boolean
-
id
[String|Symbol] identifer of the attribute or method -
ignored
inc_all [Boolean]
return true if the element has a member that matches the provided name.
229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/ox/element.rb', line 229 def respond_to?(id, inc_all=false) return true if super id_str = id.to_s id_sym = id.to_sym nodes.each do |n| next if n.is_a?(String) return true if n.value == id_str || n.value == id_sym || name_matchs?(n.value, id_str) end if instance_variable_defined?(:@attributes) && !@attributes.nil? return true if @attributes.has_key?(id_str) return true if @attributes.has_key?(id_sym) end false end |
#text ⇒ Object
Returns the first String in the elements nodes array or nil if there is no String node.
91 92 93 94 |
# File 'lib/ox/element.rb', line 91 def text() nodes.each { |n| return n if n.is_a?(String) } nil end |