Evaluating string templates - ruby-on-rails

I have a string template as shown below
template = '<p class="foo">#{content}</p>'
I want to evaluate the template based on current value of the variable called content.
html = my_eval(template, "Hello World")
This is my current approach for this problem:
def my_eval template, content
"\"#{template.gsub('"', '\"')}\"" # gsub to escape the quotes
end
Is there a better approach to solving this problem?
EDIT
I used HTML fragment in the sample code above to demonstrate my scenario. My real scenario has set of XPATH templates in a configuration file. The bind variables in the template are substituted to get a valid XPATH string.
I have thought about using ERB, but decided against as it might be a overkill.

You can do what you want with String's native method '%':
> template = "<p class='foo'>%s</p>"
> content = 'value of content'
> output = template % content
> puts output
=> "<p class='foo'>value of content</p>"
See http://ruby-doc.org/core/classes/String.html#M000770

You can render a string as if it were an erb template. Seeing that you're using this in a rake task you're better off using Erb.new.
template = '<p class="foo"><%=content%></p>'
html = Erb.new(template).result(binding)
Using the ActionController methods originally suggested, involves instantiating an ActionController::Base object and sending render or render_to_string.

I can't say I really recommend either of these approaches. This is what libraries like erb are for, and they've been throughly tested for all the edge cases you haven't thought of yet. And everyone else who has to touch your code will thank you. However, if you really don't want to use an external library, I've included some recommendations.
The my_eval method you included didn't work for me. Try something like this instead:
template = '<p class="foo">#{content}</p>'
def my_eval( template, content )
eval %Q{"#{template.gsub(/"/, '\"')}"}
end
If you want to generalize this this so you can use templates that have variables other than content, you could expand it to something like this:
def my_eval( template, locals )
locals.each_pair{ |var, value| eval "#{var} = #{value.inspect}" }
eval %Q{"#{template.gsub(/"/, '\"')}"}
end
That method would be called like this
my_eval( '<p class="foo">#{content}</p>', :content => 'value of content' )
But again, I'd advise against rolling your own in this instance.

This is also a nice one:
template = "Price of the %s is Rs. %f."
# %s - string, %f - float and %d - integer
p template % ["apple", 70.00]
# prints Price of the apple is Rs. 70.000000.
more here

To late but I think a better way is like ruby-style-guide:
template = '<p class="foo">%<content>s</p>'
content_text = 'Text inside p'
output = format( template , content: content_text )

Related

Markdown Render Newlines

I'm working on a Project and give an user the possibility to create a Post.
With loading the Post, i'm calling the markdown method, to extract links and format the text.
Now i got a Problem.
By writing "1. Example" the Output in the Post is a list.
By just writing "1.Example"_ without the whitespace between the point and the text, it'working fine.
My markdown method:
#preview = nil
options = {
autolink: true,
hard_wrap: true
}
begin
URI.extract(text, ['http', 'https', 'www']).each do |uri|
unless text.include?("<a")
text = text.gsub( uri, "#{uri}" )
#preview = LinkThumbnailer.generate(uri)
end
end
rescue OpenSSL::SSL::SSLError => e
end
renderer = Redcarpet::Render::HTML.new(options)
markdown = Redcarpet::Markdown.new(renderer)
markdown.render(text).html_safe
May you know, how to fix it.. I don't want the list, i just want the Output to be the same like the Input!
Thank you, for your time!
EDIT Added a photo to show the output.
You want to use a backslash escape in your Markdown source. As the rules explain:
Markdown allows you to use backslash escapes to generate literal characters which would otherwise have special meaning in Markdown’s formatting syntax.
Among the characters which backlash escaping supports is the dot (.). Therefore your source text should look like this:
1\. Example
Which results in this HTML:
<p>1. Example</p>
And renders as:
1. Example
By default you're going to get the list. Markdown is after all looking for syntax that it recognises in order to generate mark up.
In order to skip particular markdown features I think you're going to need to provide your own custom renderer.
If you define a new renderer:
class NoListRenderer < Redcarpet::Render::HTML
def list(contents, list_type)
contents
end
def list_item(text, list_type)
text
end
end
and use an instance of that instead of the default renderer class when you create your markdown instance it should skip the default list processing. (NB. I haven't tested this code):
renderer = NoListRenderer.new(options)
markdown = Redcarpet::Markdown.new(renderer)

Replacing html text based on database

I want to make app which will switch vocabulary in desired url of webpage Japanese to English.
But firstable I want start form just simply display desired url of webpage inline lust like Google Translate.(See here)
I got html data from desired url using code below,
and now I want to replace text in html all at same time based data on database.
def submit
require 'open-uri'
charset = nil
#html = open(params[:url]) do |f|
charset = f.charset
f.read
end
end
Database is undone, but I am going to contain Japanese vocabulary which should be switched, and English vocabulary which should be switched instead of Japanese vocabulary.
Any ideas or ways to do this?
Also, I just started learning Ruby on Rails recently so it would be nice if you explain it with some examples or detailed explanation :)
I just want to replace particular word in text based on item on database,I don't want to multilingualism.
EDIT:
For example i got following html below from desired webpage.
<html>
<head>
</head>
<body>
<p>I want to switch "aaa" this and "ccc"</p>
</body>
</html>
Lets say I want to switch(Replace) "aaa" to "bbb", "ccc" to "ddd".
Word that should be switched and be switched instead of previous word are in database.(Target:"aaa","ccc" Switch:"bbb","ddd")
since this html is the one i got it using open-uri, i can't implement code like #{target}.
Working based on the code in this answer and this answer, you could do something like this:
replacements = {'aaa' => 'ccc', 'bbb' => 'ddd' }
regex = Regexp.new(replacements.keys.map { |x| Regexp.escape(x) }.join('|'))
doc = Nokogiri::HTML::DocumentFragment.parse(html)
doc.traverse do |x|
if x.text?
x.content = x.content.gsub(regex, replacements)
end
end
I've also tested that:
replacements = {'こんにちは' => 'Good day', 'bbb' => 'ddd' }
regex = Regexp.new(replacements.keys.map { |x| Regexp.escape(x) }.join('|'))
"こんにちは Mr bbb".gsub(regex, replacements)
Gives the expected:
Good day Mr ddd
You might also want to use:
regex = Regexp.new(replacements.keys.map { |x| '\\b'+Regexp.escape(x)+'\\b' }.join('|'))
to prevent "aaardvark" being changed into "cccrdvark".

Output slim to variable

I'm using Base64.encode64(val) to convert html to base64.
Example:
- val = link_to 'Link', link_path
= Base64.encode64(val)
But how can I get slim markup to variable? Like so:
.class = link_to 'Link', link_path # <- this output with slim div
Or even multiple lines
div
span
.another_div
There is a way by putting slim code into partial and do this:
- var = render 'partial'
= Base64.encode64(var) # Convert into base64
How to do this without partial?
Slim exposes its templating through the Tilt interface, like so:
# Render a template file:
Slim::Template.new("template.slim", options).render(scope)
# Render a string:
Slim::Template.new(options) { "b slim markup" }.render(scope)
Where options is an optional hash of options for slim and scope is the object in which the template code is executed.
So the following:
slim_markup = <<-SLIM
div
span
.another_div
SLIM
# The options hash and scope have been omitted for the sake of simplicity
html_output = Slim::Template.new { slim_markup }.render
Sets the value of html_output to:
<div></div>
<span></span>
<div class="another_div"></div>
But for your example with the url helper link_path, you must provide slim a scope in which the url helpers are available e.g. a controller.
This is an old question, but I have wondered about this many times, and I always spend a lot of time researching it.
Using Slim 4 you can use capture directly:
- val = capture
div
span
.another_div
This will put the rendered slim into your variable.
Another way from the box using capture method. From the docs:
Using the Binding you can capture to local variables as follows:
module Helpers
def capture_to_local(var, &block)
set_var = block.binding.eval("lambda {|x| #{var} = x }")
# In Rails we have to use capture!
# If we are using Slim without a framework (Plain Tilt),
# you can just yield to get the captured block.
set_var.call(defined?(::Rails) ? capture(&block) : yield)
end
end
The helper can then be used in the Slim template as follows
/ The captured_content variable must be known by the Binding beforehand.
= capture_to_local captured_content=:captured_content
p This will be captured in the variable captured_content
= captured_content
Read more https://github.com/slim-template/slim#capturing-to-local-variables

HAML filters in a helper

Helper functions can receive a block which they yield to render the block. Sometimes I'd want that block to be spec'd with a filter. So for example:
= doc_page title: 'FAQ' do
:markdown
# Welcome to the *FAQ*
This is not so DRY as we are always writing doc_page and markdown together. Can I make the helper method accept a block and pass it through a HAML filter. Something like:
= doc_page title: 'FAQ' do
# Welcome to the *FAQ*
In this fantasy, doc_page is a helper method that does some setup stuff and then passes the content through markdown, saving us the need to declare :markdown everywhere and making the world a DRYer place.
Currently it is not possible to use filters in helpers. An alternative approach would be to use redcarpet to parse the markdown and then pass the output to a helper.
an example would be:
= doc_page title: 'FAQ', :markdown do
### my markdown
= doc_page title: 'FAQ' do
normal html
The implementation of the doc_page would be something like this:
def doc_page(title, markup=:html)
content = yield
if markup == :markdown
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML)
content = markdown.render(content)
end
content
end
This would solve your problem, as you define your markdown filter in the helper. And you don't need an extra indentation level in your haml.
You could use tilt (the api haml uses to render markdown) directly instead of through haml. Probably something like this (not tested).
markdown_template = Tilt['md'].new { "# this is markdown code" }
markdown_template.render
You can find a similar example in the Tilt docs.
But this is more of an idea than a complete implementation.
I'm afraid, but it's not possible, because haml is a preprocessor, basicly this piece of code:
= doc_page title: 'FAQ' do
# Welcome to the *FAQ*
%a href="/" link
Will be converted in runtime ruby code like:
concat(doc_page title: 'FAQ' do
# Welcome to the *FAQ*
concat('link')
end)

Rails - print a "recursive" html variable in Slim

I would like to render in my view a "html"-content variable, let me explain:
Somewhere in a helper there is a pseudocode like this
# view helper for an ERB view
def render_something_recursively(i = 1)
html = "<li>"
html << "hi number #{i}"
if i < 1000
html << render_something_recursively(i++)
end
html << "</li>"
end
Sorry for the bad example but I hope that it give you an idea, in facts I would like to iterate on a hierarchy structure (a tree) with flexible depth. For this reason I need a recursive method and I would like to keep it out from the view.
My question is: how can I accomplish to the same result but in Slim (or eventually in HAML)? How can I give the information of "indentation" at the htmlvariable?
Is it possible or I must use an ERB view?
My final goal should be "easily" something like that:
# recursive_list.html.slim
ul class="a_recursive_list"
=render_something_recursively
Use content_tag and it will render whatever templating engine you are using:
def render_something_recursively(i = 1)
content_tag(:li, "hi number #{i}") do
if i < 1000
render_something_recursively(i++)
end
end
end
Additionally, I don't think you should nest <li> elements directly inside each other, you should either render text or a <ul> element nested inside the <li>.

Resources