Ruby - Nokogiri xml search with 2 conditions

Viewed 47

I have an xml file.

<generic:Obs>
  <generic:ObsKey>
    <generic:Value id="TIME_PERIOD" value="2020"/>
    <generic:Value id="SPECIFICATION" value="SSL05"/>
    <generic:Value id="MONTH" value="A02"/>
    <generic:Value id="FREQ" value="A"/>
  </generic:ObsKey>
  <generic:ObsValue value="100.56"/>
  <generic:Attributes>
    <generic:Value id="NOTE_SPECIFICATION_2" value="SSL05"/>
    <generic:Value id="DECIMALS" value="2"/>
  </generic:Attributes>
</generic:Obs>

I need to show the <generic:ObsValue value="x"/> from the group <generic:Obs> if the <generic:Value id="TIME_PERIOD" value="2020"/> and <generic:Value id="MONTH" value="A02"/> match.

doc = Nokogiri::XML(URI.open("https://xml.de"))
@ObsValue = doc.xpath('//obs//obskey/@id="TIME_PERIOD"')..... 

I really cant find the solution. Any idea?

1 Answers

So one issue is you have an undefined namespace generic:. For the sake of this example I will add one to your XML.

DOC = %Q(
<generic:Obs xmlns:generic="http://example/root">
  <generic:ObsKey>
    <generic:Value id="TIME_PERIOD" value="2020"/>
    <generic:Value id="SPECIFICATION" value="SSL05"/>
    <generic:Value id="MONTH" value="A02"/>
    <generic:Value id="FREQ" value="A"/>
  </generic:ObsKey>
  <generic:ObsValue value="100.56"/>
  <generic:Attributes>
    <generic:Value id="NOTE_SPECIFICATION_2" value="SSL05"/>
    <generic:Value id="DECIMALS" value="2"/>
  </generic:Attributes>
</generic:Obs>
)

Personally unless you have a reason to retain them I would just remove the namespaces for ease of parsing

doc = Nokogiri.parse(DOC)
doc.remove_namespaces!

Then we can use the following XPath:

//Obs
  /ObsKey[
    ./Value[@id="TIME_PERIOD" and @value="2020"] and 
    ./Value[@id="MONTH" and @value="A02"]
  ]
  /following-sibling::ObsValue/@value

To break this down:

  1. //Obs - Start at the Obs node
  2. /ObsKey[./Value[@id="TIME_PERIOD" and @value="2020"] and ./Value[@id="MONTH" and @value="A02"]] find ObsKey child Node where the relative child Value nodes meet the desired criteria e.g. (Value.id = "TIME_PERIOD" AND Value.value = "2020") AND (Value.id = "MONTH" AND Value.value = "A02")
  3. /following-sibling::ObsValue/@value relative to that Node (if one exists) select the following sibling ObsValue node and then select its @value attribute

Example: (Working Example)

Success:

doc.at_xpath('//Obs
  /ObsKey[
    ./Value[@id="TIME_PERIOD" and @value="2020"] and 
    ./Value[@id="MONTH" and @value="A02"]
  ]
  /following-sibling::ObsValue/@value')&.value
#=> "100.56" 

Failure:

# changed the TIME_PERIOD value 
doc.at_xpath('//Obs
  /ObsKey[
    ./Value[@id="TIME_PERIOD" and @value="2022"] and 
    ./Value[@id="MONTH" and @value="A02"]
  ]
  /following-sibling::ObsValue/@value')&.value
#=> nil
Related