thorstenhirsch
thorstenhirsch

Reputation: 198

Nokogiri and concat()

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

Answers (2)

matt
matt

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

akuhn
akuhn

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

Related Questions