scoped Iteration variable in slim template ceases to exist when indenting - ruby-on-rails

I have the following .slim template:
.row
.col-md-12.col-lg3
.col-md-6.col-lg3
.panel.panel-default.list-panel
.panel-heading
h3.panel-title Some Title
.panel.body
ul
- #host_domains.each do |host, domains|
li
h4 for host: #{host} #{domains}
ul
- domains.each do |domain|
li
= domain
The value of #host_domains is set to a testing value of:
#host_domains = {
'some_host' => %w[www.google.com facebook.com screwyou.com],
'some_other_host' => %w[www.google.com facebook.com screwyou.com]
}
The issue is that when it starts iterating over domains I get the error undefined local variable or method 'domains'. I know it exists because it does not error on the line before (h4 for host: #{host} #{domains}) where I added domains to see if it would error (it does not).
Why does the scoped iteration variable domains cease to exist when I try to iterate over it?
It's probably something small that I'm just not seeing.
EDIT: removing the ul tag and aligning it fixes the no variable issue.
The issue is definitely indention related, changing the ul tag to say div still results in the same error.
EDIT:
I'm reasonably certain the problem is slim, it's ignoring indentation.
If I I change the template to look more like this:
- #host_domains.each do |host, domains|
li
h4 For host: #{host}
- domains.each do |domain|
li #{domain}
and then look at the generated html, I can see that instead of bunch of li tags inside of a li tag, it's putting the domain li tags next to the li tag it's supposed to be inside of.
The issue then is that slim just isn't respecting indentation and just moving things around as it pleases which is not what it should be doing.

Slim doesn't respect the indentation structure so you have to force it to do so by wrapping the inner ul/li tags within a div tag.
Like this:
- #host_domains.each do |host, domains|
li
h4 For host: #{host}
div
ul
- domains.each do |domain|
li #{domain}

Related

Pass variable from content to layout in Nanoc using Slim

I basically want to know the easiest way to pass a ruby variable from a content page to its layout using Nanoc and Slim. I am thinking of something like this:
content/content.slim:
---
title: Writeups
layout: /layout.slim
---
- age = get_age
layout/layout.slim:
doctype html
html
head
== yield
p I am #{#item[:title]} and am #{#item[:age]} years old
I know how to access values via frontmatter, but frontmatter values are fixed and what I want is a ruby function to find that value for me.
Nanoc provides a capturing helper, which makes it possible to “capture” content in one place and use it somewhere else.
content/content.slim:
---
title: Mister Tree
---
p Hello there!
- content_for :age
| hundreds of years
layout/layout.slim:
doctype html
html
body
== yield
p I am #{#item[:title]} and am #{content_for(#item, :age)} years old
lib/default.rb (or any file in lib/ of your choosing):
use_helper Nanoc::Helpers::Capturing
This generates the following output:
<!DOCTYPE html>
<html>
<body>
<p>Hello there!</p>
<p>I am Mister Tree and am hundreds of years years old</p>
</body>
</html>

How to select the smallest element that contains text

I'm using Rails 5 with Nokogiri. How do I select the smallest element that contains text?
I have this element on my page:
<td class="style35" style="font-size: medium; border: thin solid #000000">
Location</td>
and I thought I could select it using:
doc.at('td:contains("Location")')
Instead, a wrapping td that contains the table that contains this element is selected:
<td><span class="myClass"><table> ....
What is the proper way to write an expression that selects the smallest (most minimal?) element that contains the text I want?
If you use the at method it will only return the first result.
The css method will return all the elements matching the CSS selector, both the correct td element, and the td element wrapping around the whole table.
If you use something like this, it will find all the td tags, containing the word Location, then it will store the elements that is not wrapped around another td tag in an array:
td_with_no_child_and_have_location = []
doc.css("td:contains('Location')").each do |td_element|
if td_element.css("td").empty?
td_with_no_child_and_have_location << td_element
end
end
first_td = td_with_no_child_and_have_location.first
It's hard to help you if you don't supply us with the minimum HTML. I tried recreating it but YMMV:
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<html><body><table><tr>
<td><span class="myClass"><table><tr>
<td class="style35" style="font-size: medium; border: thin solid #000000">
Location</td>
</tr></table></td></tr></table></html>
EOT
doc.at('.myClass td.style35').text # => "\n Location"
If the tag you want is embedded in another table, then take advantage of some of the other characteristics to help you navigate, such as the class information.
Using at should help in this case because typically the title of a table would be in the first row which would contain the first cell. at is the equivalent of search('some selector').first.
The above selector could even be written as .myCLass .style35 or td td which would find the td inside another td. Combine that with at and you'd get the first such occurrence:
doc.at('.myClass td.style35').text # => "\n Location"
doc.at('.myClass .style35').text # => "\n Location"
doc.at('td td').text # => "\n Location"
Pick all td elements, sort by the content length and pick the first element. Change the selector as you may wish. Sort is ascending by default. So you get the smallest elements first.
doc.css('td').sort_by do |td_element|
l.text.length
end.first

How to prevent the <p> tag from wrapping around my input with tinymce in Rails?

By default, the tinymce input gets passed to the DOM as a paragraph tag:
I would like to remove that element wrapper so that tinymce passes exactly what I entered in the text editor.
How do I do that ? Please if you provide a code, can you also let me know where that code gets added ?
Regards !!!
Actually I solved my problem. All I had to do was change the styling for paragraph tag :
p {margin: 0; padding: 0;}
You need to specify the forced_root_block to false. However the documentation states that not having your root block as a <p> tag can cripple the editors behaviour. Newlines will be spaced with <br> tags instead.
tinyMCE.init({
selector: 'textarea',
forced_root_block: false
});
See the documentation here
I strip out those pesky things with gsub and regex like this:
<%= #event.desc_long.gsub(/^\<p\>/,"").gsub(/\<\/p\>$/,"") %>
First .gsub removes the <p> at the start of the TinyMCE string, and the second one removes the </p> at the end. Working great for me. This would work for any language that uses regex (gsub is for rails). JavaScript example:
var str = "{TinyMCE HTML string}";
str = str.replace(/^\<p\>/,"").replace(/\<\/p\>$/,"");
Hope this helps!
EDIT:
Re: where to put it. You leave what TinyMCE puts in your database alone. Add the above only when you display it (in the view, e-mail whatever).
In case you just want to get rid of margins:
tinymce.init({
...
setup: function(ed) {
ed.on('init', function() {
var doc = this.getDoc().getElementById("tinymce");
doc.style.margin = 0;
});
},
});

BeautifulSoup: parse only part of the page

I want to parse a part of html page, say
my_string = """
<p>Some text. Some text. Some text. Some text. Some text. Some text.
Link1
Link2
</p>
<img src="image.png" />
<p>One more paragraph</p>
"""
I pass this string to BeautifulSoup:
soup = BeautifulSoup(my_string)
# add rel="nofollow" to <a> tags
# return comment to the template
But during parsing BeautifulSoup adds <html>,<head> and <body> tags (if using lxml or html5lib parsers), and I don't need those in my code. The only way I've found up to now to avoid this is to use html.parser.
I wonder if there is a way to get rid of redundant tags using lxml - the quickest parser.
UPDATE
Originally my question was asked incorrectly. Now I removed <div> wrapper from my example, since common user does not use this tag. For this reason we cannot use .extract() method to get rid of <html>, <head> and <body> tags.
Use
soup.body.renderContents()
lxml will always add those tags, but you can use Tag.extract() to remove your <div> tag from inside them:
comment = soup.body.div.extract()
I could solve the problem using .contents property:
try:
children = soup.body.contents
string = ''
for child in children:
string += str(item)
return string
except AttributeError:
return str(soup)
I think that ''.join(soup.body.contents) would be more neat list to string converting, but this does not work and I get
TypeError: sequence item 0: expected string, Tag found

Mechanize not recognizing anchor tags via CSS selector methods

(Hope this isn't a breach of etiquette: I posted this on RailsForum, but I haven't been getting much response from there recently.)
Has anyone else had problems with Mechanize not recognizing anchor tags via CSS selectors?
The HTML looks like this (snippet with white space removed for clarity):
<td class='calendarCell' align='left'>
10
<p style="margin-bottom:15px; line-height:14px; text-align:left;">
<span class="sidenavHeadType">
Current Events</span><br />
<b><a href="http://www.mysite.org/index.php/site/
Clubs/banks_and_the_fed" class="a2">Banks and the Fed</a></b>
<br />
10:30am- 11:45am
</p>
I'm trying to collect the data from these events. Everything is working except getting the anchor within the <p>. There's clearly an <a> tag inside the <b>, and I'm going to need to follow that link to get further details on this event.
In my rake task, I have:
agent.page.search(".calendarCell,.calendarToday").each do |item|
day = item.at("a").text
item.search("p").each do |e|
anchor = e.at("a")
puts anchor
puts e.inner_html
end
end
What's interesting is that the item.at("a") always returns the anchor. But the e.at("a") returns nil. And when I do inner_html on the p element, it ignores the anchor entirely. Example output:
nil
<span class="sidenavHeadType">
Photo Club</span><br><b>Indexing Slide Collections</b>
<br>
2:00pm- 3:00pm
However, when I run the same scrape directly with Nokogiri:
doc.css(".calendarCell,.calendarToday").each do |item|
day = item.at_css("a").text
item.css("p").each do |e|
link = e.at_css("a")[:href]
puts e.inner_html
end
end
It recognizes the inside the , and it will return the href, etc.
<span class="sidenavHeadType">
Bridge Party</span><br><b>Party Bridge</b>
<br>
7:00pm- 9:00pm
Mechanize is supposed to use Nokogiri, so I'm wondering if I have a bad version or if this affects others as well.
Thanks for any leads.
Never mind. False alarm. In my Nokogiri task, I was pointing to a local copy of the page that included the anchors. The live page required a login, so when I browsed to it, I could see the a tags. Adding the login to the rake task solved it.

Resources