Class: REXML::Elements

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/rexml/element.rb

Overview

A class which provides filtering of children for Elements, and XPath search support. You are expected to only encounter this class as the element.elements object. Therefore, you are not expected to instantiate this yourself.

xml_string = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<bookstore>\n  <book category=\"cooking\">\n    <title lang=\"en\">Everyday Italian</title>\n    <author>Giada De Laurentiis</author>\n    <year>2005</year>\n    <price>30.00</price>\n  </book>\n  <book category=\"children\">\n    <title lang=\"en\">Harry Potter</title>\n    <author>J K. Rowling</author>\n    <year>2005</year>\n    <price>29.99</price>\n  </book>\n  <book category=\"web\">\n    <title lang=\"en\">XQuery Kick Start</title>\n    <author>James McGovern</author>\n    <author>Per Bothner</author>\n    <author>Kurt Cagle</author>\n    <author>James Linn</author>\n    <author>Vaidyanathan Nagarajan</author>\n    <year>2003</year>\n    <price>49.99</price>\n  </book>\n  <book category=\"web\" cover=\"paperback\">\n    <title lang=\"en\">Learning XML</title>\n    <author>Erik T. Ray</author>\n    <year>2003</year>\n    <price>39.95</price>\n  </book>\n</bookstore>\n"
d = REXML::Document.new(xml_string)
elements = d.root.elements
elements # => #<REXML::Elements @element=<bookstore> ... </>>

Instance Method Summary collapse

Constructor Details

#initialize(parent) ⇒ Elements

:call-seq:

new(parent) -> new_elements_object

Returns a new Elements object with the given parent. Does not assign parent.elements = self:

d = REXML::Document.new(xml_string)
eles = REXML::Elements.new(d.root)
eles # => #<REXML::Elements @element=<bookstore> ... </>>
eles == d.root.elements # => false


1602
1603
1604
# File 'lib/rexml/element.rb', line 1602

def initialize parent
  @element = parent
end

Instance Method Details

#[](index, name = nil) ⇒ Object

:call-seq:

elements[index] -> element or nil
elements[xpath] -> element or nil
elements[n, name] -> element or nil

Returns the first Element object selected by the arguments, if any found, or nil if none found.

Notes:

  • The index is 1-based, not 0-based, so that:

    • The first element has index 1

    • The nth element has index n.

  • The selection ignores non-Element nodes.

When the single argument index is given, returns the element given by the index, if any; otherwise, nil:

d = REXML::Document.new(xml_string)
eles = d.root.elements
eles # => #<REXML::Elements @element=<bookstore> ... </>>
eles[1] # => <book category='cooking'> ... </>
eles.size # => 4
eles[4] # => <book category='web' cover='paperback'> ... </>
eles[5] # => nil

The node at this index is not an Element, and so is not returned:

eles = d.root.first.first # => <title lang='en'> ... </>
eles.to_a # => ["Everyday Italian"]
eles[1] # => nil

When the single argument xpath is given, returns the first element found via that xpath, if any; otherwise, nil:

eles = d.root.elements # => #<REXML::Elements @element=<bookstore> ... </>>
eles['/bookstore']                    # => <bookstore> ... </>
eles['//book']                        # => <book category='cooking'> ... </>
eles['//book [@category="children"]'] # => <book category='children'> ... </>
eles['/nosuch']                       # => nil
eles['//nosuch']                      # => nil
eles['//book [@category="nosuch"]']   # => nil
eles['.']                             # => <bookstore> ... </>
eles['..'].class                      # => REXML::Document

With arguments n and name given, returns the nth found element that has the given name, or nil if there is no such nth element:

eles = d.root.elements # => #<REXML::Elements @element=<bookstore> ... </>>
eles[1, 'book'] # => <book category='cooking'> ... </>
eles[4, 'book'] # => <book category='web' cover='paperback'> ... </>
eles[5, 'book'] # => nil


1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
# File 'lib/rexml/element.rb', line 1674

def []( index, name=nil)
  if index.kind_of? Integer
    raise "index (#{index}) must be >= 1" if index < 1
    name = literalize(name) if name
    num = 0
    @element.find { |child|
      child.kind_of? Element and
      (name.nil? ? true : child.has_name?( name )) and
      (num += 1) == index
    }
  else
    XPath::first( @element, index )
  end
end

#[]=(index, element) ⇒ Object

:call-seq:

elements[] = index, replacement_element -> replacement_element or nil

Replaces or adds an element.

When eles[index] exists, replaces it with replacement_element and returns replacement_element:

d = REXML::Document.new(xml_string)
eles = d.root.elements # => #<REXML::Elements @element=<bookstore> ... </>>
eles[1] # => <book category='cooking'> ... </>
eles[1] = REXML::Element.new('foo')
eles[1] # => <foo/>

Does nothing (or raises an exception) if replacement_element is not an Element:

eles[2] # => <book category='web' cover='paperback'> ... </>
eles[2] = REXML::Text.new('bar')
eles[2] # => <book category='web' cover='paperback'> ... </>

When eles[index] does not exist, adds replacement_element to the element and returns

d = REXML::Document.new(xml_string)
eles = d.root.elements # => #<REXML::Elements @element=<bookstore> ... </>>
eles.size # => 4
eles[50] = REXML::Element.new('foo') # => <foo/>
eles.size # => 5
eles[5] # => <foo/>

Does nothing (or raises an exception) if replacement_element is not an Element:

eles[50] = REXML::Text.new('bar') # => "bar"
eles.size # => 5


1725
1726
1727
1728
1729
1730
1731
1732
1733
# File 'lib/rexml/element.rb', line 1725

def []=( index, element )
  previous = self[index]
  if previous.nil?
    @element.add element
  else
    previous.replace_with element
  end
  previous
end

#add(element = nil) ⇒ Object Also known as: <<

:call-seq:

add -> new_element
add(name) -> new_element
add(element) -> element

Adds an element; returns the element added.

With no argument, creates and adds a new element. The new element has:

  • No name.

  • Parent from the Elements object.

  • Context from the that parent.

Example:

d = REXML::Document.new(xml_string)
elements = d.root.elements
parent = elements.parent     # => <bookstore> ... </>
parent.context = {raw: :all}
elements.size                # => 4
new_element = elements.add   # => </>
elements.size                # => 5
new_element.name             # => nil
new_element.parent           # => <bookstore> ... </>
new_element.context          # => {:raw=>:all}

With string argument name, creates and adds a new element. The new element has:

  • Name name.

  • Parent from the Elements object.

  • Context from the that parent.

Example:

d = REXML::Document.new(xml_string)
elements = d.root.elements
parent = elements.parent          # => <bookstore> ... </>
parent.context = {raw: :all}
elements.size                     # => 4
new_element = elements.add('foo') # => <foo/>
elements.size                     # => 5
new_element.name                  # => "foo"
new_element.parent                # => <bookstore> ... </>
new_element.context               # => {:raw=>:all}

With argument element, creates and adds a clone of the given element. The new element has name, parent, and context from the given element.

d = REXML::Document.new(xml_string)
elements = d.root.elements
elements.size                 # => 4
e0 = REXML::Element.new('foo')
e1 = REXML::Element.new('bar', e0, {raw: :all})
element = elements.add(e1) # => <bar/>
elements.size                 # => 5
element.name                  # => "bar"
element.parent                # => <bookstore> ... </>
element.context               # => {:raw=>:all}


1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
# File 'lib/rexml/element.rb', line 1915

def add element=nil
  if element.nil?
    Element.new("", self, @element.context)
  elsif not element.kind_of?(Element)
    Element.new(element, self, @element.context)
  else
    @element << element
    element.context = @element.context
    element
  end
end

#collect(xpath = nil) ⇒ Object

:call-seq:

collect(xpath = nil) {|element| ... } -> array

Iterates over the elements; returns the array of block return values.

With no argument, iterates over all elements:

d = REXML::Document.new(xml_string)
elements = d.root.elements
elements.collect {|element| element.size } # => [9, 9, 17, 9]

With argument xpath, iterates over elements that match the given xpath:

xpath = '//book [@category="web"]'
elements.collect(xpath) {|element| element.size } # => [17, 9]


1978
1979
1980
1981
1982
1983
1984
# File 'lib/rexml/element.rb', line 1978

def collect( xpath=nil )
  collection = []
  XPath::each( @element, xpath ) {|e|
    collection << yield(e)  if e.kind_of?(Element)
  }
  collection
end

#delete(element) ⇒ Object

:call-seq:

delete(index) -> removed_element or nil
delete(element) -> removed_element or nil
delete(xpath) -> removed_element or nil

Removes an element; returns the removed element, or nil if none removed.

With integer argument index given, removes the child element at that offset:

d = REXML::Document.new(xml_string)
elements = d.root.elements
elements.size # => 4
elements[2] # => <book category='children'> ... </>
elements.delete(2) # => <book category='children'> ... </>
elements.size # => 3
elements[2] # => <book category='web'> ... </>
elements.delete(50) # => nil

With element argument element given, removes that child element:

d = REXML::Document.new(xml_string)
elements = d.root.elements
ele_1, ele_2, ele_3, ele_4 = *elements
elements.size # => 4
elements[2] # => <book category='children'> ... </>
elements.delete(ele_2) # => <book category='children'> ... </>
elements.size # => 3
elements[2] # => <book category='web'> ... </>
elements.delete(ele_2) # => nil

With string argument xpath given, removes the first element found via that xpath:

d = REXML::Document.new(xml_string)
elements = d.root.elements
elements.delete('//book') # => <book category='cooking'> ... </>
elements.delete('//book [@category="children"]') # => <book category='children'> ... </>
elements.delete('//nosuch') # => nil


1815
1816
1817
1818
1819
1820
1821
1822
# File 'lib/rexml/element.rb', line 1815

def delete element
  if element.kind_of? Element
    @element.delete element
  else
    el = self[element]
    el.remove if el
  end
end

#delete_all(xpath) ⇒ Object

:call-seq:

delete_all(xpath)

Removes all elements found via the given xpath; returns the array of removed elements, if any, else nil.

d = REXML::Document.new(xml_string)
elements = d.root.elements
elements.size # => 4
deleted_elements = elements.delete_all('//book [@category="web"]')
deleted_elements.size # => 2
elements.size # => 2
deleted_elements = elements.delete_all('//book')
deleted_elements.size # => 2
elements.size # => 0
elements.delete_all('//book') # => []


1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
# File 'lib/rexml/element.rb', line 1841

def delete_all( xpath )
  rv = []
  XPath::each( @element, xpath) {|element|
    rv << element if element.kind_of? Element
  }
  rv.each do |element|
    @element.delete element
    element.remove
  end
  rv
end

#each(xpath = nil) ⇒ Object

:call-seq:

each(xpath = nil) {|element| ... } -> self

Iterates over the elements.

With no argument, calls the block with each element:

d = REXML::Document.new(xml_string)
elements = d.root.elements
elements.each {|element| p element }

Output:

<book category='cooking'> ... </>
<book category='children'> ... </>
<book category='web'> ... </>
<book category='web' cover='paperback'> ... </>

With argument xpath, calls the block with each element that matches the given xpath:

elements.each('//book [@category="web"]') {|element| p element }

Output:

<book category='web'> ... </>
<book category='web' cover='paperback'> ... </>


1957
1958
1959
# File 'lib/rexml/element.rb', line 1957

def each( xpath=nil )
  XPath::each( @element, xpath ) {|e| yield e if e.kind_of? Element }
end

#empty?Boolean

:call-seq:

empty? -> true or false

Returns true if there are no children, false otherwise.

d = REXML::Document.new('')
d.elements.empty? # => true
d = REXML::Document.new(xml_string)
d.elements.empty? # => false


1745
1746
1747
# File 'lib/rexml/element.rb', line 1745

def empty?
  @element.find{ |child| child.kind_of? Element}.nil?
end

#index(element) ⇒ Object

:call-seq:

index(element)

Returns the 1-based index of the given element, if found; otherwise, returns -1:

d = REXML::Document.new(xml_string)
elements = d.root.elements
ele_1, ele_2, ele_3, ele_4 = *elements
elements.index(ele_4) # => 4
elements.delete(ele_3)
elements.index(ele_4) # => 3
elements.index(ele_3) # => -1


1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
# File 'lib/rexml/element.rb', line 1763

def index element
  rv = 0
  found = @element.find do |child|
    child.kind_of? Element and
    (rv += 1) and
    child == element
  end
  return rv if found == element
  -1
end

#inject(xpath = nil, initial = nil) ⇒ Object

:call-seq:

inject(xpath = nil, initial = nil) -> object

Calls the block with elements; returns the last block return value.

With no argument, iterates over the elements, calling the block elements.size - 1 times.

  • The first call passes the first and second elements.

  • The second call passes the first block return value and the third element.

  • The third call passes the second block return value and the fourth element.

  • And so on.

In this example, the block returns the passed element, which is then the object argument to the next call:

d = REXML::Document.new(xml_string)
elements = d.root.elements
elements.inject do |object, element|
  p [elements.index(object), elements.index(element)]
  element
end

Output:

[1, 2]
[2, 3]
[3, 4]

With the single argument xpath, calls the block only with elements matching that xpath:

elements.inject('//book [@category="web"]') do |object, element|
  p [elements.index(object), elements.index(element)]
  element
end

Output:

[3, 4]

With argument xpath given as nil and argument initial also given, calls the block once for each element.

  • The first call passes the initial and the first element.

  • The second call passes the first block return value and the second element.

  • The third call passes the second block return value and the third element.

  • And so on.

In this example, the first object index is -1

elements.inject(nil, 'Initial') do |object, element|
  p [elements.index(object), elements.index(element)]
  element
end

Output:

[-1, 1]
[1, 2]
[2, 3]
[3, 4]

In this form the passed object can be used as an accumulator:

elements.inject(nil, 0) do |total, element|
  total += element.size
end # => 44

With both arguments xpath and initial are given, calls the block only with elements matching that xpath:

elements.inject('//book [@category="web"]', 0) do |total, element|
  total += element.size
end # => 26


2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
# File 'lib/rexml/element.rb', line 2063

def inject( xpath=nil, initial=nil )
  first = true
  XPath::each( @element, xpath ) {|e|
    if (e.kind_of? Element)
      if (first and initial == nil)
        initial = e
        first = false
      else
        initial = yield( initial, e ) if e.kind_of? Element
      end
    end
  }
  initial
end

#parentObject

:call-seq:

parent

Returns the parent element cited in creating the Elements object. This element is also the default starting point for searching in the Elements object.

d = REXML::Document.new(xml_string)
elements = REXML::Elements.new(d.root)
elements.parent == d.root # => true


1617
1618
1619
# File 'lib/rexml/element.rb', line 1617

def parent
  @element
end

#sizeObject

:call-seq:

size -> integer

Returns the count of Element children:

d = REXML::Document.new '<a>sean<b/>elliott<b/>russell<b/></a>'
d.root.elements.size # => 3 # Three elements.
d.root.size          # => 6 # Three elements plus three text nodes..


2087
2088
2089
2090
2091
# File 'lib/rexml/element.rb', line 2087

def size
  count = 0
  @element.each {|child| count+=1 if child.kind_of? Element }
  count
end

#to_a(xpath = nil) ⇒ Object

:call-seq:

to_a(xpath = nil) -> array_of_elements

Returns an array of element children (not including non-element children).

With no argument, returns an array of all element children:

d = REXML::Document.new '<a>sean<b/>elliott<c/></a>'
elements = d.root.elements
elements.to_a # => [<b/>, <c/>]               # Omits non-element children.
children = d.root.children
children # => ["sean", <b/>, "elliott", <c/>] # Includes non-element children.

With argument xpath, returns an array of element children that match the xpath:

elements.to_a('//c') # => [<c/>]


2111
2112
2113
2114
2115
# File 'lib/rexml/element.rb', line 2111

def to_a( xpath=nil )
  rv = XPath.match( @element, xpath )
  return rv.find_all{|e| e.kind_of? Element} if xpath
  rv
end