xml to array in ruby - ruby-on-rails

I am new to ruby and rails programming and I need to parse an xml file that I get as a response and store the station names in an array. A sample of the xml is as follows :
<Stations>
<Station>
<Code>HT</Code>
<Type>knooppuntIntercitystation</Type>
<Namen>
<Kort>Den Bosch</Kort>
<Middel>'s-Hertogenbosch</Middel>
<Lang>'s-Hertogenbosch</Lang>
</Namen>
<Land>NL</Land>
<UICCode>8400319</UICCode>
<Lat>51.69048</Lat>
<Lon>5.29362</Lon>
<Synoniemen>
<Synoniem>Hertogenbosch ('s)</Synoniem>
<Synoniem>Den Bosch</Synoniem>
</Synoniemen>
</Station>
<Station>
<Code>HTO</Code>
<Type>stoptreinstation</Type>
<Namen>
<Kort>Dn Bosch O</Kort>
<Middel>Hertogenbosch O.</Middel>
<Lang>'s-Hertogenbosch Oost</Lang>
</Namen>
<Land>NL</Land>
<UICCode>8400320</UICCode>
<Lat>51.700553894043</Lat>
<Lon>5.3183331489563</Lon>
<Synoniemen>
<Synoniem>Hertogenbosch Oost ('s)</Synoniem>
<Synoniem>Den Bosch Oost</Synoniem>
</Synoniemen>
</Station>
</Stations>
I need to get the Code and the Lang name in an array of hashes or just the lang name in an array.
How can I do that in ruby ?
thanks in advance

you can use
hash = Hash.from_xml(xml)
Refrence doc:
http://apidock.com/rails/v4.0.2/Hash/from_xml/class

Here's a solution which doesn't require Rails but a small gem (xml-simple) :
# gem install xml-simple
require 'xmlsimple'
stations = XmlSimple.xml_in(xml, :ForceArray => ['Station', 'Synoniem'])
codes_and_langs = stations['Station'].map{|station| {:code => station["Code"], :lang => station.fetch("Namen",{})["Lang"]}}
puts codes_and_langs.inspect
#=> [{:code=>"HT", :lang=>"'s-Hertogenbosch"}, {:code=>"HTO", :lang=>"'s-Hertogenbosch Oost"}]
If you are using Rails or have Rails installed :
require 'active_support/core_ext/hash' # <- Use this line for non-Rails Ruby scripts.
hash = Hash.from_xml(xml)
root_node = hash["Stations"] || {}
stations = root_node["Station"] || []
codes_and_langs = stations.compact.map do |station|
{
:code => station["Code"],
:lang => station.fetch('Namen',{})['Lang']
}
end
puts codes_and_langs.inspect
#[{:code=>"HT", :lang=>"'s-Hertogenbosch"}, {:code=>"HTO", :lang=>"'s-Hertogenbosch Oost"}]
just_langs = stations.compact.map do |station|
station.fetch('Namen',{})['Lang']
end
puts just_langs.inspect
# ["'s-Hertogenbosch", "'s-Hertogenbosch Oost"]
Hash#fetch is used to avoid an exception if "Namen" isn't defined.
Here's xml variable for both scripts :
xml="<Stations>
<Station>
<Code>HT</Code>
<Type>knooppuntIntercitystation</Type>
<Namen>
<Kort>Den Bosch</Kort>
<Middel>'s-Hertogenbosch</Middel>
<Lang>'s-Hertogenbosch</Lang>
</Namen>
<Land>NL</Land>
<UICCode>8400319</UICCode>
<Lat>51.69048</Lat>
<Lon>5.29362</Lon>
<Synoniemen>
<Synoniem>Hertogenbosch ('s)</Synoniem>
<Synoniem>Den Bosch</Synoniem>
</Synoniemen>
</Station>
<Station>
<Code>HTO</Code>
<Type>stoptreinstation</Type>
<Namen>
<Kort>Dn Bosch O</Kort>
<Middel>Hertogenbosch O.</Middel>
<Lang>'s-Hertogenbosch Oost</Lang>
</Namen>
<Land>NL</Land>
<UICCode>8400320</UICCode>
<Lat>51.700553894043</Lat>
<Lon>5.3183331489563</Lon>
<Synoniemen>
<Synoniem>Hertogenbosch Oost ('s)</Synoniem>
<Synoniem>Den Bosch Oost</Synoniem>
</Synoniemen>
</Station>
</Stations>
"

Related

Rails - Find missing keys between different locales (.yml files)

I have 2 locale files en.yml and pt.yml. There are some keys that exist only on pt.yml and other keys that exist only on en.yml
Is there a method or routine to list all these keys? (Just comparing the two files)
Example output:
en.activerecord.attributes.person.hand
pt.activerecord.models.bird
Obs: something other than the i18n-tasks missing task.
I find this solution work perfect. It's from a blog post of Kisko Labs. Reference is here: http://blog.kiskolabs.com/post/908453942/comparing-rails-locale-files-for-missing
LOCALE_1 = "~/Code/project/config/locales/fi.yml"
LOCALE_2 = "~/Code/project/config/locales/en.yml"
require 'yaml'
def flatten_keys(hash, prefix="")
keys = []
hash.keys.each do |key|
if hash[key].is_a? Hash
current_prefix = prefix + "#{key}."
keys << flatten_keys(hash[key], current_prefix)
else
keys << "#{prefix}#{key}"
end
end
prefix == "" ? keys.flatten : keys
end
def compare(locale_1, locale_2)
yaml_1 = YAML.load(File.open(File.expand_path(locale_1)))
yaml_2 = YAML.load(File.open(File.expand_path(locale_2)))
keys_1 = flatten_keys(yaml_1[yaml_1.keys.first])
keys_2 = flatten_keys(yaml_2[yaml_2.keys.first])
missing = keys_2 - keys_1
file = locale_1.split('/').last
if missing.any?
puts "Missing from #{file}:"
missing.each { |key| puts " - #{key}" }
else
puts "Nothing missing from #{file}."
end
end
This would do it:
require 'set'
require 'yaml'
files = ['en.yml', 'pt.yml']
p files.map {| file_path| YAML.load(File.read(file_path))}
.map {|object| Set.new(object.keys) }
.reduce(:^)
Docs: YAML and Set

Parsing XML sheet with Rails Nokogiri XPath for attributes and elements

I'm trying to use Nokogiri in a rails 4.2.0 environment to parse a data sheet of classes. What I intend is to have each course parsed, with the #catalog_nbr, #subject attributes stored, as well as the first instructor listed. The code I have below simply yields empty arrays. I believe the problem has to do with using the .each method, but I can't figure it out!
require 'nokogiri'
require 'open-uri'
doc = Nokogiri::HTML( open("https://courseroster.reg.cornell.edu/courses/roster/SP15/CS/xml/") )
doc.xpath("//course").each do
num = doc.xpath("./#catalog_nbr").text
subject = doc.xpath("./#subject").text
instructor = doc.xpath("./sections/section/meeting/instructors/instructor")[1].text
Course.create(:subject => subject, :number => num, :instructor => instructor)
end
Try this.
After selecting the doc, we need to traverse each of the rows in the document. Lets call each of that rows as row
Next. Assign default values if they are blank. Read this article to get more information on this.
doc.xpath("//course").each do |row|
num = row.xpath("./#catalog_nbr").text || "N/A"
subject = row.xpath("./#subject").text || "N/A"
instructor = row.xpath("./sections/section/meeting/instructors/instructor")[1].text || "N/A"
Course.create(:subject => subject, :number => num, :instructor => instructor)
end
Here's a working solution. Note that the XML file you have linked to always has both catalog numbers and subjects for each course, so there's no need for any || "N/A" there (but perhaps it's nice to be safe):
require 'nokogiri'
require 'open-uri'
doc = Nokogiri.XML( open("https://courseroster.reg.cornell.edu/courses/roster/SP15/CS/xml/") )
doc.xpath("/courses/course").each do |course|
num = course["catalog_nbr"] || "N/A" # in case it doesn't exist
subj = course["subject"] || "N/A" # in case it doesn't exist
inst = (course.at("sections/section/meeting/instructors/instructor/text()") || "N/A").to_s
data = { subject:subj, number:num, instructor:inst }
p data
end
#=> {:subject=>"CS", :number=>"1110", :instructor=>"Van Loan,C (cfv3)"}
#=> {:subject=>"CS", :number=>"1112", :instructor=>"Fan,K (kdf4)"}
#=> {:subject=>"CS", :number=>"1130", :instructor=>"Frey,C (ccf27)"}
#=> {:subject=>"CS", :number=>"1130", :instructor=>"Frey,C (ccf27)"}
#=> {:subject=>"CS", :number=>"1132", :instructor=>"Fan,K (kdf4)"}
#=> etc.

Can't get values from json stored in database

I'm trying to write app which logs json msgs to db. That part I got. The problem comes with getting that json back. I can't get values from it.
I've tried to get raw msgs from db and getting values from this ( json gem seems not to see it as json)
I've tried to parse it via .to_json , but it doesn't seem to work either. Maby you have some idea how to get it?
Thanks in advance
table:
mjl_pk bigserial
mjl_body text <- JSON is stored here
mjl_time timestamp
mjl_issuer varchar
mjl_status varchar
mjl_action varchar
mjl_object varchar
mjl_pat_id varchar
mjl_stu_id varchar
code:
#Include config catalog, where jsonadds is
$LOAD_PATH << 'config'
#Requirements
require 'sinatra'
require 'active_record'
require 'json'
require 'jsonadds'
require 'RestClient'
#Class for db
class Mjl < ActiveRecord::Base
#table name
self.table_name = "msg_json_log"
#serialization
serialize :properties, JSON
#overwrite ActiveRecord id with "mjl_pk"
def self.primary_key
"mjl_pk"
end
end
#Get json msg and write it to db
post '/logger' do
content_type :json
#Check if msg is json
if JSON.is_json?(params[:data])
#insert data into db
msg = Mjl.create(:mjl_body => params[:data] ,:mjl_issuer => 'LOGGER', :mjl_action => 'test', :mjl_object =>'obj')
else
#else return error
puts "Not a JSON \n" + params[:data]
end
end
#Get json with id = params[:id]
get '/json/:id' do
content_type :json
#Get json from db
json_body = Mjl.where(mjl_pk: params[:id]).pluck(:mjl_body)
puts json_body
json_body = json_body.to_json
puts json_body
#Get 'patientData' from json
puts json_body['key']
puts json_body[0]['key']
end
Output:
{
"key": "I am a value",
"group": {
"key2": "Next value",
"key3": "Another one"
},
"val1": "Val"
}
["{\n \"key\": \"I am a value\",\n \"group\": {\n \"key2\": \"Next value\",\n \"key3\": \"Another one\"\n },\n \"val1\": \"Val\"\n}"]
key
<--empty value from 'puts json_body[0]['key']'
I've also created a JSON Log in my project like this, this might help you...
In Controller
current_time = Time.now.strftime("%d-%m-%Y %H:%M")
#status_log = {}
#arr = {}
#arr[:status_id] = "2.1"
#arr[:status_short_desc] = "Order confirmed"
#arr[:status_long_desc] = "Order item has been packed and ready for shipment"
#arr[:time] = current_time
#status_log[2.1] = #arr
#status_log_json = JSON.generate(#status_log)
StoreStock.where(:id => params[:id]).update_all(:status_json => #status_log_json)
In View
#json_status = JSON.parse(ps.status_json) # ps.status_json contails raw JSON Log
= #json_status['2.1']['status_long_desc']
Hope this might help you.
when you are doing
json_body = Mjl.where(mjl_pk: params[:id]).pluck(:mjl_body)
you already have json
so no need doing
json_body = json_body.to_json
Since doing this you get the string representation of json, just remove that line and you will get all the values.
Finally I've found how to do it.
I saw explanation on JSON methods at:
from json to a ruby hash?
Code which works for me:
json_body = Mjl.where(mjl_pk: params[:id]).pluck(:mjl_body)
puts json_body
jparsed = JSON.parse(json_body[0])
puts jparsed['key']

How can I change data collection from hash to array?

Now I'm fetching data from another url...
Here is my code:
require 'rubygems'
require 'nokogiri'
html = page.body
doc = Nokogiri::HTML(html)
doc.encoding = 'utf-8'
rows = doc.search('//table[#id = "MainContent_GridView1"]//tr')
#details = rows.collect do |row|
detail = {}
[
[:car, 'td[1]/text()'],
[:article, 'td[2]/text()'],
[:group, 'td[3]/text()'],
[:price, 'td[4]/text()'],
].each do |name, xpath|
detail[name] = row.at_xpath(xpath).to_s.strip
end
detail
end
#details
I tried to do it via array, not a hash. But I get a lot of errors...
Are there any ideas?
I need it for another method...
also i set data (this result hash) to another car here:
oem_art = []
#constr_num.each do |o|
as_oem = get_from_as_oem(o.ARL_SEARCH_NUMBER)
if as_oem.present?
oem_art << as_oem
end
end
#oem_art = oem_art.to_a.uniq
Do you just want to change a hash into an array? If so, just use the to_a method on your hash.
hash = {:a => "something", :b => "something else"}
array = hash.to_a
array.inspect #=> [[:a, "something"], [:b, "something else"]]
It looks like you're looking for something like hash['key'] to hash.key in Ruby
The Hash Class doesn't support .key notation by default, OpenStruct creates an Object from the Hash so you can use dot notation to access the properties. Overall it's basically just syntactic sugar with overhead.
Suggested code (from linked answer)
>> require 'ostruct'
=> []
>> foo = {'bar'=>'baz'}
=> {"bar"=>"baz"}
>> foo_obj = OpenStruct.new foo
=> #<OpenStruct bar="baz">
>> foo_obj.bar
=> "baz"
So in your example, you could do:
# Initialised somewhere
require 'ostruct'
DETAIL_INDICES = {
:car => 1,
:article => 2,
:group => 3,
:price => 4,
}
# ** SNIP **
#details = rows.map do |row|
DETAIL_INDICES.inject({}) do |h,(k,v)|
h.merge(k => row.at_xpath("td[#{v}]/text()").to_s.strip)
end
end.collect { |hash| OpenStruct.new hash }
#details.each do |item|
puts item.car
end
Of course if performance is a concern you can merge your map&collect (They are the same), but this is just a minor separation for basic semantic differences, although I usually only use map for consistency, so feel free to choose yourself :)
EDIT -- Additional code from your edit simplified
#oem_art = #constr_num.select do |item|
as_oem = get_from_as_oem(item.ARL_SEARCH_NUMBER)
as_oem.present?
end
puts #oem_art.uniq

Ruby soap4r build headers

Again with the soap.
I am trying to build a header using soap4r that is supposed to look like this
<SOAP-ENV:Header>
<ns1:UserAuthentication
SOAP-ENV:mustUnderstand="1"
SOAP-ENV:actor="http://api.affiliatewindow.com">
<ns1:iId>*****</ns1:iId>
<ns1:sPassword>*****</ns1:sPassword>
<ns1:sType>affiliate</ ns1:sType>
</ns1:UserAuthentication>
<ns1:getQuota SOAP-ENV:mustUnderstand="1" SOAP-
ENV:actor="http://api.affiliatewindow.com">true</ns1:getQuota>
</SOAP-ENV:Header>
What I have done is created a header derv. class
AffHeader < SOAP::Header::SimpleHandler
Created a UserAuthentification element.
def initialize
#element = XSD::QName.new(nil, "UserAuthentification")
super(#element)
end
And return a hash
def on_simple_outbound
self.mustunderstand = 1
{ "iId" => ID, "sPassword" => PASSWORD, "sType" => "affiliate" }
end
How can I make my header look like I want further. How do I add the actor for example.
I am going to keep searching on this, any Help is very appreciated.
Thank you
In SOAP4R, the target_actor attribute is read only but you can add a new method like:
def target_actor= (uri)
#target_actor = uri
end
and in your on_simple_outbound method, you can call target_actor with your uri like so:
def on_simple_outbound
self.mustunderstand = 1
self.target_actor = "http://api.affiliatewindow.com"
{ "iId" => ID, "sPassword" => PASSWORD, "sType" => "affiliate" }
end
E.g.
irb(main):003:0> h = AffHeader.new
=> #<AffHeader:0x3409ef0 #target_actor=nil, #encodingstyle=nil,
#element=#<XSD::QName:0x1a04f5a {}UserAuthentification>,
#mustunderstand=false, #elename=#<XSD::QName:0x1a04f5a {}UserAuthentification>>
irb(main):006:0> h.on_simple_outbound
=> {"sType"=>"affiliate", "sPassword"=>"secret", "iId"=>"johndoe"}
irb(main):007:0> h
=> #<AffHeader:0x3409ef0 #target_actor="http://api.affiliatewindow.com",
#encodingstyle=nil,
#element=#<XSD::QName:0x1a04f5a {}UserAuthentification>,
#mustunderstand=1, #elename=#<XSD::QName:0x1a04f5a
{}UserAuthentification>>

Resources