Module: Xpath

Included in:
Rind::Cdata, Rind::Comment, Rind::Element, Rind::ProcessingInstruction, Rind::Text
Defined in:
lib/rind/xpath.rb

Overview

Current Xpath support is fairly basic but covers almost all axes and node tests. Predicates are limited to attribute and position checks. I intend to expand support but that should cover most of the needed functionality.

Instance Method Summary collapse

Instance Method Details

#s(path) ⇒ Object

Xpath search of a node that returns a list of matching nodes.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/rind/xpath.rb', line 6

def s(path)
  node = self

  # absolute paths to the top
  if '/' == path[0,1]
    while not node.parent.nil?
      node = node.parent
    end
    if '/' != path[1,1]
      path[0] = 'self::'
    end
  end

  # node check
  nodes = [node]
  path.scan(%r{(?:^\/?|\/)
            (?:([^\/]*?)::)? # axis
            ([^\/\[]+)?      # node test
            ((?:\[.+?\])*)   # predicates
  }x) do |axis, node_test, predicates|
    case node_test
    when nil
      axis = 'descendant-or-self'
      node_test = 'node()'
    when '.'
      axis = 'self'
      node_test = 'node()'
    when '..'
      axis = 'parent'
      node_test = 'node()'
    end

    axis = 'child' if axis.nil?

    node_test.gsub!(/^@/, 'attribute::')
    predicates.gsub!(/^@/, 'attribute::')

    # find matching nodes
    nodes = nodes.collect{|node| node.find_matching_nodes(axis, node_test)}.flatten.compact.uniq

    # check predicates
    if not predicates.nil?
      # true() and false()
      predicates.gsub!(/(true|false)\(\)/, '\1')
      # ==
      predicates.gsub!(/=/, '==')

      predicates.scan(/\[(.*?)\]/) do |predicate|
        predicate = predicate[0]
        # last()
        predicate.gsub!(/last\(\)/, nodes.length.to_s)

        nodes = nodes.find_all do |node|
          node.validate_predicate(predicate.clone, Rind::Nodes[*nodes].exact_index(node)+1)
        end
        break if nodes.empty?
      end
    end

    return Rind::Nodes.new if nodes.empty?
  end

  Rind::Nodes.new(nodes)
end

#sf(path) ⇒ Object

Xpath search returning only the first matching node in the list.



72
73
74
# File 'lib/rind/xpath.rb', line 72

def sf(path)
  self.s(path).first
end