I have a project that I am working on and I do not know much about Rails or Ruby.
I need to generate an XML file from user input.
Can some direct me to any resource that can show me how to do this pretty quickly and easily?
The Nokogiri gem has a nice interface for creating XML from scratch. It's powerful while still easy to use. It's my preference:
require 'nokogiri'
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.products {
xml.widget {
xml.id_ "10"
xml.name "Awesome widget"
}
}
}
end
puts builder.to_xml
Will output:
<?xml version="1.0"?>
<root>
<products>
<widget>
<id>10</id>
<name>Awesome widget</name>
</widget>
</products>
</root>
Also, Ox does this too. Here's a sample from the documenation:
require 'ox'
doc = Ox::Document.new(:version => '1.0')
top = Ox::Element.new('top')
top[:name] = 'sample'
doc << top
mid = Ox::Element.new('middle')
mid[:name] = 'second'
top << mid
bot = Ox::Element.new('bottom')
bot[:name] = 'third'
mid << bot
xml = Ox.dump(doc)
# xml =
# <top name="sample">
# <middle name="second">
# <bottom name="third"/>
# </middle>
# </top>
Nokogiri is a wrapper around libxml2.
Gemfile
gem 'nokogiri'
To generate xml simple use the Nokogiri XML Builder like this
xml = Nokogiri::XML::Builder.new { |xml|
xml.body do
xml.node1 "some string"
xml.node2 123
xml.node3 do
xml.node3_1 "another string"
end
xml.node4 "with attributes", :attribute => "some attribute"
xml.selfclosing
end
}.to_xml
The result will look like
<?xml version="1.0"?>
<body>
<node1>some string</node1>
<node2>123</node2>
<node3>
<node3_1>another string</node3_1>
</node3>
<node4 attribute="some attribute">with attributes</node4>
<selfclosing/>
</body>
Source: http://www.jakobbeyer.de/xml-with-nokogiri
Related
I am using Ruby to pull information from an excel sheet and with this information produce an xml file. I need to produce this in Ruby:
What I want:
<Betrag waehrung="EUR">150000</Betrag>
What I have:
<Betrag waehrung ="EUR"/>
I am currently trying xml.Betrag "Waehrung": "Eur"
the Betrag has a row Identifier of "#{row[13]}" which is where it can be found on the excel sheet I am using. I have tried: xml.Betrag "Waehrung": ("Eur"), ("#{row[13]}") with no success, could you please advise?
require 'nokogiri'
builder = Nokogiri::XML::Builder.new do |xml|
xml.Betrag(waehrung: 'EUR') do |e|
e << '150000'
end
end
puts builder.to_xml
=>
<?xml version="1.0"?>
<Betrag waehrung="EUR">150000</Betrag>
I'm using the Ruby Gem Builder, and I need this output..
<?xml version="1.0" encoding="utf-8"?>
<fileAttachment> "name of file here.xls"
<Data>zip</Data>
<Size>7434</Size>
</fileAttachment>
My code is below, but the file name next to "fileAttachment" just isn't working.. This is something simple I'm just not seeing it?? The error says I can't mix text with a block.. makes sense I just don't know the correct syntax.
xml = Builder::XmlMarkup.new(:indent => 2 )
xml.instruct! :xml,:version=>"1.0", :encoding => "utf-8"
xml.fileAttachment("name of file here.xls") do
xml.Data "zip"
xml.Size "7434"
end
I think you want to use the text! method:
xml.fileAttachment do
xml.text! "name of file here.xls"
xml.Data "zip"
xml.Size "7434"
end
I'm attempting to parse this XML with nokogirl but I'm having trouble. Any ideas where I'm going wrong? I'd like to get each Dealer and get the values for each of them.
doc = Nokogiri::Slop(response.body)
puts doc.content #works, shows the response below
puts doc.DTX_LEAD_ID.content #errors, no method found.
puts doc.NEWCAR_PINGGX_RESPONSE.content #errors, no method found
returned XML:
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="www.example.com/">
<?xml version="1.0" encoding="utf-8"?>
<NEWCAR_PINGGX_RESPONSE xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="www.example.com/SellerMessages">
<DTX_LEAD_ID>1779853194</DTX_LEAD_ID>
<SUCCESS>true</SUCCESS>
<CACHED_RESPONSE>false</CACHED_RESPONSE>
<PRICE>20</PRICE>
<DealerList>
<Dealer>
<BUYER_ID>0000-2127</BUYER_ID>
<Reservation_ID>1779853194|0000-2067|520a8037-57c8-497e-be4b-f4ea8dfa6c6f|14187-20</Reservation_ID>
<Price>20</Price>
<Name>Randy's Rides</Name>
<State>MI</State>
<City>Southfield</City>
<Street>2001 Town Center</Street>
<Postalcode>48076</Postalcode>
<Distance>2.56002068066733</Distance>
<DealerGroup id="2067" max_post="5" />
<Contact><Name>John Campbell</Name>
<Phone>2483521314</Phone>
</Contact>
</Dealer>
</DealerList>
</NEWCAR_PINGGX_RESPONSE></string>
Previously I've had a response like this:
<?xml version="1.0" encoding="utf-8"?>
<results>
<status>accepted</status>
<id>1724128693</id>
<purchaseprice>8.0000</purchaseprice>
<error>false</error>
<messages>
<message>coverage available</message>
</messages>
</results>
Which parses really easily with nokogiri:
doc.results.messages.message.content #coverage available
I want to do something like:
doc.NEWCAR_PINGGX_RESPONSE.DealerList.Dealer.Name.content #returns "Randy's Rides"
To see what's wrong with a document use the errors method. After parsing your XML:
doc.errors
# => [#<Nokogiri::XML::SyntaxError: xmlns: URI www.example.com/ is not absolute>,
# #<Nokogiri::XML::SyntaxError: XML declaration allowed only at the start of the document>,
# #<Nokogiri::XML::SyntaxError: xmlns: URI www.example.com/SellerMessages is not absolute>]
To extract the data I'd use something like this:
doc = Nokogiri::XML(XML)
doc.remove_namespaces!
dealers = doc.search('Dealer').map{ |dealer|
{
buyer_id: dealer.at( 'BUYER_ID' ).text,
reservation_id: dealer.at( 'Reservation_ID' ).text,
name: dealer.at( 'Name' ).text
}
}
dealers
# => [{:buyer_id=>"0000-2127",
# :reservation_id=>
# "1779853194|0000-2067|520a8037-57c8-497e-be4b-f4ea8dfa6c6f|14187-20",
# :name=>"Randy's Rides"},
# {:buyer_id=>"0000-2127",
# :reservation_id=>
# "1779853194|0000-2067|e42fd5c6-0a36-4552-8b6a-ad2decebd0db|14200-10",
# :name=>"Jarrett's New Car Dealership 01"},
# {:buyer_id=>"0000-2127",
# :reservation_id=>
# "1779853194|0000-2067|3fecb591-3a81-49f9-82b3-1f0d7fb3f7a6|14160-20",
# :name=>"Campbell's Crazy Cars"},
# {:buyer_id=>"0000-2127",
# :reservation_id=>
# "1779853194|0000-2067|731b09e9-700b-4f41-8cb0-eaf80e861d76|14158-7",
# :name=>"Demo Dealer 3"}]
Of course you'll want to add/remove/change fields being extracted to fit your use-case.
Using slop mode has its dangers, as stated by the Nokogiri documentation.
Don’t use this.
This may or may not be a backhanded compliment.
No, really, don’t use this. If you use it, don’t report bugs.
You’ve been warned!
I've never used it as a result. Often we don't want to use remove_namespaces! either, but it appears safe in your situation.
I can't figure out how to get a rendered collection (as XML) to include a style sheet line such as:
<?xml-stylesheet type="text/xsl" href="example.xsl" ?>
This guy says to add a proc as such:
proc = Proc.new { |options| options[:builder].instruct!(:xml-stylesheet, type=>'text/xsl', :href=>'something.xsl') }
#foo.to_xml :procs => [proc]
But I can't get that to work. Any suggestions?
Take a look at Nokogiri gem: http://nokogiri.org/Nokogiri/XSLT/Stylesheet.html
doc = Nokogiri::XML(File.read('some_file.xml'))
xslt = Nokogiri::XSLT(File.read('some_transformer.xslt'))
puts xslt.transform(doc)
You can use the atom_feed helper which is included with Rails:
atom_feed(instruct: {
'xml-stylesheet' => {type: 'text/xsl', href: 'styles.xml'}
}) do |feed|
feed.title "My Atom Feed"
# entries...
end
Which results in (showing only first 3 lines):
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="styles.xml"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
Or, if not rendering an atom_feed, you can simply use instruct inside your builder:
xml.instruct! 'xml-stylesheet', href: 'style.xml', type: 'text/xsl'
to_xml on Hash with string array fails with Not all elements respond to to_xml
>>r={"records"=>["001","002"]}
=> {"records"=>["001", "002"]}
>>r.to_xml
RuntimeError: Not all elements respond
to to_xml from
/jruby/../1.8/gems/activesupport2.3.9/lib/active_support/core_ext/array/conversions.rb:163:in `to_xml'
Is there a rails preferred way to change the Hash.to_xml behavior to return
<records>
<record>001</record>
<record>002</record>
</records>
...
Just like DigitalRoss said, this appears to work out of the box in Ruby 1.9 with ActiveSupport 3:
ruby-1.9.2-p0 > require 'active_support/all'
=> true
ruby-1.9.2-p0 > r={"records"=>["001","002"]}
=> {"records"=>["001", "002"]}
ruby-1.9.2-p0 > puts r.to_xml
<?xml version="1.0" encoding="UTF-8"?>
<hash>
<records type="array">
<record>001</record>
<record>002</record>
</records>
</hash>
At least with MRI (you're using JRuby, though), you can get similar behavior on Ruby 1.8 with ActiveSupport 2.3.9:
require 'rubygems'
gem 'activesupport', '~>2.3'
require 'active_support'
class String
def to_xml(options = {})
root = options[:root] || 'string'
options[:builder].tag! root, self
end
end
Which gives you...
ruby-1.8.7-head > load 'myexample.rb'
=> true
ruby-1.8.7-head > r={"records"=>["001","002"]}
=> {"records"=>["001", "002"]}
ruby-1.8.7-head > puts r.to_xml
<?xml version="1.0" encoding="UTF-8"?>
<hash>
<records type="array">
<record>001</record>
<record>002</record>
</records>
</hash>
Note that my code doesn't work with Ruby 1.9 and ActiveRecord 3.
No, because there is no way that "001" and "002" know how to become <record>001</record>. These strings are just that: strings. They don't know that they are used in a hash with an array, let alone that these strings share a key, that needs to be singularized.
You could do something like:
record = Struct.new(:value) do
def to_xml
"<record>#{value}</record>"
end
end
r = { "records" => [ record.new("001"), record.new("002") ] }
r.to_xml
Or, use a tool like Builder to make the xml separately from the data structure.