I am working on test-first-ruby-master (you can find it at https://github.com/appacademy/test-first-ruby).
The 13_xml_document_spec.rb is the Rspec test that my code must pass. This test has several tasks, but it is the last one (called "indents") that my code doesn't accomplish.
Here is the Rspec test:
require "13_xml_document"
describe XmlDocument do
before do
#xml = XmlDocument.new
end
it "renders an empty tag" do
expect(#xml.hello).to eq("<hello/>")
end
it "renders a tag with attributes" do
expect(#xml.hello(:name => "dolly")).to eq('<hello name="dolly"/>')
end
it "renders a randomly named tag" do
tag_name = (1..8).map{|i| ("a".."z").to_a[rand(26)]}.join
expect(#xml.send(tag_name)).to eq("<#{tag_name}/>")
end
it "renders block with text inside" do
expect(#xml.hello { "dolly" }).to eq("<hello>dolly</hello>")
end
it "nests one level" do
expect(#xml.hello { #xml.goodbye }).to eq("<hello><goodbye/></hello>")
end
it "nests several levels" do
xml = XmlDocument.new
xml_string = xml.hello do
xml.goodbye do
xml.come_back do
xml.ok_fine(:be => "that_way")
end
end
end
expect(xml_string).to eq('<hello><goodbye><come_back><ok_fine
be="that_way"/></come_back></goodbye></hello>')
end
it "indents" do
#xml = XmlDocument.new(true)
xml_string = #xml.hello do
#xml.goodbye do
#xml.come_back do
#xml.ok_fine(:be => "that_way")
end
end
end
expect(xml_string).to eq(
"<hello>\n" +
" <goodbye>\n" +
" <come_back>\n" +
" <ok_fine be=\"that_way\"/>\n" +
" </come_back>\n" +
" </goodbye>\n" +
"</hello>\n"
)
end
end
And here is my code:
class XmlDocument
def initialize(indentation = false)
#indentation = indentation
#counter = 0
end
def method_missing(method, *args, &block)
hash = {}
if block
if #indentation == false
"<#{method}>#{yield}</#{method}>"
elsif #indentation == true
string = ""
string << indent1
string << "<#{method}>\n"
(###)
add_indent
string << indent1
string << yield + "\n"
sub_indent
string << indent2
string << "</#{method}\>"
string
end
elsif args[0].is_a?(Hash)
args[0].map { |key,value| "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>" }.join(" ")
elsif hash.empty?
"<#{method.to_s}/>"
end
end
def indent1
" " * #counter
end
def indent2
" " * #counter
end
def add_indent
#counter += 1
end
def sub_indent
#counter -= 1
end
end
This is the output I get for the "indents" part:
<hello>
<goodbye>
<come_back>
+ <ok_fine be="that_way"/>
</come_back>
</goodbye>
</hello>
Contrary to the right answer, the 4th line ('ok_fine be="that_way"/') seems be two indents closer to the left than it is supposed to be. As opposed to the rest of the lines, the 4th line is not a block, but an argument of the called method 'come_back'.
I cannot see where my mistake is. Even writing an exception in the code (where the (###) is in my code) doesn't seem to have any effect on the 4th line.
Here is the exception (###):
if args[0].is_a?(Hash)
add_indent
string << indent
arg[0].map{|key, value| string << "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>"}
end
NOTE: I assume that if I manage to give the 4th line the right numbers of indents, that also will increase the number of indents of the lines after it, so the method 'indent2' will need to be modified.
I figured out what the problem was. As I said in my question, in the Rspec test they have the following input:
xml_string = xml.hello do
xml.goodbye do
xml.come_back do
xml.ok_fine(:be => "that_way")
end
end
end
where the 4th line (xml.ok_fine(:be => "that_way")) doesn't have a block nested, but an argument. In my code I established a condition (if block) for when there is a block present and inside this first condition, a second condition (if #indentation == true) for when #indentation is true:
if block
if #indentation == false
"<#{method}>#{yield}</#{method}>"
elsif #indentation == true
...
It is inside this second condition that I create the variable 'string' where I shovel in the different parts:
elsif #indentation == true
string = ""
string << indent1
string << "<#{method}>\n"
(###)
add_indent
string << indent1
string << yield + "\n"
sub_indent
string << indent2
string << "</#{method}\>"
string
end
But because the 4th line doesn't carry a block, the first condition (if block) doesn't return true for it and therefore this 4th line is skipped.
I've re-written my code so now it passes the Rspec test:
class XmlDocument
def initialize(indentation = false)
#indentation = indentation
#counter = 0
end
def method_missing(method, args = nil, &block)
string = ""
arguments = args
if #indentation == false
if (arguments == nil) && (block == nil)
"<#{method.to_s}/>"
elsif arguments.is_a?(Hash)
arguments.map { |key,value| "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>" }.join(" ")
elsif block
"<#{method}>#{yield}</#{method}>"
end
elsif #indentation == true
if (block) || (arguments.is_a?(Hash))
string << indent1
string << "<#{method}>\n" unless !block
add_indent
string << indent1 unless !block
if block
string << yield + "\n"
elsif arguments.is_a?(Hash)
arguments.map { |key,value| string << "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>" }
end
sub_indent
string << indent2 unless !block
string << "</#{method}\>" unless !block
if indent2 == ""
string << "\n"
end
end
string
end
end
def indent1
" " * #counter
end
def indent2
" " * #counter
end
def add_indent
#counter += 1
end
def sub_indent
#counter -= 1
end
end
In contrast to the code I wrote in my question, in this one, the two main conditions are #indentation == false and #indentation == true and inside these two conditions I establish different exceptions for the different cases (block or no block, argument or no argument...). Specifically for elsif #indentation == true I created a condition that accepts the 4th line: if (block) || (arguments.is_a?(Hash)), or in other words, it accepts methods that have a block or an argument (especifically a a hash).
Now, I shovel in the different parts in 'string', and when I reach a block to yield there is a bifurcation:
if block
string << yield + "\n"
elsif arguments.is_a?(Hash)
arguments.map { |key,value| string << "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>" }
if there is a block I "yield" it, and if there is and argument that is a hash I shovel it into 'string'.
Also, there is this exception unless !block either when I indent or I shovel a method because otherwise it can introduce unwanted indents and '\n' if there is a method that doesn't have a block (as line 4th).
Finally, I had to add at the end
if indent2 == ""
string << "\n"
end
because the solution requires a '\n' at the end.
I hope this answer can help other
NOTE: I wrote a 'NOTE' in my question where I assumed I would have to modify 'indent2'. That, obviously I didn't have to do because the output I was getting did not considered the 4th line (because it doesn't have a block), so the bigger indentation (" ") of 'indent2' is all right.
Related
I need some help/idea on how to dynamically position values in a 2D array.
Basically, I have a condition that must be met and that determines which column should the value be written.
This is what I have (a loop that pass conditions before writing the value):
#records.each do |client|
client_info = Report.get_client_info(client)
client_address = Report.get_client_address(client)
client_records = []
client_records << [client_info.id, client_info.full_name]
if #include_address == "1"
client_address.each_with_index do |address, i|
if client_records[i].present?
client_records[i][2] = address.full_address
client_records[i][3] = address.address_type
else
client_records << ["","", address.full_address, address.address_type]
end
end
end
if #include_contact == "1"
client_contact.each_with_index do |contact, i|
if client_records[i].present?
client_records[i][4] = contact.value
client_records[i][5] = contact.label
else
client_records << ["","","","", contact.value, contact.label]
end
end
end
end
Problem is: IF I want to make another condition, and the condition in between the two (conditions) is not true, the position of the column value set by: client_records[i][2] >> 2,3 etc... is hard coded, instead of the third condition occupying the place of the second condition's columns, it naturally leaves it blank and stays at the same place.
This is how it would look like:
Instead of:
How can I work around this?
For example, try that:
#records.each do |client|
client_info = Report.get_client_info(client)
client_address = Report.get_client_address(client)
client_records = []
line = [client_info.id, client_info.full_name]
[client_address.count, client_contact.count].max.times do |i|
if #include_address == "1" && client_address[i]
line << client_address[i].full_address << client_address[i].address_type
else
line << '' << ''
end
if ##include_contact == "1" && client_contact[i]
line << client_contact[i].value << client_contact[i].label
else
line << '' << ''
end
client_records << line.clone
line = ['', '']
end
end
I have a method in spec\factories\campaigns.rb:
def campaign_trait(name, *callback_attrs, &block)
trait name do
association :campaign_type, factory: [:campaign_type, name]
after(:build) do |campaign, evaluator|
eval_str = ""
callback_attrs.each do |arg|
arg = [arg] unless arg.is_a? Array
method_name = arg.shift
method_args = arg
method_name = "add_#{method_name}" unless respond_to? method_name
eval_str << method_name.to_s
eval_str << "(campaign"
eval_str << ", evaluator" if method_name == "add_campaign_scopes"
if method_args.any?
method_args.map! { |i| i.is_a?(Symbol) ? ":#{i}" : i }
eval_str << ", " << method_args.map(&:to_s).join(', ')
end
eval_str << ")\n"
end
eval eval_str
end
yield(block) if block_given?
end
end
I call it here:
FactoryGirl.define do
campaign_trait :basket, :campaign_scopes, [:banner, :basket] do
initialize_with { Campaigns::Basket.new(attributes, without_protection: true) }
emitent_article 'emitent'
emitent_name 'Emitent'
end
end
The problem that I face is that in the method campaign_trait I get callback_attrs that equals [:campaign_scopes, []] instead of expected [:campaign_scopes, [:banner, :basket]].
If I call campaign_trait without the block, everything is OK and I get [:campaign_scopes, [:banner, :basket]] as expected.
Could you please help me?
The problem was that I was calling campaign_trait number of times through many tests and somehow (I don't know why), callback_attrs are shared between tests. And method_name = arg.shift breaks my code modifying callback_attrs.
Thanks, BroiSatse! Deep debugging helped me.
Using Rails, I need to get a plain text and show it as HTML, but I don't want to use <pre> tag, as it changes the format.
I needed to subclass HTML::WhiteListSanitizer to escape non whitelisted tags (by changing process_node), monkey patch HTML::Node to don't downcase tags' names and monkey patch HTML::Text to apply <wbr /> word splitting:
class Text2HTML
def self.convert text
text = simple_format text
text = auto_link text, :all, :target => '_blank'
text = NonHTMLEscaper.sanitize text
text
end
# based on http://www.ruby-forum.com/topic/87492
def self.wbr_split str, len = 10
fragment = /.{#{len}}/
str.split(/(\s+)/).map! { |word|
(/\s/ === word) ? word : word.gsub(fragment, '\0<wbr />')
}.join
end
protected
extend ActionView::Helpers::TagHelper
extend ActionView::Helpers::TextHelper
extend ActionView::Helpers::UrlHelper
class NonHTMLEscaper < HTML::WhiteListSanitizer
self.allowed_tags << 'wbr'
def self.sanitize *args
self.new.sanitize *args
end
protected
# Copy, just to reference this Node definition
def tokenize(text, options)
options[:parent] = []
options[:attributes] ||= allowed_attributes
options[:tags] ||= allowed_tags
tokenizer = HTML::Tokenizer.new(text)
result = []
while token = tokenizer.next
node = Node.parse(nil, 0, 0, token, false)
process_node node, result, options
end
result
end
# gsub <> instead of returning nil
def process_node(node, result, options)
result << case node
when HTML::Tag
if node.closing == :close
options[:parent].shift
else
options[:parent].unshift node.name
end
process_attributes_for node, options
options[:tags].include?(node.name) ? node : node.to_s.gsub(/</, "<").gsub(/>/, ">")
else
bad_tags.include?(options[:parent].first) ? nil : node.to_s
end
end
class Text < HTML::Text
def initialize(parent, line, pos, content)
super parent, line, pos, content
#content = Text2HTML.wbr_split content
end
end
# remove tag/attributes downcases and reference this Text
class Node < HTML::Node
def self.parse parent, line, pos, content, strict=true
if content !~ /^<\S/
Text.new(parent, line, pos, content)
else
scanner = StringScanner.new(content)
unless scanner.skip(/</)
if strict
raise "expected <"
else
return Text.new(parent, line, pos, content)
end
end
if scanner.skip(/!\[CDATA\[/)
unless scanner.skip_until(/\]\]>/)
if strict
raise "expected ]]> (got #{scanner.rest.inspect} for #{content})"
else
scanner.skip_until(/\Z/)
end
end
return HTML::CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
end
closing = ( scanner.scan(/\//) ? :close : nil )
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
unless closing
scanner.skip(/\s*/)
attributes = {}
while attr = scanner.scan(/[-\w:]+/)
value = true
if scanner.scan(/\s*=\s*/)
if delim = scanner.scan(/['"]/)
value = ""
while text = scanner.scan(/[^#{delim}\\]+|./)
case text
when "\\" then
value << text
value << scanner.getch
when delim
break
else value << text
end
end
else
value = scanner.scan(/[^\s>\/]+/)
end
end
attributes[attr] = value
scanner.skip(/\s*/)
end
closing = ( scanner.scan(/\//) ? :self : nil )
end
unless scanner.scan(/\s*>/)
if strict
raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})"
else
# throw away all text until we find what we're looking for
scanner.skip_until(/>/) or scanner.terminate
end
end
HTML::Tag.new(parent, line, pos, name, attributes, closing)
end
end
end
end
end
end
I wanted to check if a string is palindrome or not using ruby code.
I am a starter in ruby so not too aquainted with the string methods in ruby
If you are not acquainted with Ruby's String methods, you should have a look at the documentation, it's very good. Mithun's answer already showed you the basic principle, but since you are new to Ruby, there's a couple more things to keep in mind:
*) If you have a predicate method, it's customary to name it with a trailing question mark, e.g. palindrome?.
*) Boolean expressions evaluate to a boolean, so you don't need to explicitly return true or false. Hence a short idiomatic version would be
def palindrome?(str)
str == str.reverse
end
*) Since Ruby's classes are open, you could add this to the string class:
class String
def palindrome?
self == self.reverse
end
end
*) If you don't want to monkey-patch String, you can directly define the method on single object (or use a module and Object#extend):
foo = "racecar"
def foo.palindrome?
self == self.reverse
end
*) You might want to make the palindrome check a bit more complex, e.g. when it comes to case or whitespace, so you are also able to detect palindromic sentences, capitalized words like "Racecar" etc.
pal = "Never a foot too far, even."
class String
def palindrome?
letters = self.downcase.scan(/\w/)
letters == letters.reverse
end
end
pal.palindrome? #=> true
def check_palindromic(variable)
if variable.reverse == variable #Check if string same when reversed
puts "#{ variable } is a palindrome."
else # If string is not the same when reversed
puts "#{ variable } is not a palindrome."
end
end
The recursive solution shows how strings can be indexed in Ruby:
def palindrome?(string)
if string.length == 1 || string.length == 0
true
else
if string[0] == string[-1]
palindrome?(string[1..-2])
else
false
end
end
end
If reading the Ruby string documentation is too boring for you, try playing around with the Ruby practice questions on CodeQuizzes and you will pick up most of the important methods.
def is_palindrome(value)
value.downcase!
# Reverse the string
reversed = ""
count = value.length
while count > 0
count -= 1
reversed += value[count]
end
# Instead of writing codes for reverse string
# we can also use reverse ruby method
# something like this value == value.reverse
if value == reversed
return "#{value} is a palindrom"
else
return "#{value} is not a palindrom"
end
end
puts "Enter a Word"
a = gets.chomp
p is_palindrome(a)
class String
def palindrome?
self.downcase == self.reverse.downcase
end
end
puts "racecar".palindrome? # true
puts "Racecar".palindrome? # true
puts "mississippi".palindrome? # false
str= gets.chomp
str_rev=""
n=1
while str.length >=n
str_rev+=str[-n]
n+=1
end
if str_rev==str
puts "YES"
else
puts "NO"
end
> first method
a= "malayalam"
if a == a.reverse
puts "a is true"
else
puts "false"
end
> second one
a= "malayalam"
a=a.split("")
i=0
ans=[]
a.count.times do
i=i+1
k=a[-(i)]
ans << k
end
if a== ans
puts "true"
else
puts "false"
end
def palindrome?(string)
string[0] == string[-1] && (string.length <= 2 || palindrome?(string[1..-2]))
end
**Solution 1** Time complexity = O(n), Space complexity = O(n)
This solution does not use the reverse method of the String class. It uses a stack(we could use an array that only allows entry and exit of elements from one end to mimic a stack).
def is_palindrome(str)
stack = []
reversed_str = ''
str.each_char do |char|
stack << char
end
until stack.empty?
reversed_str += stack.pop
end
if reversed_str == str
return true
else
return false
end
end
` Solution 2: Time complexity = O(n), Space complexity = O(1)
def inplace_reversal!(str)
i =0
j = str.length - 1
while i < j
temp = str[i]
str[i] = str[j]
str[j] = temp
i+=1
j-=1
end
return str
end
def palindrome?(str)
return "Please pass the string" if str.nil?
str = str.downcase
str_array = str.split('')
reverse_string = str_array.each_index{ |index| str_array[str_array.count - index - 1 ] end
return ("String #{str} is not a palindrome") unless str == reverse_string.join('')
"String #{str} is palindrome"
end
I would like to enumerate all the URLs in a text string, for example:
text = "fasòls http://george.it sdafsda"
For each URL found, I want to invoke a function method(...) that transforms the string.
Right now I'm using a method like this:
msg = ""
for i in text.split
if (i =~ URI::regexp).nil?
msg += " " + i
else
msg+= " " + method(i)
end
end
text = msg
This works, but it's slow for long strings. How can I speed this up?
I think "gsub" is your friend here:
class UrlParser
attr_accessor :text, :url_counter, :urls
def initialize(text)
#text = parse(text)
end
private
def parse(text)
#counter = 0
#urls = []
text.gsub(%r{(\A|\s+)(http://[^\s]+)}) do
#urls << $2
"#{$1}#{replace_url($2)}"
end
end
def replace_url(url)
#counter += 1
"[#{#counter}]"
end
end
parsed_url = UrlParser.new("one http://x.com/url two")
puts parsed_url.text
puts parsed_url.urls
If you really need extra fast parsing of long strings, you should build a ruby C extension with ragel.