I have a XML document:
<event>
<type>SUBSCRIPTION_ORDER</type>
<marketplace>
<baseUrl>https://www.acme-marketplace.com</baseUrl>
<partner>ACME</partner></marketplace>
</marketplace>
<creator>
<email>admin#fakeco</email>
<firstName>Alice</firstName>
<lastName>Hacker</lastName>
<openId>https://www.acme-marketplace.com/openid/id/a11a7918-bb43-4429-a256-f6d729c71033</openId>
<uuid>a11a7918-bb43-4429-a256-f6d729c71033</uuid>
</creator>
<payload>
<company>
<uuid>d15bb36e-5fb5-11e0-8c3c-00262d2cda03</uuid>
<email>admin#fakeco</email>
<name>Fake Co.</name>
<phoneNumber>1-415-555-1212</phoneNumber>
<website>fakeco</website>
</company>
<order>
<editionCode>BASIC</editionCode>
<item>
<quantity>10</quantity>
<unit>USER</unit>
</item>
</order>
</payload>
</event>
and I got strange result when I load it with nokogiri:
1.9.3p194 :056 > doc = Nokogiri::XML(File.open("test.xml")).to_s
=> "<?xml version=\"1.0\"?>\n<event>\n <type>SUBSCRIPTION_ORDER</type>\n <marketplace>\n <baseUrl>https://www.acme-marketplace.com</baseUrl>\n <partner>ACME</partner></marketplace>\n </event>\n"
What am I doing wrong?
You've got two closing </marketplace> tags, which is invalid XML:
<partner>ACME</partner></marketplace>
</marketplace>
Remove one of them and Nokogiri should read the file fine.
If you parse your XML into a Nokgiri::XML document:
doc = Nokogiri::XML(<<EOT)
<event>
<type>SUBSCRIPTION_ORDER</type>
<marketplace>
<baseUrl>https://www.acme-marketplace.com</baseUrl>
<partner>ACME</partner></marketplace>
</marketplace>
<creator>
<email>admin#fakeco</email>
<firstName>Alice</firstName>
<lastName>Hacker</lastName>
<openId>https://www.acme-marketplace.com/openid/id/a11a7918-bb43-4429-a256-f6d729c71033</openId>
<uuid>a11a7918-bb43-4429-a256-f6d729c71033</uuid>
</creator>
<payload>
<company>
<uuid>d15bb36e-5fb5-11e0-8c3c-00262d2cda03</uuid>
<email>admin#fakeco</email>
<name>Fake Co.</name>
<phoneNumber>1-415-555-1212</phoneNumber>
<website>fakeco</website>
</company>
<order>
<editionCode>BASIC</editionCode>
<item>
<quantity>10</quantity>
<unit>USER</unit>
</item>
</order>
</payload>
</event>
EOT
Then check the document's errors method, you'll see:
doc.errors
[
[0] #<Nokogiri::XML::SyntaxError:0x100a6dbb8
attr_reader :code = 76,
attr_reader :column = 19,
attr_reader :domain = 1,
attr_reader :file = nil,
attr_reader :int1 = 1,
attr_reader :level = 3,
attr_reader :line = 6,
attr_reader :str1 = "event",
attr_reader :str2 = "marketplace",
attr_reader :str3 = nil
>,
[1] #<Nokogiri::XML::SyntaxError:0x100a6daa0
attr_reader :code = 5,
attr_reader :column = 5,
attr_reader :domain = 1,
attr_reader :file = nil,
attr_reader :int1 = 0,
attr_reader :level = 3,
attr_reader :line = 7,
attr_reader :str1 = nil,
attr_reader :str2 = nil,
attr_reader :str3 = nil
>
]
That's Nokogiri telling you about the problems with the document. You can react to that simply enough using:
if (!doc.errors.empty?)
...
end
It tries to recover from errors because of the RECOVER flag being set in the parse step, but there are some cases it can't fix, such as the doubled closing tag. You'll need to do a pre-flight check and fix to cleanse the document before Nokogiri can make sense of it. Unfortunately, not all XML is generated correctly, and whoever created that should have run it through a validity checker before putting it out there. As is, it's non-conforming and considered illegal in the XML world.
Related
Protobuf generated:
# source: event.proto
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_file("event.proto", :syntax => :proto3) do
add_message "myapp.Event" do
optional :name, :string, 1
optional :entity, :enum, 2, "myapp.Event.Entity"
oneof :event_data do
optional :first_event_data, :message, 3, "myapp.Event.FirstEventData"
optional :second_event_data, :message, 4, "myapp.Event.SecondEventData"
end
end
add_message "myapp.Event.FirstEventData" do
optional :id, :string, 1
optional :to, :string, 2
optional :from, :string, 3
end
add_message "myapp.Event.SecondEventData" do
optional :metadata_url, :string, 1
end
add_enum "myapp.Event.Entity" do
value :FIRST, 0
value :SECOND, 1
end
end
end
module Myapp
Event = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("myapp.Event").msgclass
Event::FirstEventData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("myapp.Event.FirstEventData").msgclass
Event::SecondEventData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("myapp.Event.SecondEventData").msgclass
Event::Entity = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("myapp.Event.Entity").enummodule
end
Now in the console when I do
message = Myapp::Event.new(
entity: :SECOND,
name: "started",
event_data: {
second_event_data:
Myapp::Event::SecondEventData.new(
metadata_url: "local-dev-url",
)
}
)
I get this error:
Traceback (most recent call last):
3: from (irb):178
2: from (irb):178:in `new'
1: from (irb):178:in `initialize'
ArgumentError (Unknown field name 'event_data' in initialization map entry.)
I have tried different combinations and every time different error, I think I am organising my message incorrectly.
Any help would be appreciated greatly. Thanks
Well, after lots of research, apparently I can't do it all at once. I need to create 2 or 3 seperate objects:
i.e
message = Myapp::Event.new(
entity: :SECOND,
name: "started")
And then I can do this:
message.second_event_data = Myapp::Event::SecondEventData.new(
metadata_url: "local-dev-url",
)
Then when I encode it, second_event_data will sits under the event_data
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>
"
I've been racking my brain for a while now and I can't figure out why my csv upload in my rails app is failing. I have a simple model that converts two names in the csv to integers of foreign_ids. The model works completely fine when executed manually in the console but for some reason it fails on the server. I get the error message: undefined method `id' for nil:NilClass
The model looks as follows:
require 'csv'
class Schedule < ActiveRecord::Base
belongs_to :team
belongs_to :opponent, :foreign_key => 'opponent_id', :class_name => 'Team'
def self.import(file)
CSV.foreach(file.path, headers: true, :header_converters => :symbol) do |row|
week_hash = row.to_hash
teamname = week_hash[:team]
teamhash = Team.where(:name => teamname).first
teamhash_id = teamhash.id
week_newhash = week_hash.reject!{ |k| k == :team}
week_newhash[:team_id] = teamhash_id
opponentname = week_hash[:opponent]
opponent_hash = Team.where(:name => opponentname).first
hashopponent_id = opponent_hash.id
week_newhash = week_newhash.reject!{ |k| k == :opponent}
week_newhash[:opponent_id] = hashopponent_id
Schedule.create!(week_newhash)
end
end
end
The problem must be in here somewhere. Any help would be greatly appreciated. Thanks.
I'm an idiot. The model was fine I just had a column mislabeled in my csv.
Maybe change:
teamhash_id = teamhash.id
to:
teamhash_id = teamhash[:id], and hashopponent_id = opponent_hash.id to hashopponent_id = opponent_hash[:id]?
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
consider that i have a migration as follows
create_table :dummies do |t|
t.decimal :the_dummy_number
end
i instantiate like the following
dummy = Dummy.new
dummy.the_dummy_number = "a string"
puts dummy.the_dummy_number
the output for the above is
0.0
how did this happen? since i assign a wrong value shouldn't it raise an error?
The biggest problem is the following.
Since it automatically converts my validate method fails miserably.
update-the validate method
validate :is_dummy_number_valid, :the_dummy_number
def is_dummy_number_valid
read_attribute(:the_dummy_number).strip()
end
The reason that this does not work as you expect is that the underlying ruby implementation of BigDecimal does not error when passed a string.
Consider the following code
[ 'This is a string', '2is a string', '2.3 is also a string',
' -3.3 is also a string'].each { |d| puts "#{d} = #{BigDecimal.new(d)}" }
This is a string = 0.0
2is a string = 2.0
2.3 is also a string = 2.3
-3.3 is also a string = -3.3
So BigDecimal scans the string and assigns anything at the beginning of the string that could be a decimal to its value.
If you set your model up like this
class Dummy < ActiveRecord::Base
validates_numericality_of :the_dummy_number
end
Then the validation should work fine
>> d=Dummy.new(:the_dummy_number => 'This is a string')
=> #<Dummy id: nil, the_dummy_number: #<BigDecimal:5b9230,'0.0',4(4)>, created_at: nil, updated_at: nil>
>> puts d.the_dummy_number
0.0
=> nil
>> d.valid?
=> false
>> d.errors
=> #<ActiveRecord::Errors:0x5af6b8 #errors=#<OrderedHash
{"the_dummy_number"=>[#<ActiveRecord::Error:0x5ae114
#message=:not_a_number, #options={:value=>"This is a string"}
This works because the validates_numericality_of macro uses the raw_value method to get at the value before it was typecast and assigned to the internal decimal value.