Testing Nokogiri XML for Attributes - ruby-on-rails

Using RSpec How could/should I test to ensure elements exists and have specified values
In my example I'm looking to ensure I have an EnvelopeVersion with a value 1.0, i would also like to see a test just to ensure EnvelopeVersion exists
def self.xml_header
builder = Nokogiri::XML::Builder.new do |xml|
xml.Root{
xml.EnvelopeVersion "1.0"
}
end
builder.to_xml
end
I've tried this, but it failed undefined method `has_node?' for #
it 'should create valid header' do
doc = GEM::xml_header
doc.should have_node("EnvelopeVersion ")
end

Your solution can be simplified:
doc = Nokogiri::XML::Document.parse(GEM::xml_header)
doc.xpath('//Root/EnvelopeVersion').text.should eq("1.0")
can be simplified to:
doc = Nokogiri::XML(GEM::xml_header)
doc.at('EnvelopeVersion').text.should eq("1.0")
Nokogiri has two main "find-it" methods: search and at. They are generic, in that they accept both XPath and CSS accessors. search returns a NodeSet of all matching nodes, and at returns the first Node that matches.
There are also the methods at_xpath and at_css if you want something a bit more mnemonic, along with xpath and css.

I ended up using nokogiri in my tests to parse the generated xml and query it
require 'nokogiri'
describe 'function' do
describe '.xml_header' do
it 'should create valid header' do
doc = Nokogiri::XML::Document.parse(GEM::xml_header)
doc.xpath('//Root/EnvelopeVersion').text.should eq("1.0")
end
end
end

Related

Nokogiri Use Ruby-Core Method as XML Node Name

How do I get Nokogiri to accept a ruby-core method as a node name e.g.
xml.hash Digest::SHA256.file form.survey_xml should return something like this
<hash>cde6f0dd030aac1d3aa6d231b7c0cc30a34686a6f6780c468ccc64a4822f01e0</hash>
Instead I am getting an error ArgumentError: wrong number of arguments (1 for 0) in hash of course because hash is a ruby method.
How do I set the node name to hash using the Nokogiri DSL since the API I am interacting with expects that node.
I can just create the xml manually but the answer I am looking for is specifically using nokogiri
More Info
Here is the xml I am trying to create:
<?xml version=\"1.0\"?>
<xforms xmlns=\"http://openrosa.org/xforms/xformsList\">
<xform>
<formID>1</formID>
<name>BLAH BLAH</name>
<version>1</version>
<hash>892734982SDHFK238479823749234934</hash>
<downloadUrl>/Users/me/workspace/dashboard/public/uploads/survey_xml/survey_xml/2/S_1_.xml</downloadUrl>
</xform>
</xforms>
Here is my code:
require 'nokogiri'
require 'digest'
def mine
xml = Nokogiri::XML::Builder.new{ |xml|
xml.xforms xmlns: 'http://openrosa.org/xforms/xformsList' do
#forms.each do |form|
xml.xform do
xml.formID form.id
xml.name form.name
xml.version 1
xml.hash Digest::SHA256.file form.survey_xml.survey_xml.file.file
xml.downloadUrl form.survey_xml.survey_xml.file.file
end
end
end
}.to_xml
end
Based on dimakura's answer:
You can use other Nokogiri methods.
xpath:
node.xpath('hash').first
search:
node.search('hash').first
children:
xml.children.select{|x| x.name == 'hash'}
If you are creating new elements, not getting them. Then you can add them, for example, like this:
xml.add_child '<hash>hash-code</hash>'
Update When working with Nokogiri::XML::Builder special names should be used with underscore (_):
xml.hash_ 'your-hash'

How do I use Hpricot to search the inner_text of all elements?

I would like to use Hpricot to scan the inner_text of all elements, and know what element is currently being scanned. However, each approach I have taken leads to a recursion. Is there a built-in function to do this with Hpricot (or Nokogiri)? The code below just scans one level down:
#t = []
doc = Hpricot(open("some html doc"))
(doc/"html").each do |e|
e.children.each do |child|
if child.is_a?(Hpricot::Text)
#t << child.to_s.strip
end
end
end
Although I'm not sure exactly why you want to collect all text nodes (perhaps there is a more efficient solution), this should get you started:
require 'nokogiri'
doc = Nokogiri::HTML(open('doc'))
doc.at_css("body").traverse do |node|
puts "***#{node.name}"
puts node.text
end
It uses Nokogiri's traverse which will visit all nodes under your starting node.

How do I test a paperclip URL?

I'm using Test::Unit, and I'd like to test a paperclip url.
Right now, I have a test that looks like this:
def test_our_custom_url
dummy = Dummy.new(:image => File.open("#{RAILS_ROOT}/test/fixtures/12k.png"))
dummy.save!
assert_match "/system/images/1/original/12k.png", dummy.image.url
dummy.reload
assert_match "/system/images/1/original/12k.png", dummy.image.url
end
And it fails like this:
Expected /\/system\/images\/1\/original\/12k.png/ to match "/system/dummies/images/000/000/001/original/12k.png?1352140343".
How can I get the ?1352140343 part added to the test?
I see in paperclips source code that they seem to define it like this:
def timestamp_as_needed(url, options)
if options[:timestamp] && timestamp_possible?
delimiter_char = url.match(/\?.+=/) ? '&' : '?'
"#{url}#{delimiter_char}#{#attachment.updated_at.to_s}"
else
url
end
end
but doing dummy.updated_at.to_s returns a normal date/timestamp, not 1352140343 (in this case).
I'm using local storage, if it makes a difference.
Its not good to compare the ?1352140343 part of the url.
you can compare your received_url.starts_with?(expected_url) to check only that part of the strings...

Rspec for the following method

Can anyone please help me in writing rspec for the following method The write_entry_to_xml method write xml nodes.
The entry is an object which looks like
entry = Sitemap::Entry.new("http://www.example.com", 'monthly', 0.8, "2011-11-23 13:56:42 UTC")
def write_entry_to_xml(entry)
node = Nokogiri::XML::Node.new("url", #xml_document)
node["loc"] = entry.loc
node["lastmod"] = entry.lastmod.to_s
node["changefreq"] = entry.changefreq.to_s
node["priority"] = entry.priority.to_s
node.to_xml
end
Thanks
First off you should know what the expected output of your method should be. We can't tell you what your code is supposed to be doing. Once you have that, your spec doesn't have to be anything more than
describe SomeClass do
describe('write_entry_to_xml') do
let(:entry) { Sitemap::Entry.new("http://www.example.com", 'monthly', 0.8, "2011-11-23 13:56:42 UTC")}
it 'should return a properly formatted xml fragment' do
SomeClass.write_entry_to_xml(entry).should == "<url>...</url>"
end
end
end
Here I've assumed that this method is a class method on SomeClass - change to match what you've done, you should also obviously replace "..." with the desired output
There are various ways to get some pre-defined data that you would use as input in ur spec.
1.
before :each do
#input = input
end
Now #input will be accessible to the specs as this block will be run before every spec.
2. You could define the inputs inside the spec as a local variable and then access it. If you require to access the same variable at many places then you can use the above method.
You could get some XML via using a library like Builder.
builder = Builder::XmlMarkup.new
xml = builder.url do | b|
b.loc = "http://www.experteer.de/jobboerse/deutschland/jobs/mannheim"
b.changefreq =0.8
b.priority = "monthly"
b.lastmod = "2011-11-23 13:56:42 UTC"
end
this code should give you the XML object. Do let me know if you need further help.

Whats the best way to put a small ruby app online?

I have a small ruby application I wrote that's an anagram searcher. It's for learning ruby, but I would like to put it up online for personal use. I have some experience with Rails, and many here have recommended Sinatra. I'm fine with either, but I cannot find any information on how to use a text file instead of a database.
The application is quite simple, validates against a text file of a word list, then finds all anagrams. I have been assuming that this should be quite simple, but I'm stuck on importing that textfile into Rails (or Sinatra if i choose that way). In the Rails project, I have placed the textfile in the lib directory.
Unfortunately, even though the path appears to be correct in Rails, I get an error:
no such file to load -- /Users/court/Sites/cvtest/lib/english.txt
(cvtest is the name of the rails project)
Here is the code. It works great by itself:
file_path = '/Users/court/Sites/anagram/dictionary/english.txt'
input_string = gets.chomp
# validate input to list
if File.foreach(file_path) {|x| break x if x.chomp == input_string}
#break down the word
word = input_string.split(//).sort
# match word
anagrams = IO.readlines(file_path).partition{
|line| line.strip!
(line.size == word.size && line.split(//).sort == word)
}[0]
#list all words except the original
anagrams.each{ |matched_word| puts matched_word unless matched_word == input_string }
#display error if
else
puts "This word cannot be found in the dictionary"
end
Factor the actual functionality (finding the anagrams) into a method. Call that method from your Web app.
In Rails, you'd create a controller action that calls that method instead of ActiveRecord. In Sinatra, you'd just create a route that calls the method. Here's a Sinatra example:
get '/word/:input'
anagrams = find_anagrams(params[:input])
anagrams.join(", ")
end
Then, when you access the http://yourapp.com/word/pool, it will print "loop, polo".
I know the question is marked as answered, but I prefer the following, as it uses query parameters rather than path based parameters, which means you can pass the parameters in using a regular GET form submission:
require 'rubygems'
require 'sinatra'
def find_anagrams word
# your anagram method here
end
get '/anagram' do
#word = params['word']
#anagrams = find_anagrams #word if #word
haml :anagram
end
And the following haml (you could use whatever template language you prefer). This will give you an input form, and show the list of anagrams if a word has been provided and an anagram list has been generated:
%h1
Enter a word
%form{:action => "anagram"}
%input{:type => "text", :name => "word"}
%input{:type => "submit"}
- if #word
%h1
Anagrams of
&= #word
- if #anagrams
%ul
- #anagrams.each do |word|
%li&= word
- else
%p No anagrams found
With sinatra, you can do anything. These examples doesn't even require sinatra, you could roll your own rack interface thing.
require 'rubygems'
require 'sinatra'
require 'yaml'
documents = YAML::load_file("your_data.yml")
Or:
require 'rubygems'
require 'sinatra'
content = Dir[File.join(__DIR__, "content/*.textile)].map {|path|
content = RedCloth(File.read(path)).to_html
}
Etcetera.

Resources