Search code examples
htmlrubyerbtilt

How to make ERB or ERUBI escape HTML in the template without escaping the entire template - NOT RAILS


I'm using ERB or ERUB or Tilt here outside of Rails and I want to make my HTML templates work like rails where stuff is HTML escaped by default.

e.g.

<!-- file.html.erb -->
<h1>Hello <%= name %></h1>

When I render this, if name is <b>Pat</b>, then the HTML sent to the browser is:

<h1>Hello <b>Pat</b></h1>

I'd like it to render this:

<h1>Hello &lt;b&gt;Pat&lt;/b&gt;</h1>

And I'd like that behavior to be the default anytime <%= is used.

I'm trying Tilt, but when I do Tilt.new("file.html.erb",escape_html: true) it escapes the entire file, rendering &lt;h1&gt;Hello &lt;b&gt;Pat&lt;/b&gt;&lt;/h1&gt;

I tried reading Rails' source to figure this out, but it's highly abstracted and wondering if anyone reading this happens to know how to achieve this.


Solution

  • Answering my own question -> Erubi and Tilt + Erbui does actually work the way I want, but my problem was in using yield to create a layout-style nested set of templates.

    In the most basic case, things work:

    <!-- outer.html.erb -->
    <h1>Hello! <%= name %></h1>
    
    require "tilt"
    
    name = "<b>Pat</b>"
    
    template = Tilt.new("outer.html.erb",
                        engine: Erubi::Engine,
                        escape_html: true)
    s = template.render(self, name: name)
    puts s
    

    produces:

    <h1>Hello! &lt;b&gt;Pat&lt;/b&gt;</h1>
    

    What I was trying to do, which I omitted from the question because I thought I was simplifying, was to do a layout-style nested template setup.

    <!-- inner.html.erb -->
    <h2>Yup <%= name %></h2>
    
    <!-- outer.html.erb -->
    <h1>Hello! <%= name %></h1>
    
    <%= yield %>
    
    require "tilt"
    
    name = "<b>Pat</b>"
    
    inner = Tilt.new("inner.html.erb",
                     engine: Erubi::Engine,                  
                     escape_html: true)
    rendered_inner = inner.render(self, name: name)
    
    template = Tilt.new("outer.html.erb",
                        engine: Erubi::Engine,
                        escape_html: true)
    s = template.render(self, name: name) do
      rendered_inner
    end
    puts s
    

    Because I'm yielding a string containing HTML to render, I get the HTML escaped when I didn't want it to:

    <h1>Hello! &lt;b&gt;Pat&lt;/b&gt;</h1>
    &lt;h2&gt;Yup &amp;lt;b&amp;gt;Pat&amp;lt;/b&amp;gt;&lt;/h2&gt;
    

    To me, it appeared as if escape_html: was escaping everything.

    The solution is to use <%== in outer.html.erb:

    <!-- outer.html.erb -->
    <h1>Hello! <%= name %></h1>
    <%== yield %>
    

    Which does what I want:

    <h1>Hello! &lt;b&gt;Pat&lt;/b&gt;</h1>
    <h2>Yup &lt;b&gt;Pat&lt;/b&gt;</h2>
    

    Whew!

    Of course, omitted escape_html: true does not escape by default.