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 = <<-EOT
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
  <book category="cooking">
    <title lang="en">Everyday Italian</title>
    <author>Giada De Laurentiis</author>
    <year>2005</year>
    <price>30.00</price>
  </book>
  <book category="children">
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
  </book>
  <book category="web">
    <title lang="en">XQuery Kick Start</title>
    <author>James McGovern</author>
    <author>Per Bothner</author>
    <author>Kurt Cagle</author>
    <author>James Linn</author>
    <author>Vaidyanathan Nagarajan</author>
    <year>2003</year>
    <price>49.99</price>
  </book>
  <book category="web" cover="paperback">
    <title lang="en">Learning XML</title>
    <author>Erik T. Ray</author>
    <year>2003</year>
    <price>39.95</price>
  </book>
</bookstore>
EOT
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


1608
1609
1610
# File 'lib/rexml/element.rb', line 1608

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


1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
# File 'lib/rexml/element.rb', line 1680

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
    return XPath::first( @element, index )
    #{ |element|
    #       return element if element.kind_of? Element
    #}
    #return nil
  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


1735
1736
1737
1738
1739
1740
1741
1742
1743
# File 'lib/rexml/element.rb', line 1735

def []=( index, element )
  previous = self[index]
  if previous.nil?
    @element.add element
  else
    previous.replace_with element
  end
  return 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}


1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
# File 'lib/rexml/element.rb', line 1925

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]


1988
1989
1990
1991
1992
1993
1994
# File 'lib/rexml/element.rb', line 1988

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


1825
1826
1827
1828
1829
1830
1831
1832
# File 'lib/rexml/element.rb', line 1825

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') # => []


1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
# File 'lib/rexml/element.rb', line 1851

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
  return 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'> ... </>


1967
1968
1969
# File 'lib/rexml/element.rb', line 1967

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

Returns:

  • (Boolean)


1755
1756
1757
# File 'lib/rexml/element.rb', line 1755

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


1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
# File 'lib/rexml/element.rb', line 1773

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
  return -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


2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
# File 'lib/rexml/element.rb', line 2073

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


1623
1624
1625
# File 'lib/rexml/element.rb', line 1623

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..


2097
2098
2099
2100
2101
# File 'lib/rexml/element.rb', line 2097

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/>]


2121
2122
2123
2124
2125
# File 'lib/rexml/element.rb', line 2121

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