Question

say i have

html/body/span/div/p/h1/i/font
html/body/span/div/div/div/div/table/tr/p/h1
html/body/span/p/h1/b
html/body/span/div

how can i get the common ancestor? in this case span would be the common ancestor of "font, h1, b, div" would be "span"

Was it helpful?

Solution

To find common ancestry between two nodes:

(node1.ancestors & node2.ancestors).first

A more generalized function that works with multiple nodes:

# accepts node objects or selector strings
class Nokogiri::XML::Element
  def common_ancestor(*nodes)
    nodes = nodes.map do |node|
      String === node ? self.document.at(node) : node
    end

    nodes.inject(self.ancestors) do |common, node|
      common & node.ancestors
    end.first
  end
end

# usage:

node1.common_ancestor(node2, '//foo/bar')
# => <ancestor node>

OTHER TIPS

The function common_ancestor below does what you want.

require 'rubygems'
require 'nokogiri'

doc = Nokogiri::XML(DATA)

def common_ancestor *elements
  return nil if elements.empty?
  elements.map! do |e| [ e, [e] ] end #prepare array
  elements.map! do |e| # build array of ancestors for each given element
    e[1].unshift e[0] while e[0].respond_to?(:parent) and e[0] = e[0].parent
    e[1]
  end
  # merge corresponding ancestors and find the last where all ancestors are the same
  elements[0].zip(*elements[1..-1]).select { |e| e.uniq.length == 1 }.flatten.last
end

i = doc.xpath('//*[@id="i"]').first
div = doc.xpath('//*[@id="div"]').first
h1 = doc.xpath('//*[@id="h1"]').first

p common_ancestor i, div, h1 # => gives the p element

__END__
<html>
  <body>
    <span>
      <p id="common-ancestor">
        <div>
          <p><h1><i id="i"></i></h1></p>
          <div id="div"></div>
        </div>
        <p>
          <h1 id="h1"></h1>
        </p>
        <div></div>
      </p>
    </span>
  </body>
</html>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top