Reputation: 198
How can I use concat() in a Nokogiri xpath expression? I've tried:
xml.xpath("concat(/root/a/text(), /root/b/text())")
But that fails with:
ArgumentError: node_set must be a Nokogiri::XML::NodeSet
Actually it seems like Nokogiri accepts only xpath expressions that start with "." or "/", so I tried these:
xml.xpath(".concat(/root/a/text(), /root/b/text())")
xml.xpath("/concat(/root/a/text(), /root/b/text())")
xml.xpath("/concat('foo', 'bar')")
But they all fail with this error:
Nokogiri::XML::XPath::SyntaxError: Invalid expression: /concat('foo', 'bar')
I know Nokogiri is based on libxml2 and as such it only implements XPath 1.0. But concat() is part of XPath 1.0. However, a similar function in XPath 2.0 is string-join() and I gave it a chance:
xml.xpath("string-join('foo', 'bar')")
Error message:
RuntimeError: xmlXPathCompOpEval: function string-join not found
See, it's a different error message than the one for concat(). So at least the function concat() is being found. One more hint that it might work somehow is...
xml.xpath("concat()")
...returns the expected error message:
Nokogiri::XML::XPath::SyntaxError: Invalid number of arguments: concat()
Any chance to get concat() working with arguments?
P.S.: Basic xpath expressions like xml.xpath("/root/a/text()")
work fine.
Upvotes: 3
Views: 857
Reputation: 79783
An XPath query commonly returns a node-set, a collection of nodes from the document. In Nokogiri this is represented by the Nokogiri::XML::NodeSet
object.
Nokogiri also allows you to use a NodeSet
with the xpath
method. In this case Nokogiri executes the query individually against each of the nodes in the NodeSet
in turn, and combines them all into a new NodeSet
which it returns as the result.
Normally this works as you would expect, with the result being the combination of all the nodes that match the query. However, this doesn’t work when using an XPath query that returns something other than a node-set, as in this case.
After executing the query, Nokogiri tries to add the result (which it expects to be a NodeSet
) to the newly created NodeSet
. When the result is actually a string this fails with the error you are getting.
This has been brought up in a Nokogiri bug, and added to their roadmap, but there is no resolution as yet.
The workaround/solution is to make all your non-node-set returning XPath queries on single nodes rather than node-sets. at_xpath
will return the first matching node for a query, and that node can be used for further xpath
calls without this problem.
Upvotes: 2
Reputation: 27803
Seem to work for me
body = "<root><a>aaa</a><b>bbb</b></root>"
xml = Nokogiri::XML(body)
xml.xpath("concat(/root/a/text(), /root/b/text())") # => "aaabbb"
Given your update, maybe try this?
body = "<wrapper><root><a>aaa</a><b>bbb</b></root></wrapper>"
xml = Nokogiri::XML(body)
xml = xml.xpath("wrapper") # returns a node set
xml.map { |each| each.xpath("concat(./root/a/text(), ./root/b/text())") } # => ["aaabbb"]
NB, notice the leading .
to anchor the query at the current node.
Upvotes: 1