Embedding escaped code in a Jade template

Making google-code-prettify and Jade place nice together in node.js

A while back I had built a bit of a blogging platform using the ubiquitous node.js stack of Express and Jade templates. It was pretty straight-forward — blog posts were just written in Jade and served with Express — but syntax highlighting ended up being a problem.

I had opted to use google-code-prettify, which takes an escaped <code> block of HTML like this and highlights it:

<pre class="prettyprint">
    <code>
        &lt;html&gt;
            &lt;head&gt;&lt;/head&gt;
            &lt;body&gt;&lt;/body&gt;
        &lt;/html&gt;
    </code>
</pre>

I really didn't want to pre-escape my code, of course. That's horrible. And as far as I can tell, Jade has no way to automatically escape HTML like this, unless it's already stored in a variable.

Solution: Custom Filters

Jade's documentation briefly mentions filters. The syntax is just a word prefixed with a colon. There are a few canned ones like :markdown. I jumped in and wrote my own custom filter, :code, to strip out significant HTML characters and replace them with &lt; &gt; and the like.

require('jade').filters.code = function( block ) {
    return block
        .replace( /&/g, '&amp;' )
        .replace( /</g, '&lt;' )
        .replace( />/g, '&gt;' )
        .replace( /"/g, '&quot;' );
}

Cryptic errors ensued.

SyntaxError: Unexpected token ILLEGAL
    at Object.Function (unknown source)
    at Object.compile (/Users/paul/Code/paulzumbrun.com/node_modules/jade/lib/jade.js:176:8)
    at Function.compile (/Users/paul/Code/paulzumbrun.com/node_modules/express/lib/view.js:68:33)

Turns out the result still gets parsed by Jade; it's not just a string that gets dropped in like I thought. This is actually a good thing because it means you can chain filters, but we have to work around it. To make the parser happy with me again I had to strip out a few extra characters. Newlines needed to become \n. Other escape sequences needed double-escaped. Hashes need stripped too, since they're used to execute JavaScript inline #{thusly}.

This is the final working filter.

require('jade').filters.code = function( block ) {
    return block
        .replace( /&/g, '&amp;'  )
        .replace( /</g, '&lt;'   )
        .replace( />/g, '&gt;'   )
        .replace( /"/g, '&quot;' )
        .replace( /#/g, '&#35;'  )
        .replace( /\\/g, '\\\\'  )
        .replace( /\n/g, '\\n'   );
}

Now when you go to use this in a template, all you have to do is this. Pretty close to my original goal.

pre.prettyprint
    code.lang-html
        :code
            <html>
                <head></head>
                <body></body>
            </html>

There may be a cleaner way to do this, but I sure couldn't find it with the search terms I was using.

Comments

comments powered by Disqus