Rails XML Feed: ID as node attribute - ruby-on-rails

I set up a simple XML feed for a vendor we're using (who refuses to read JSON).
<recipes type="array">
<recipe>
<id type="integer">1</id>
<name>
Hamburgers
</name>
<producturl>
http://test.com
</producturl>
...
</recipe>
...
<recipe>
However, the vendor requests that instead of having an id node, id is an attribute in the parent node. e.g.
<recipes type="array">
<recipe id="1">
<name>
Hamburgers
</name>
<producturl>
http://test.com
</producturl>
...
</recipe>
...
<recipe>
I'm building this with (basically)
xml_feed = []
recipes.each do |recipe|
xml_feed <<{id: recipe.id, name: recipe.name, ...}
end
...
render xml: xml_feed.to_xml(root: 'recipes')
But I'm unsure of how to include the id (or any field) as an attribute in the parent node like that. I googled around and couldn't find anything, nor were the http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html docs very helpful
Thanks!

I would suggest you use the nokogiri gem. It provides all you can possible need for handling XML.
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.objects {
xml.object.classy.thing!
}
}
end
puts builder.to_xml
<?xml version="1.0"?>
<root>
<objects>
<object class="classy" id="thing"/>
</objects>
</root>

The suggestion to use Nokogiri is fine. Just the sintax should be a little bit different to achive what you have requested:
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.object('type' => 'Client') {
xml.name 'John'
}
}
end
puts builder.to_xml
<?xml version="1.0"?>
<root>
<object type="Client">
<name>John</name>
</object>
</root>

Related

Add a node to XML using Nokogiri::XML::Builder

I have a Nokogiri::XML::Builder instance, when I call to_xml it produces following structure:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>...</item>
<item>...</item>
</root>
Using this instance I'd like to add one more <item> node under <root> like this:
def add_static_job(builder)
source = builder.doc.root
item = Nokogiri::XML::Node.new('item', source)
item.content = '<title>Hello</title>'
source << item
end
Unfortunatelly this doesn't produce valid xml in the end, rather something like:
<item><title>Hello<title></item>
What could the problem be?
You could do it in 2 steps :
create title node with "Hello" as content
create item node with title as content
xml = '<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>A</item>
<item>B</item>
</root>'
require 'nokogiri'
doc = Nokogiri::XML.parse(xml)
source = doc.root
title = Nokogiri::XML::Node.new('title', doc)
title.content = "Hello"
item = Nokogiri::XML::Node.new('item', doc)
item << title
source << item
puts doc
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <root>
# <item>A</item>
# <item>B</item>
# <item><title>Hello</title></item></root>

Nokogiri : NoMethodError (undefined method `inner_html' for nil:NilClass)

I'm trying to parse a simple XML data with nokogiri.
this is my XML:
POST /.... HTTP/1.1
Host: ....
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://...."
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="...." xmlns:xsd="...." xmlns:soap="....">
<soap:Body>
<WS_QueryOnSec xmlns="......">
<type>string</type>
<ID>string</ID>
</WS_QueryOnSec>
</soap:Body>
</soap:Envelope>
and this is my simle request:
require "nokogiri"
#doc = Nokogiri::XML(request.body.read)
#something = #doc.at('type').inner_html
But Nokogiri can not find the Type or ID node.
When I change the data into this every thing works fine:
<soap:Body>
<type>string</type>
<ID>string</ID>
</soap:Body>
It seems the problem is the raw text above the data and the nods with xmlns or the other attributes!
What do you recommend to resolve this ?
The first "XML" isn't XML. It's text that contains XML. Remove the header information down to the blank line and try it again.
I think it'd help you to read the XML spec or to read some tutorials about creating XML which will help you understand how it's defined. XML is a tight specification and doesn't allow any deviation. The syntax is pretty flexible, but you have to play by its rules.
Consider these examples:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
foo
<root>
<node />
</root>
EOT
doc.errors # => [#<Nokogiri::XML::SyntaxError: Start tag expected, '<' not found>]
Removing the text, which is outside the root tag results in a proper parse:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<root>
<node />
</root>
EOT
doc.errors # => []
<root> isn't neccesarily the name of the "root" node, it's just the outermost tag:
doc = Nokogiri::XML(<<EOT)
<foo>
<node />
</foo>
EOT
doc.errors # => []
and still results in a valid DOM/internal representation of the document:
puts doc.to_html
# >> <foo>
# >> <node></node>
# >> </foo>
Your XML sample is using namespaces, which complicate matters somewhat. The Nokogiri documentation talks about how to deal with them, so you'll want to understand that part of parsing XML because you'll encounter it again. Here's the easy way of working with them:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="utf-8"?>
<Envelope xmlns:xsi="...." xmlns:xsd="...." xmlns:soap="....">
<Body>
<WS_QueryOnSec xmlns="......">
<type>string</type>
<ID>string</ID>
</WS_QueryOnSec>
</Body>
</Envelope>
EOT
namespaces = doc.collect_namespaces
doc.at('type', namespaces).text # => "string"

How to convert XML to Hash in ruby?

I have a XML code which I want to convert into Hash
<meta_description><language id="1"></language><language id="2"></language></meta_description>
<meta_keywords><language id="1"></language><language id="2"></language></meta_keywords>
<meta_title><language id="1"></language><language id="2" ></language></meta_title>
<link_rewrite><language id="1" >konsult-500-krtim</language><language id="2" >konsult-500-krtim</language></link_rewrite>
<name><language id="1" >Konsult 500 kr/tim</language><language id="2" >Konsult 500 kr/tim</language></name>
<description><language id="1" ></language><language id="2" ></language></description>
<description_short><language id="1" ></language><language id="2" ></language></description_short>
<available_now><language id="1" ></language><language id="2" ></language></available_now>
<available_later><language id="1" ></language><language id="2" ></language></available_later>
<associations>
<categories nodeType="category" api="categories">
<category>
<id>2</id>
</category>
</categories>
<images nodeType="image" api="images"/>
<combinations nodeType="combination" api="combinations"/>
<product_option_values nodeType="product_option_value" api="product_option_values"/>
<product_features nodeType="product_feature" api="product_features"/>
<tags nodeType="tag" api="tags"/>
<stock_availables nodeType="stock_available" api="stock_availables">
<stock_available>
<id>111</id>
<id_product_attribute>0</id_product_attribute>
</stock_available>
</stock_availables>
<accessories nodeType="product" api="products"/>
<product_bundle nodeType="product" api="products"/>
</associations>
I want to convert this xml into Hash .
I try to find functions which convert this xml to h=Hash.new
How I do this?
There is ActiveSupport's Hash#from_xml method that you can use:
xml = File.open("data.xml").read # if your xml is in the 'data.xml' file
Hash.from_xml(xml)
If you are using Rails you can use the answer provided above, otherwise you can require the ActiveSuppport gem:
require 'active_support/core_ext/hash'
xml = '<foo>bar</foo>'
hash = Hash.from_xml(xml)
=>{"foo"=>"bar"}
Note this will only work with valid xml. See comments on op. Also note that using element attributes like id="1" won't convert back the same way for example:
xml = %q(
<root>
<foo id="1"></foo>
<bar id="2"></bar>
</root>).strip
hash = Hash.from(xml)
=>{"root"=>{"foo"=>{"id"=>"1"}, "bar"=>{"id"=>"2"}}}
puts hash.to_xml
# will output
<?xml version="1.0" encoding="UTF-8"?>
<hash>
<root>
<foo>
<id>1</id>
</foo>
<bar>
<id>2</id>
</bar>
</root>
</hash>
Use nokogiri to parse XML response to ruby hash. It's pretty fast.
require 'active_support/core_ext/hash' #from_xml
require 'nokogiri'
doc = Nokogiri::XML(response_body)
Hash.from_xml(doc.to_s)

Generating iTunes XML nodes in RSS feed using Ruby/Rails

Q: How do I generate the XML nodes specific to iTunes using Ruby/Rails?
Trying to generate iTunes XML feed, e.g. (based off example):
xml.instruct! :xml, :version => "1.0"
xml.rss(:version => "2.0") do
xml.channel do
xml.title "Your Blog Title"
xml.description "A blog about software and chocolate"
xml.link posts_url
#posts.each do |post|
xml.item do
xml.title post.title
xml.description "Temporary post description"
xml.pubDate post.created_at.to_s(:rfc822)
xml.link post_url(post)
xml.guid post_url(post)
end
end
end
end
Which happily generates something like:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Your Blog Title</title>
<description>A blog about software and chocolate</description>
<link>https://pubweb-thedanielmay.c9.io/sermons</link>
<item>
... omitted ...
</item>
</channel>
</rss>
But looks like I need to generate iTunes-specific XML nodes (as per Working With iTunes, e.g.)
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0"> <-- *
<channel>
<title>Your Blog Title </title>
... omitted ...
<itunes:subtitle>A program about everything</itunes:subtitle> <-- *
... etc ...
Not sure how I generate the iTunes-specific nodes as they have colons in them.
Standard RSS nodes are like:
xml.item --> <item>
How do I get to generating nodes like:
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
<itunes:author>
Ah, answers as per code via Ryan Bates' awesome Railscasts RSS sample
xml.rss "xmlns:itunes" => "http://www.itunes.com/dtds/podcast-1.0.dtd", :version => "2.0"
and
xml.itunes :author, author

How to change Rails 3 to_xml encoding

Im using Rails 3 to_xml on a Model with a few options like include, except and methods.
So this is not my first time using to_xml.
I'm doing something like this:
to_xml(include: {
order: {
methods: [:my_avg],
except: [:this_attr, :and_this_attr ]
},
customer: {}
})
The XML result:
<?xml version="1.0" encoding="UTF-8"?>
<my-model>
<attr1 type="integer">12</attr1>
<attr2 type="integer">12</attr2>
<order>
<name>foo</name>
<desc>bar</desc>
<my-avg>
<avg type="integer">123</avg>
<foo>ok</foo>
</my-avg>
</order>
<updated-at type="datetime">2014-04-14T11:16:56-03:00</updated-at>
</my-model>
But now I want to change the xml encoding ISO_8859_1 instead of utf8.
I haven't seen an encoding option on ActiveRecord::Serialization module.
If I simply add one encoding option it creates a XML attribute instead of changing the encoding that results on this XML:
<?xml version="1.0" encoding="UTF-8"?>
<my-model>
<attr1 type="integer" encoding="ISO-8859-1">12</attr1>
<attr2 type="integer" encoding="ISO-8859-1">12</attr2>
<order>
<name>foo</name>
<desc>bar</desc>
<my-avg>
<avg type="integer">123</avg>
<foo>ok</foo>
</my-avg>
</order>
<updated-at type="datetime">2014-04-14T11:16:56-03:00</updated-at>
</my-model>
Is there a way to specify the encoding using ActiveRecord's to_xml?
you may override to_xml in your Model & specify encoding. something like this could work:
class ModelName < ActiveRecord::Base
def to_xml(options = {})
require 'builder'
options[:indent] ||= 2
xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
xml.instruct! :xml, :version=>"1.0", :encoding => "ISO-8859-1"
xml.level_one do
xml.tag!(:second_level, 'content')
end
end
end

Resources