Vincent
Vincent

Reputation: 16216

Parsing simple XML with Nokogiri

I have the following XML:

<links>

  <item>
    <title>Title 1</title>
    <url>http://www.example.com/url-1</url>
  </item>

  <item>
   <title>Title 2</title>
   <url>http://www.example.com/url-2</url>
  </item>

  <item>
    <title>Title 3</title>
    <url>http://www.example.com/url-3</url>
  </item>

</links>

And, I would like to convert it to a HTML list:

<ul>
  <li><a href="http://www.example.com/url-1">Title 1</a></li>
  <li><a href="http://www.example.com/url-2">Title 2</a></li>
  <li><a href="http://www.example.com/url-3">Title 3</a></li>
</ul>

Currently I have this:

Controller:

require 'nokogiri'
doc = Nokogiri::XML(...)

@links = doc.xpath('//links/item').map do |i|
  {'title' => i.xpath('//title'), 'url' => i.xpath('//url')}
end

Template:

<ul>
  <% @links.each do |l| %>
    <li><a href="<%= l['url'] %>"><%= l['title'] %></a></li>
  <% end %>
</ul> 

Resulting HTML:

<ul>
  <li><a href="http://www.example.com/url-1http://www.example.com/url-2http://www.example.com/url-3">Title 1Title 2Title 3</a></li>
  <li><a href="http://www.example.com/url-1http://www.example.com/url-2http://www.example.com/url-3">Title 1Title 2Title 3</a></li>
  <li><a href="http://www.example.com/url-1http://www.example.com/url-2http://www.example.com/url-3">Title 1Title 2Title 3</a></li>
</ul>

What am I doing wrong? Is there a more optimal way of doing this?

Upvotes: 17

Views: 13025

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243459

Replace this:

@links = doc.xpath('//links/item').map do |i| 
  {'title' => i.xpath('//title'), 'url' => i.xpath('//url')} 

with:

@links = doc.xpath('//links/item').map do |i| 
  {'title' => i.xpath('title'), 'url' => i.xpath('url')} 

Explanation:

//title 

and

//url

are absolute XPath expressions and they select all (respectively) title and all url elements in the XML document.

Contrast this with:

title

and

url

These are relative XPath expressions and select all (respectively) title and url children of the current node only.

Upvotes: 29

Matchu
Matchu

Reputation: 85784

The trouble here is that the Xpath //title searches for titles from the root of the document, and so returns all title tags. Using the Xpath title searches within the context of the given node, like you want. Ditto on url.

@links = doc.xpath('//links/item').map do |i|
  {'title' => i.xpath('title'), 'url' => i.xpath('url')}
end

Upvotes: 6

Related Questions