Django/Jinja2 Language Definition for Prism.js

 Oct. 26, 2016     0 comments

Recently I made some improvements to this blog CMS in order to better handle code samples in various programming languages. One of the improvements is a brand-new language definition for Prism syntax highlighter. Prism is an easy to use JavaScript library for in-browser code syntax highlighting that supports many languages (programming, markup, configuration etc.). Unfortunately, Django template languages was not among them so I decided to fill-in this gap. And because Jinga2 template syntax is very similar, I decided to make one language definition for both.

Here is my Django/Jinja2 language definition (prism-django.js):

// Django/Jinja2 syntax definition for Prism.js <http://prismjs.com> syntax highlighter.
// Mostly it works OK but can paint code incorrectly on complex html/template tag combinations.

var _django_template = {
  'property': {
    pattern: /(?:{{|{%)[\w\W]*?(?:%}|}})/g,
    greedy: true,
    inside: {
      'string': {
        pattern: /("|')(?:\\\\|\\?[^\\\r\n])*?\1/,
        greedy: true
      },
      'keyword': /\b(?:\||load|verbatim|widthratio|ssi|firstof|for|url|ifchanged|csrf_token|lorem|ifnotequal|autoescape|now|templatetag|debug|cycle|ifequal|regroup|comment|filter|endfilter|if|spaceless|with|extends|block|include|else|empty|endif|endfor|as|endblock|endautoescape|endverbatim|trans|endtrans|[Tt]rue|[Ff]alse|[Nn]one|in|is|static|macro|endmacro|call|endcall|set|endset|raw|endraw)\b/,
      'operator' : /[-+=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,
      'function': /\b(?:_|abs|add|addslashes|attr|batch|callable|capfirst|capitalize|center|count|cut|d|date|default|default_if_none|defined|dictsort|dictsortreversed|divisibleby|e|equalto|escape|escaped|escapejs|even|filesizeformat|first|float|floatformat|force_escape|forceescape|format|get_digit|groupby|indent|int|iriencode|iterable|join|last|length|length_is|linebreaks|linebreaksbr|linenumbers|list|ljust|lower|make_list|map|mapping|number|odd|phone2numeric|pluralize|pprint|random|reject|rejectattr|removetags|replace|reverse|rjust|round|safe|safeseq|sameas|select|selectattr|sequence|slice|slugify|sort|string|stringformat|striptags|sum|time|timesince|timeuntil|title|trim|truncate|truncatechars|truncatechars_html|truncatewords|truncatewords_html|undefined|unordered_list|upper|urlencode|urlize|urlizetrunc|wordcount|wordwrap|xmlattr|yesno)\b/,
      'important': /\b-?\d+(?:\.\d+)?\b/,
      'variable': /\b\w+?\b/,
      'punctuation' : /[[\];(),.:]/
    }
  },
};

Prism.languages.django = Prism.languages.extend('markup', {'comment': /(?:<!--|{#)[\w\W]*?(?:#}|-->)/});
// Updated html tag pattern to allow template tags inside html tags
Prism.languages.django.tag.pattern = /<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^>=]+))?)*\s*\/?>/i;
Prism.languages.insertBefore('django', 'entity', _django_template);
Prism.languages.insertBefore('inside', 'tag', _django_template, Prism.languages.django.tag);

if (Prism.languages.javascript) {
  // Combine js code and template tags painting inside <script> blocks
  Prism.languages.insertBefore('inside', 'string', _django_template, Prism.languages.django.script);
  Prism.languages.django.script.inside.string.inside = _django_template;
}
if (Prism.languages.css) {
  // Combine css code and template tags painting inside <style> blocks
  Prism.languages.insertBefore('inside', 'atrule', {'tag': _django_template.property}, Prism.languages.django.style);
  Prism.languages.django.style.inside.string.inside = _django_template;
}

// Add an Jinja2 alias
Prism.languages.jinja2 = Prism.languages.django;

Feel free to use it as you like. Consider it MIT licensed. Update: Now this definition is officially included in Prism! Just select the necessary language on the Prism download page.

The following examples show how it works with Django and Jinga2 templates.

Django (base.html from django.contrib.admin):

 Click to show
{% load i18n static %}<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
<head>
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}" />
{% block extrastyle %}{% endblock %}
{% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}" />{% endif %}
{% block extrahead %}{% endblock %}
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
</head>
{% load i18n %}

<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}"
  data-admin-utc-offset="{% now "Z" %}">

<!-- Container -->
<div id="container">

    {% if not is_popup %}
    <!-- Header -->
    <div id="header">
        <div id="branding">
        {% block branding %}{% endblock %}
        </div>
        {% block usertools %}
        {% if has_permission %}
        <div id="user-tools">
            {% block welcome-msg %}
                {% trans 'Welcome,' %}
                <strong>{% firstof user.get_short_name user.get_username %}</strong>.
            {% endblock %}
            {% block userlinks %}
                {% if site_url %}
                    <a href="{{ site_url }}">{% trans 'View site' %}</a> /
                {% endif %}
                {% if user.is_active and user.is_staff %}
                    {% url 'django-admindocs-docroot' as docsroot %}
                    {% if docsroot %}
                        <a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
                    {% endif %}
                {% endif %}
                {% if user.has_usable_password %}
                <a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a> /
                {% endif %}
                <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
            {% endblock %}
        </div>
        {% endif %}
        {% endblock %}
        {% block nav-global %}{% endblock %}
    </div>
    <!-- END Header -->
    {% block breadcrumbs %}
    <div class="breadcrumbs">
    <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
    {% if title %} &rsaquo; {{ title }}{% endif %}
    </div>
    {% endblock %}
    {% endif %}

    {% block messages %}
        {% if messages %}
        <ul class="messagelist">{% for message in messages %}
          <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message|capfirst }}</li>
        {% endfor %}</ul>
        {% endif %}
    {% endblock messages %}

    <!-- Content -->
    <div id="content" class="{% block coltype %}colM{% endblock %}">
        {% block pretitle %}{% endblock %}
        {% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}
        {% block content %}
        {% block object-tools %}{% endblock %}
        {{ content }}
        {% endblock %}
        {% block sidebar %}{% endblock %}
        <br class="clear" />
    </div>
    <!-- END Content -->

    {% block footer %}<div id="footer"></div>{% endblock %}
</div>
<!-- END Container -->

</body>
</html>

Jinja2 (layout.html from Sphinx basic theme):

 Click to show
{#
    basic/layout.html
    ~~~~~~~~~~~~~~~~~

    Master layout template for Sphinx themes.

    :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
#}
{%- block doctype -%}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
{%- endblock %}
{%- set reldelim1 = reldelim1 is not defined and ' &raquo;' or reldelim1 %}
{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
                         (sidebars != []) %}
{%- set url_root = pathto('', 1) %}
{# XXX necessary? #}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if not embedded and docstitle %}
  {%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
{%- else %}
  {%- set titlesuffix = "" %}
{%- endif %}

{%- macro relbar() %}
    <div class="related" role="navigation" aria-label="related navigation">
      <h3>{{ _('Navigation') }}</h3>
      <ul>
        {%- for rellink in rellinks %}
        <li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
          <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
             {{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
          {%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
        {%- endfor %}
        {%- block rootrellink %}
        <li class="nav-item nav-item-0"><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
        {%- endblock %}
        {%- for parent in parents %}
          <li class="nav-item nav-item-{{ loop.index }}"><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
        {%- endfor %}
        {%- block relbaritems %} {% endblock %}
      </ul>
    </div>
{%- endmacro %}

{%- macro sidebar() %}
      {%- if render_sidebar %}
      <div class="sphinxsidebar" role="navigation" aria-label="main navigation">
        <div class="sphinxsidebarwrapper">
          {%- block sidebarlogo %}
          {%- if logo %}
            <p class="logo"><a href="{{ pathto(master_doc) }}">
              <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
            </a></p>
          {%- endif %}
          {%- endblock %}
          {%- if sidebars != None %}
            {#- new style sidebar: explicitly include/exclude templates #}
            {%- for sidebartemplate in sidebars %}
            {%- include sidebartemplate %}
            {%- endfor %}
          {%- else %}
            {#- old style sidebars: using blocks -- should be deprecated #}
            {%- block sidebartoc %}
            {%- include "localtoc.html" %}
            {%- endblock %}
            {%- block sidebarrel %}
            {%- include "relations.html" %}
            {%- endblock %}
            {%- block sidebarsourcelink %}
            {%- include "sourcelink.html" %}
            {%- endblock %}
            {%- if customsidebar %}
            {%- include customsidebar %}
            {%- endif %}
            {%- block sidebarsearch %}
            {%- include "searchbox.html" %}
            {%- endblock %}
          {%- endif %}
        </div>
      </div>
      {%- endif %}
{%- endmacro %}

{%- macro script() %}
    <script type="text/javascript">
      var DOCUMENTATION_OPTIONS = {
        URL_ROOT:    '{{ url_root }}',
        VERSION:     '{{ release|e }}',
        COLLAPSE_INDEX: false,
        FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
        HAS_SOURCE:  {{ has_source|lower }}
      };
    </script>
    {%- for scriptfile in script_files %}
    <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
    {%- endfor %}
{%- endmacro %}

{%- macro css() %}
    <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
    <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
    {%- for cssfile in css_files %}
    <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
    {%- endfor %}
{%- endmacro %}

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
    {{ metatags }}
    {%- block htmltitle %}
    <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
    {%- endblock %}
    {{ css() }}
    {%- if not embedded %}
    {{ script() }}
    {%- if use_opensearch %}
    <link rel="search" type="application/opensearchdescription+xml"
          title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
          href="{{ pathto('_static/opensearch.xml', 1) }}"/>
    {%- endif %}
    {%- if favicon %}
    <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
    {%- endif %}
    {%- endif %}
{%- block linktags %}
    {%- if hasdoc('about') %}
    <link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
    {%- endif %}
    {%- if hasdoc('genindex') %}
    <link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
    {%- endif %}
    {%- if hasdoc('search') %}
    <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
    {%- endif %}
    {%- if hasdoc('copyright') %}
    <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
    {%- endif %}
    <link rel="top" title="{{ docstitle|e }}" href="{{ pathto(master_doc) }}" />
    {%- if parents %}
    <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
    {%- endif %}
    {%- if next %}
    <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
    {%- endif %}
    {%- if prev %}
    <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
    {%- endif %}
{%- endblock %}
{%- block extrahead %} {% endblock %}
  </head>
  <body role="document">
{%- block header %}{% endblock %}

{%- block relbar1 %}{{ relbar() }}{% endblock %}

{%- block content %}
  {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}

    <div class="document">
  {%- block document %}
      <div class="documentwrapper">
      {%- if render_sidebar %}
        <div class="bodywrapper">
      {%- endif %}
          <div class="body" role="main">
            {% block body %} {% endblock %}
          </div>
      {%- if render_sidebar %}
        </div>
      {%- endif %}
      </div>
  {%- endblock %}

  {%- block sidebar2 %}{{ sidebar() }}{% endblock %}
      <div class="clearer"></div>
    </div>
{%- endblock %}

{%- block relbar2 %}{{ relbar() }}{% endblock %}

{%- block footer %}
    <div class="footer" role="contentinfo">
    {%- if show_copyright %}
      {%- if hasdoc('copyright') %}
        {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
      {%- else %}
        {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
      {%- endif %}
    {%- endif %}
    {%- if last_updated %}
      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
    {%- endif %}
    {%- if show_sphinx %}
      {% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
    {%- endif %}
    </div>
{%- endblock %}
  </body>
</html>

As you can see, the language definition works quite well, except for small issues on complex html/template tag combinations.

  DjangoJavaScript