How do you count the number of “levels” of descendants of a Nokogiri node?

You can call Nokogiri::XML::Node#ancestors.size to see how deep the node is. But is there a way to determine how deeply nested the deepest nested child from node is?

Alternatively, how can you find all leaf nodes that go off node?

+4
source share
2 answers

You can call Nokogiri :: XML :: Node # ancestors.size for how deep the node is. But is there a way to determine how deeply nested the most deeply nested child of a node is?

Using

 count(ancestor::node()) 

This expression expresses the number of ancesstors that the (current) node context in the document hierarchy has.

To find the nesting level of the “deepest nested child”, first you need to determine all the “leaf” nodes:

 descendant-or-self::node()[not(node())] 

and for each of them get their own level of nesting using the aforementioned XPath expression.

Then the maximum level of nesting (the maximum of all numbers produced) must be calculated, and this last calculation is not possible with pure XPath 1.0 .

This can be expressed in one XPath 2.0 expression:

 max(for $leaf in /descendant-or-self::node()[not(node())], $depth in count($leaf/ancestor::node()) return $depth ) 
+1
source

The following Nokogiri::XML::Node monkey- Nokogiri::XML::Node for fun, but of course you can extract them as individual methods using the node argument if you want. (Only the height method is part of your question, but I thought the deepest_leaves method might be interesting.)

 require 'nokogiri' class Nokogiri::XML::Node def depth ancestors.size # The following is ~10x slower: xpath('count(ancestor::node())').to_i end def leaves xpath('.//*[not(*)]').to_a end def height tallest = leaves.map{ |leaf| leaf.depth }.max tallest ? tallest - depth : 0 end def deepest_leaves by_height = leaves.group_by{ |leaf| leaf.depth } by_height[ by_height.keys.max ] end end doc = Nokogiri::XML "<root> <a1> <b1></b1> <b2><c1><d1 /><d2><e1 /><e2 /></d2></c1><c2><d3><e3/></d3></c2></b2> </a1> <a2><b><c><d><e><f /></e></d></c></b></a2> </root>" a1 = doc.at_xpath('//a1') p a1.height #=> 4 p a1.deepest_leaves.map(&:name) #=> ["e1", "e2", "e3"] p a1.leaves.map(&:name) #=> ["b1", "d1", "e1", "e2", "e3"] 

Change To answer a brief question without wrapping it in reusable fragments:

 p a1.xpath('.//*[not(*)]').map{ |n| n.ancestors.size }.max - a1.ancestors.size 
+1
source

All Articles