How to change Rails 3 to_xml encoding - ruby-on-rails

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

Related

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 parse this returned XML with Nokogiri

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.

Rails 2: #to_xml(:methods => :method_name) when #method_name returns an Array

Is it possible to override how #to_xml renders a method result if it's an Array?
Given #numbers is not a field but a method
And #object.numbers #=> [0,1,2,3,4,5]
currently it does:
#object.to_xml(:methods => :numbers)
=> "<object><numbers>012345</numbers></object>"
is it possible to override this behavior so it returns:
#object.to_xml(:methods => :numbers)
=> "<object>
<numbers>
<number>0</number>
<number>1</number>
<number>2</number>
<number>3</number>
<number>4</number>
<number>5</number>
</numbers>
</object>"
(formatted so it's easily read)
Thanks in advance for your suggestions!
You have two options:
Upgrade to Rails 3. It already has the desired output.
gem "activemodel", '~> 3.2.12'
require "active_model"
# This could be an Active Record model
class Result
include ActiveModel::Serializers::Xml
def numbers
(0..5).to_a
end
def attributes
{}
end
end
result = Result.new
puts result.to_xml(:methods => :numbers)
Outputs:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<numbers type="array">
<number type="integer">0</number>
<number type="integer">1</number>
<number type="integer">2</number>
<number type="integer">3</number>
<number type="integer">4</number>
<number type="integer">5</number>
</numbers>
</result>
Use XML Builder where you are in full control of the output. You will have to specify all attributes manually.
# app/views/results/show.xml.builder
xml.instruct!
xml.result {
xml.numbers {
result.numbers.each do |n|
xml.number n
end
}
}
Outputs:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<numbers>
<number>0</number>
<number>1</number>
<number>2</number>
<number>3</number>
<number>4</number>
<number>5</number>
</numbers>
</result>
You can use the :procs option to add a custom rendering for your method inside the model:
def to_xml(options={})
numbers_proc = Proc.new do |options|
xml = options[:builder]
xml.numbers do
numbers.each do |n|
xml.number n
end
end
end
super options.merge(:procs => numbers_proc)
end

Rendering XML with Stylesheet in Rails

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

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.

Resources