How to create xml element with attribute and value in Nokogiri
Asked Answered
I

3

6

I'm using Rails 2.1.0 and Nokogiri 1.6.1. What I want seems pretty simple. I want my Rails Rest API to return XML with an element like this:

<PeopleNumber unit="NumberOfPeople">2.235075</PeopleNumber>

I tried writing something like:

xml = Nokogiri::NML::Builder.new do |xml|
  xml.PeopleNumber(:unit => "NumberOfPeople") 2.235075

ActionController fires off a syntax error.

If I try re-writing this as

xml = Nokogiri::NML::Builder.new do |xml|
  xml.PeopleNumber(:unit => "NumberOfPeople") { 2.235075 }

I get something like

<PeopleNumber unit="NumberOfPeople" />

Does anyone know of a way to get the desired behavior in Nokogiri?

Illuminate answered 30/9, 2014 at 10:11 Comment(3)
Please check your code, and format it properly so we could help. stackoverflow.com/help/formattingRevengeful
possible duplicate of set tag attribute and add plain text content to the tag using nokogiri builder (ruby)Fusion
Thanks @JustinKo. Your solution worked great. Pretty easy, once you have the solution!Illuminate
H
3

I know this is quite old question, but even I stumbled upon same problem.
I found a clean solution.

builder = Nokogiri::NML::Builder.new do |xml|
  xml.PeopleNumber(2.235075, :unit => "NumberOfPeople")
end

now if you debug the XML output you will get the right output

puts builder.to_xml
# output will be following
<PeopleNumber unit="NumberOfPeople">2.235075</PeopleNumber>

Explanation: When you want to set some attributes and text content to XML tag using Nokogiri builder then you need to directly pass text content as first argument and then other attributes as key-value pairs same as we pass to any method.

builder = Nokogiri::NML::Builder.new do |xml|
  xml.YourTagName(PLAIN_TEXT_CONTENT, attr1: value1, attr2: value2, attrN: valueN)
end

This will output as

puts builder.to_xml
# output will be following
<YourTagName attr1="value1" attr2="value2" attrN="valueN">PLAIN_TEXT_CONTENT</YourTagName>

Even there are other ways but this is the cleanest one.

Hotchpotch answered 14/10, 2020 at 13:7 Comment(0)
S
1

Do it the simple way:

require 'nokogiri'

doc = Nokogiri::XML('<foo></foo>')
doc.at('foo').add_child('<PeopleNumber unit="NumberOfPeople">2.235075</PeopleNumber>')
puts doc.to_xml
# >> <?xml version="1.0"?>
# >> <foo>
# >>   <PeopleNumber unit="NumberOfPeople">2.235075</PeopleNumber>
# >> </foo>

The trick is add_child, which can take a predefined node, or a string consisting of the XML you want to add. From the documentation:

Add node_or_tags as a child of this Node. node_or_tags can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.

"a string containing markup" is a free-pass to doing it an easy way.

If you need a different value for the unit parameter, or a different value for the tag itself, you can interpolate those into the string:

foo = 'WheelSize'
bar = '355/113'

doc = Nokogiri::XML('<foo></foo>')
doc.at('foo').add_child("<PeopleNumber unit='#{foo}'>#{bar}</PeopleNumber>")
puts doc.to_xml
# >> <?xml version="1.0"?>
# >> <foo>
# >>   <PeopleNumber unit="WheelSize">355/113</PeopleNumber>
# >> </foo>

Or you can directly modify the DOM and nodes:

doc = Nokogiri::XML('<foo><PeopleNumber /></foo>')
people_number = doc.at('PeopleNumber')
people_number['unit'] = 'fred'
people_number.content = 'ethel'
puts doc.to_xml
# >> <?xml version="1.0"?>
# >> <foo>
# >>   <PeopleNumber unit="fred">ethel</PeopleNumber>
# >> </foo>

There are other ways to do this in addition, but it's really up to you to use whatever fits your head best.

Sociology answered 30/9, 2014 at 16:47 Comment(1)
I guess I could do this too. But it seems messier? I don't know. I also wondered if this is a good way to create a response from an API controller. Not a criticism, just a question @theTinMan.Illuminate
T
0

I just found a way to do it in my code:

xml = Nokogiri::NML::Builder.new do |xml|
  child = xml.PeopleNumber 2.235075
  child["unit"] = "NumberOfPeople"

It generated the appropriate XML you were requesting. Not sure if that helps you in your case but the "simple" answer is not how my own functional code is running.

Thane answered 17/9, 2020 at 16:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.