Search code examples
htmlrubyxpathnokogiri

Iterations of elements in Nokogiri xpath


I tried to make iteration of some <li> elements

Iteration won't work as expected:

require 'nokogiri'

doc = Nokogiri::HTML(<<-END_OF_HTML)
  <ul class="attribute_radio_list">
    <li>
      <input type="radio" name="group_4" value="709" id="comb_709_group_4" checked="checked">
      <label for="comb_709_group_4" class="label_comb_price label_comb_price_punda comb_709_group_4 checked">
        <span class="radio_label">1-10 kg</span>
        <span class="price_comb">14.85 €</span>
      </label>
      <span class="pundaline-variations-tooltip">1-10 kg</span>
    </li>
    <li class=" comb_710_group_4_li" id="comb_710_group_4_li">
      <input type=" radio" name="group_4" value="710" id="comb_710_group_4">
      <label for="comb_710_group_4">
        <span class="radio_label">10-20 kg</span>
        <span class="price_comb">17.82 €</span>
      </label>
      <span class="pundaline-variations-tooltip">10-20 kg</span>
    </li>
    <li id="comb_711_group_4_li">
      <input type=" radio" name="group_4" value="711" id="comb_711_group_4">
      <label for="comb_711_group_4">
        <span class="radio_label">20-40 kg</span>
        <span class="price_comb">19.80 €</span>
      </label>
      <span class="pundaline-variations-tooltip">20-40 kg</span></li>
  </ul>
END_OF_HTML

lis = doc.xpath("//li")

lis.each do |li|
  p li.xpath("//span[@class = 'price_comb']/text()").to_s
end

returns this:

"14.85 €17.82 €19.80 €"
"14.85 €17.82 €19.80 €"
"14.85 €17.82 €19.80 €"

But I'm expected to see this:

"14.85 €"
"17.82 €"
"19.80 €"

Why xpath works so strange and how can I fix that?


Solution

  • You are missing a dot . at the beginning of your XPath expression.
    Instead of

    "//span[@class = 'price_comb']/text()"
    

    It should be

    ".//span[@class = 'price_comb']/text()"
    

    So the entire code piece will be:

    lis.each do |li|
      p li.xpath(".//span[@class = 'price_comb']/text()").to_s
    end
    

    This XPath expression //span[@class = 'price_comb']/text() is searching from the top of the document, not inside a specific node.
    To make it search inside a node you should start the expression with a dot .: .//span[@class = 'price_comb']/text()
    UPDATE
    As mentioned by engineersmnky and may be useful:

    • The dot . is a relative path, meaning it will only search inside the node.
    • The double slash "//" means anywhere inside this node;
    • where as a single slash "/" would be just direct descendants.
    • Xpath Cheatsheet might help give you the basics