Reading xml file using REXML, says <UNDEFINED> ... </> - ruby-on-rails

I have a very simple xml file that I am trying to access:
<article>
<text>hello world</text>
</article>
I'm doing this so far:
file = File.open("#{Rails.root}/public/files/#{file_id}.xml", "r")
xml = file.read
doc = REXML::Document.new(xml)
When I run this code in rails console, I see:
1.9.3-p194 :033 > doc.inspect
=> "<UNDEFINED> ... </>"
I can't seem to understand why it is not loading the file correctly, I can't access the text xml element either.

It is loading correctly, the document just doesn't have a root node.
require "rexml/document"
doc = REXML::Document.new DATA.read
doc.root_node # => <UNDEFINED> ... </>
doc.inspect # => "<UNDEFINED> ... </>"
doc.to_s # => "<article>\n <text>hello world</text>\n</article>\n"
doc.get_elements('//article') # => [<article> ... </>]
doc.get_elements('//text') # => [<text> ... </>]
__END__
<article>
<text>hello world</text>
</article>
By the way, I think the Ruby community has pretty much universally endorsed Nokogiri for xml parsing.

Related

Declaring XML Tags in Ruby

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>

Nokogiri::XML parse value based on another XML attribute's value

Using Nokogiri::XML how can I retrieve a attribute's value based on another attribute?
XML file:
<RateReplyDetails>
<ServiceType>INT</ServiceType>
<Price>1.0</Price>
</RateReplyDetails>
<RateReplyDetails>
<ServiceType>LOCAL</ServiceType>
<Price>2.0</Price>
</RateReplyDetails>
And I would like to retrieve the Price of the LOCAL ServiceType which is 2.0
I could take the value without any condition with this:
rated_shipment.at('RateReplyDetails/Price').text
And probably I could do something like:
if rated_shipment.at('RateReplyDetails/ServiceType').text == "LOCAL"
rated_shipment.at('RateReplyDetails/Price').text
But is there any elegant and clean way of doing so?
I'd do something like:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<xml>
<RateReplyDetails>
<ServiceType>INT</ServiceType>
<Price>1.0</Price>
</RateReplyDetails>
<RateReplyDetails>
<ServiceType>LOCAL</ServiceType>
<Price>2.0</Price>
</RateReplyDetails>
</xml>
EOT
service_type = doc.at('//RateReplyDetails/*[text() = "LOCAL"]')
service_type.name # => "ServiceType"
'//RateReplyDetails/*[text() = "LOCAL"]' is an XPath selector that looks for the < RateReplyDetails> node that contains a text node equal to "LOCAL" and returns the node containing the text, which is the <ServiceType> node.
service_type.next_element.text # => "2.0"
Once we've found that it's easy to look at the next element and get its text.
try, content is the xml content string.
doc = Nokogiri::HTML(content)
doc.at('servicetype:contains("INT")').next_element.content
[16] pry(main)>
doc.at('servicetype:contains("INT")').next_element.content
=> "1.0"
[17] pry(main)>
doc.at('servicetype:contains("LOCAL")').next_element.content
=> "2.0"
I have test it, it's working.
Fully in XPath:
rated_shipment.at('//RateReplyDetails[ServiceType="LOCAL"]/Price/text()').to_s
# => "2.0"
EDIT:
it didnt work for me
Full code as proof it does work:
#!/usr/bin/env ruby
require 'nokogiri'
rated_shipment = Nokogiri::XML(DATA)
puts rated_shipment.at('//RateReplyDetails[ServiceType="LOCAL"]/Price/text()').to_s
__END__
<xml>
<RateReplyDetails>
<ServiceType>INT</ServiceType>
<Price>1.0</Price>
</RateReplyDetails>
<RateReplyDetails>
<ServiceType>LOCAL</ServiceType>
<Price>2.0</Price>
</RateReplyDetails>
</xml>
(outputs 2.0.) If it does not work, then it is because your file contents do not match your OP.

CSV download in Rails includes textarea tag - override Remotipart render?

I'm trying to create a CSV file for download in Rails, and cannot get it to send just the CSV without a tag around the data. In my controller, I have:
csv_string = CSV.generate do |csv|
headers = ['Header 1', 'Header 2']
csv << headers
#matches.each do |match|
csv << match
end
end
send_data(csv_string, :filename => filename, :layout => false)
The form to run this has:
=form_tag log_path, :id =>'log_search_form', :multipart => true, :remote=>true do
.search_fields
.panel.panel-default
.panel-heading
Search Log File:
=file_field_tag :search
=submit_tag "Find Matches", :class=>'btn btn-primary btn-xs'
When I press "Find Matches", I am prompted to download a csv file, but the first line has:
<textarea data-type="text/csv" data-status="200" data-statusText="OK">Header 1
and the file ends with
</textarea>
The (legacy) code uses remotipart - it seems I need to stop it from overriding render and adding the textarea. How can I do this to get a clean CSV download?
Thank you!
I've never seen that superfluous <textarea> issue before, but try this send_data(...) parameterization, which is taken from a Rails app I maintain:
# inside your format.csv handler...
# set the filename and csv_string variables...
send_data(csv_string, :type => 'text/csv',
:disposition => :attachment,
:filename => filename)

What can I use to generate a local XML file?

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

Unwelcome string at start and end of XML output

I'm exporting XML directly to file, to avoid the time it takes to render to view, and it's working nicely:
controller:
def onixout
s = render_to_string(:template=>"isbns/onix.xml.builder")
send_data(s, :type=>"text/xml",:filename => "onix2.1.xml")
end
The Builder file is pretty standard:
xml.instruct!(:xml, :version => "1.0", :encoding => "utf-8")
xml.declare! :DOCTYPE, :ONIXMessage, :SYSTEM, "http://www.editeur.org/onix/2.1/03/reference/onix-international.dtd"
xml.ONIXMessage do
xml.Header do
if Company.where(:client_id => current_user.client_id).first.nil?
else
xml.FromCompany Company.where(:client_id => current_user.client_id).first.sendername
end
end
Isbn.where(:client_id => current_user.client_id).search(params[:q]).result.all.each do |isbn|
xml.Product do
xml.NotificationType isbn.notificationtype unless isbn.notificationtype.blank?
isbn.productcodes.each do |productcode|
xml.ProductIdentifier do
#more
In the resultant file, though, I'm getting an undesirable string at the start (e047d in this case):
e047d
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ONIXMessage SYSTEM "http://www.editeur.org/onix/2.1/03/reference/onix-international.dtd">
<ONIXMessage>
<Header>
#more
And there's a '0' at the end of the doc too.
Any idea what they are? And, obviously, how to get rid?

Resources