Skip first occurrence of semicolon using regex - ruby-on-rails

I am trying to skip the first semicolon in the string and do a split on the rest of the semicolon using a regular expression:
lines = </li> <li> Urinary tract infection </li> <li> Respiratory infection </li> <li> Sinus problems; and </li> <li> Ear infections; <li> Some more info </li>
I am using this code to split this at every semicolon except the first one:
lines.split(/(?<!\\\\);/)
My expected output is:
["</li> <li> Urinary tract infection </li> <li> Respiratory infection </li> <li> Sinus problems; and </li> <li> Ear infections","<li> Some more info </li>" ]
Note that the string could be long with any number of semicolons but I want to prevent the splitting from happening only from the first semicolon.

Must I use a regex?
str = "Now is; the time; for all good; people; to party"
f, sc, l = str.partition(';')
af, *a = str[str.index(';')+1..-1].split(';')
[f+sc+af, *a]
#=> ["Now is; the time", " for all good", " people", " to party"]
The steps:
f, sc, l = str.partition(';')
#=> ["Now is", ";", " the time; for all good; people; to party"]
f #=> "Now is"
sc #=> ";"
l #=> " the time; for all good; people; to party"
idx = str.index(';')+1
#=> 7
b = str[idx..-1]
#=> " the time; for all good; people; to party"
af, *a = b.split(';')
af #=> " the time"
a #=> [" for all good", " people", " to party"]
[f+sc+af, *a]
#=> ["Now is; the time", " for all good", " people", " to party"]
For the OP's example:
f, sc, l = lines.partition(';')
af, *a = lines[lines.index(';')+1..-1].split(';')
[f+sc+af, *a]
#=> ["</li> <li> Urinary tract infection </li> <li> Respiratory infection\
</li> <li> Sinus problems; and </li> <li> Ear infections",
# " <li> Some more info </li>"]
Another way:
b = str.sub(';', 0.chr).split(';')
#=> ["Now is\u0000 the time", " for all good", " people", " to party"]
a[0].sub!(0.chr, ';')
#=> "Now is; the time"
a #=> ["Now is; the time", " for all good", " people", " to party"]

Related

Rendering a view of hierarchical lists with unknown/unlimited nesting

The following pattern is a set of lists nested within other list items.
<ul id="nestedlist">
<li> 1
<ul>
<li> 1.1
<ul>
<li> 1.1.1</li>
<li> 1.1.2</li>
<li> 1.1.3</li>
</ul>
</li>
<li class="connect"> 1.2
<ul>
<li> 1.2.1</li>
<li> 1.2.2</li>
<li> 1.2.3</li>
<li> 1.2.4</li>
<li> 1.2.5
<ul>
<li> 1.2.5.1</li>
<li> 1.2.5.2
<ul>
<li> 1.2.5.2.1</li>
<li> 1.2.5.2.2</li>
</ul>
</li>
<li> 1.2.5.3</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
The model Node
belongs_to :parentnode
has_many :children_nodes, foreign_key: :parentnode_id, class_name: 'Parentnode'
Thus an unlimited number of levels can be defined in a hierarchy.
The view rendering is essential a pattern where each node can become a parentnode
Parentnode.children_nodes.each_with_index do |node, index|
node.name
**parent_indicies.(index + 1)**
end
How can this be rendered in a rails view to represent each node level and its nesting level when the nesting levels are not known beforehand?
You can use rails helper methods to generate rails view.
Example:
content_tag(:ul) do
(1..10).each do |node|
concat content_tag(:li, node)
end
end
And the logic would be to use recursion:
def generate_list(node, level)
return if node == nil
#put logic of generating ul li here.
#add ul if node.children_nodes.present?
#else add li.
node.children_nodes.each_with_index |node, index|
generate_list(node, "#{level}.#{index+1}"
end
end
This is just the base logic. I hope you can do the rest of the part. If you still face difficulty just let me know.
Updated with below working code to generate html:
class Node
attr_accessor :val, :children_nodes
def initialize(value)
self.val = value
self.children_nodes = []
end
end
p1 = Node.new("P")
n1 = Node.new("1")
n2 = Node.new("2")
p1.children_nodes.push(n1)
p1.children_nodes.push(n2)
def generate_list(node, level, final_str)
return if node == nil
if node.children_nodes.size > 0
final_str << "<li> #{node.val}"
final_str << "<ul> "
else
final_str << "<li> #{node.val} </li>"
end
node.children_nodes.each_with_index do |n, index|
generate_list(n, "#{level}.#{index+1}", final_str)
end
if node.children_nodes.size > 0
final_str << "</ul> "
final_str << "</li>"
end
end
def generate(node)
final_str = ""
final_str << "<ul>"
generate_list(node, 1, final_str)
final_str << "</ul>"
final_str
end
puts generate(p1)

Parse 'ul' and 'ol' tags

I have to handle deep nesting of ul, ol, and li tags. I need to give the same view as we are giving in the browser. I want to achieve the following example in a pdf file:
text = "
<body>
<ol>
<li>One</li>
<li>Two
<ol>
<li>Inner One</li>
<li>inner Two
<ul>
<li>hey
<ol>
<li>hiiiiiiiii</li>
<li>why</li>
<li>hiiiiiiiii</li>
</ol>
</li>
<li>aniket </li>
</li>
</ul>
<li>sup </li>
<li>there </li>
</ol>
<li>hey </li>
<li>Three</li>
</li>
</ol>
<ol>
<li>Introduction</li>
<ol>
<li>Introduction</li>
</ol>
<li>Description</li>
<li>Observation</li>
<li>Results</li>
<li>Summary</li>
</ol>
<ul>
<li>Introduction</li>
<li>Description
<ul>
<li>Observation
<ul>
<li>Results
<ul>
<li>Summary</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Overview</li>
</ul>
</body>"
I have to use prawn for my task. But prawn doesn't support HTML tags. So, I came up with a solution using nokogiri:. I am parsing and later removing the tags with gsub. The below solution I have written for a part of the above content but the problem is ul and ol can vary.
RULES = {
ol: {
1 => ->(index) { "#{index + 1}. " },
2 => ->(index) { "#{}" },
3 => ->(index) { "#{}" },
4 => ->(index) { "#{}" }
},
ul: {
1 => ->(_) { "\u2022 " },
2 => ->(_) { "" },
3 => ->(_) { "" },
4 => ->(_) { "" },
}
}
def ol_rule(group, deepness: 1)
group.search('> li').each_with_index do |item, i|
prefix = RULES[:ol][deepness].call(i)
item.prepend_child(prefix)
descend(item, deepness + 1)
end
end
def ul_rule(group, deepness: 1)
group.search('> li').each_with_index do |item, i|
prefix = RULES[:ul][deepness].call(i)
item.prepend_child(prefix)
descend(item, deepness + 1)
end
end
def descend(item, deepness)
item.search('> ol').each do |ol|
ol_rule(ol, deepness: deepness)
end
item.search('> ul').each do |ul|
ul_rule(ul, deepness: deepness)
end
end
doc = Nokogiri::HTML.fragment(text)
doc.search('ol').each do |group|
ol_rule(group, deepness: 1)
end
doc.search('ul').each do |group|
ul_rule(group, deepness: 1)
end
puts doc.inner_text
1. One
2. Two
1. Inner One
2. inner Two
• hey
1. hiiiiiiiii
2. why
3. hiiiiiiiii
• aniket
3. sup
4. there
3. hey
4. Three
1. Introduction
1. Introduction
2. Description
3. Observation
4. Results
5. Summary
• Introduction
• Description
• Observation
• Results
• Summary
• Overview
Problem
1) What I want to achieve is how to handle space when working with ul and ol tags
2) How to handle deep nesting when li come inside ul or li come inside ol
I've come up with a solution that handles multiple identations with configurable numeration rules per level:
require 'nokogiri'
ROMANS = %w[i ii iii iv v vi vii viii ix]
RULES = {
ol: {
1 => ->(index) { "#{index + 1}. " },
2 => ->(index) { "#{('a'..'z').to_a[index]}. " },
3 => ->(index) { "#{ROMANS.to_a[index]}. " },
4 => ->(index) { "#{ROMANS.to_a[index].upcase}. " }
},
ul: {
1 => ->(_) { "\u2022 " },
2 => ->(_) { "\u25E6 " },
3 => ->(_) { "* " },
4 => ->(_) { "- " },
}
}
def ol_rule(group, deepness: 1)
group.search('> li').each_with_index do |item, i|
prefix = RULES[:ol][deepness].call(i)
item.prepend_child(prefix)
descend(item, deepness + 1)
end
end
def ul_rule(group, deepness: 1)
group.search('> li').each_with_index do |item, i|
prefix = RULES[:ul][deepness].call(i)
item.prepend_child(prefix)
descend(item, deepness + 1)
end
end
def descend(item, deepness)
item.search('> ol').each do |ol|
ol_rule(ol, deepness: deepness)
end
item.search('> ul').each do |ul|
ul_rule(ul, deepness: deepness)
end
end
doc = Nokogiri::HTML.fragment(text)
doc.search('ol:root').each do |group|
binding.pry
ol_rule(group, deepness: 1)
end
doc.search('ul:root').each do |group|
ul_rule(group, deepness: 1)
end
You can then remove the tags or use doc.inner_text depending on your environment.
Two caveats though:
Your entry selector must be carefully selected. I used your snippet verbatim without root element, thus i had to use ul:root/ol:root. Maybe "body > ol" works for your situation too. Maybe selecting each ol/ul but than walking each and only find those, that have no list parent.
Using your example verbatim, Nokogiri does not handle the last 2 list items of the first group ol very well ("hey", "Three")
When parsing with nokogiri, thus elements already "left" their ol tree and got placed in the root tree.
Current Output:
1. One
2. Two
a. Inner One
b. inner Two
◦ hey
◦ hey
3. hey
4. hey
hey
Three
1. Introduction
a. Introduction
2. Description
3. Observation
4. Results
5. Summary
• Introduction
• Description
◦ Observation
* Results
- Summary
• Overview
Firstly for handling space, I have used a hack in the lambda call.
Also, I am using add_previous_sibling function given by nokogiri to append something in starting. Lastly Prawn doesn't handle space when we deal with ul & ol tags so for that I have used this gsub gsub(/^([^\S\r\n]+)/m) { |m| "\xC2\xA0" * m.size }. You can read more from this link
Note: Nokogiri doesn't handle invalid HTML so always provide valid HTML
RULES = {
ol: {
1 => ->(index) { "#{index + 1}. " },
2 => ->(index) { "#{}" },
3 => ->(index) { "#{}" },
4 => ->(index) { "#{}" }
},
ul: {
1 => ->(_) { "\u2022 " },
2 => ->(_) { "" },
3 => ->(_) { "" },
4 => ->(_) { "" },
},
space: {
1 => ->(index) { " " },
2 => ->(index) { " " },
3 => ->(index) { " " },
4 => ->(index) { " " },
}
}
def ol_rule(group, deepness: 1)
group.search('> li').each_with_index do |item, i|
prefix = RULES[:ol][deepness].call(i)
space = RULES[:space][deepness].call(i)
item.add_previous_sibling(space)
item.prepend_child(prefix)
descend(item, deepness + 1)
end
end
def ul_rule(group, deepness: 1)
group.search('> li').each_with_index do |item, i|
space = RULES[:space][deepness].call(i)
prefix = RULES[:ul][deepness].call(i)
item.add_previous_sibling(space)
item.prepend_child(prefix)
descend(item, deepness + 1)
end
end
def descend(item, deepness)
item.search('> ol').each do |ol|
ol_rule(ol, deepness: deepness)
end
item.search('> ul').each do |ul|
ul_rule(ul, deepness: deepness)
end
end
doc = Nokogiri::HTML.parse(text)
doc.search('ol').each do |group|
ol_rule(group, deepness: 1)
end
doc.search('ul').each do |group|
ul_rule(group, deepness: 1)
end
Prawn::Document.generate("hello.pdf") do
#puts doc.inner_text
text doc.at('body').children.to_html.gsub(/^([^\S\r\n]+)/m) { |m| "\xC2\xA0" * m.size }.gsub("<ul>","").gsub("<\/ul>","").gsub("<ol>","").gsub("<\/ol>","").gsub("<li>", "").gsub("</li>","").gsub("\\n","").gsub(/[\n]+/, "\n")
end
Whenever you are in a ol, li or ul element, you must recursively check for ol, li and ul. If there are none of them, return (what have been discovered as a substructure), if there are, call the same function on the new node and add its return value to the current structure.
You perform a different action on each node no matter where it is depending on its type and then the function automatically repackage everything.

Ruby on Rails sort on priorities

Following were my query to get list of countries along with cities:
#countries_cities = Product.joins(:user).where("country is not null and country <> ''").where("city is not null and city <> ''").where(:users => {:merchant_status => 1}).group(:country, :city).select("country,city").as_json
The output result were as follow:
Object[city:"Bangkok",country:"Thailand"],Object[city:"Phuket",country:"Thailand"],Object[city:"Malaysia",country:"Kuala Lumpur"],Object[city:"Malaysia",country:"Penang"],Object[city:"Shanghai",country:"China"],Object[city:"Beijing",country:"China"]
cchs = #countries_cities.group_by{|cc| cc["country"]}
#search_location_country = cchs
And the view is:
<ul id="color-dropdown-menu" class="dropdown-menu dropdown-menu-right" role="menu">
<% #search_location_country.each do |country, cities| %>
<li class="input" style="background:#ECECEC; "><%= country.upcase %></li>
<% cities.each do |city| %>
<li class="input"><%= city["city"].titleize %></li>
<% end %>
<% end %>
</ul>
Now the Drop down result follow this pattern:
Thailand
-Bangkok
-Phuket
Malaysia
-Kuala Lumpur
-Penang
China
-Beijing
-Shanghai
How can I ensure that Malaysia will always place at the top of the drop down lists? Thanks!!
How about:
#countries_cities = Product.joins(:user)
.where.not(country: [nil, ''])
.where(users: {merchant_status: 1})
.group(:country, :city)
.order("country!= 'Malaysia'")
.select(:country, :city)
.as_json
In Postgres, false is sorted before true (see this answer here: Custom ORDER BY Explanation)
You can customize you query like this:
#countries_cities = Product.joins(:user)
.where.not(country: [nil, ''])
.where(:users => {:merchant_status => 1})
.group(:country, :city)
.order("ORDER BY
CASE WHEN country = 'Malaysia' THEN 1
ELSE 2
END ASC")
.select(:country, :city)
.as_json
So we set the order of Malaysia = 2, and others = 1 to ensure the result with Malaysia will be on the top.

Rails get unique country with cities

How can I list unique country followed by related cities? Following were my product table:
name country city
p1 US New York
p2 US Boston
p3 US Chicago
k1 UK London
k2 UK Liverpool
Controller:
#countries = Product.joins(:user).distinct.where("country is not null and country <> ''").where(:users => {:merchant_status => 1}).pluck(:country)
#cities = Product.joins(:user).distinct.where("city is not null and city <> ''").where(:users => {:merchant_status => 1}).pluck(:city)
#countries.map! {|country| country.split.map(&:capitalize).join(' ')}
#search_location_country = #countries
And in my View:
<ul id="color-dropdown-menu" class="dropdown-menu dropdown-menu-right" role="menu">
<% #search_location_country.each do |country| %>
<li class="input"><%= country %></li>
<% end %>
</ul>
How can I sort the end result for drop down like this:
US
- New York
- Boston
- Chicago
UK
- London
- Liverpool
Thanks!!
EDIT
To be display something like this:
Hey you can try this way using group it gives you all distinct records
#countries_cities = Product.joins(:user).where("country is not null and country <> ''").where("city is not null and city <> ''").where(:users => {:merchant_status => 1}).group(:country, :city).select("country,city").as_json
It will give you output like
[{:country => "US", :city => "New York"}..]
If you want to again group it by country then used like
cchs = #countries_cities.group_by{|cc| cc["country"]}
Convert above multidimensional array to hash using
#country_cities_hash = = Hash[*cchs]
In your view file as
<% #country_cities_hash.each do |country, cities| %>
<li class="input"><%= country %></li>
<% cities.each do |city| %>
<li class="input"><%= "#{city}(#{country})" %></li>
<% end %>
<% end %>
Not sure I do understand the question but... I guess you have a collection of Product, looking like this :
produts = [
<Product #name="p1", #country="US" #city="New York">,
<Product #name="p1", #country="US" #city="Boston">,
<Product #name="k2", #country="FR" #city="Paris">,
...
]
In that case, to index the city names by country :
#cities_by_coutry = products.inject({}) do |index, product|
index[product.country] ||= []
index[product.country] << product.city
index
end
Which result to :
{"US"=>["New York", "Boston"], "FR"=>["Paris"]}
Then you can iterate :
#cities_by_coutry.each do |country, cities|
cities.each do |city|
puts "City: #{city} is in country {country}"
end
end

Ruby (on Rails) nested if statements with or condition

I was looking for a way to improve this piece of code:
<% if A || B %>
<a></a>
<ul>
<% if A %>
<li>Action A</li>
<% end %>
<li>Action C</li>
<% if B %>
<li>Action B</li>
<% end %>
</ul>
<% end %>
this involves to evaluate A, eventually B and then A again and B again. Any idea on how to improve that code?
P.S.
Order of actions matters.
In your controller:
#action_list = []
if A || B
#action_list << "Action A" if A
#action_list << "Action C"
#action_list << "Action B" if B
end
In your view:
<% if #action_list.count > 0 %>
<a></a>
<ul>
<% #action_list.each do |action| %>
<li><%= action %></li>
<% end %>
</ul>
<% end %>
I'd go with the same approach as msergeant, but stick it in a class like so:
class ActionListItems
def initialize(a = nil, b = nil)
#a = a
#b = b
end
def render?
!!(#a || #b)
end
def each
yield "Action A" if #a
yield "Action C"
yield "Action B" if #b
end
end
action_list_items = ActionListItems.new "a", "b"
puts action_list_items.render?
#=> true
action_list_items.each do |item|
puts item
end
#=> Action A
#=> Action C
#=> Action B

Resources