Martin Geber
Martin is student at EUFH, Cologne, DE
working casually at DataCollect & EWE
who loves economies, web-technology
and all things J.K. Rowling.
Learn More.
I always loved being able to link back a certain chapter of a webpage. Sometimes it is great to just link to the source code of a weblog entry or any other part of it. To enable the visitors of my weblog to do that I wanted to set up some kind of Table of Contents.
I wanted it to be flexible, so that I can adjust whether the table of contents is shown or not. This makes sense, because some articles have just two headlines and a table of contents with two points looks pathetic. So the users would be able to click on the headline and jump to it directly. (Now they can copy and paste the URL given in the address line.)
Like discussed in Markdown With Syntax Highlighting In Django I don't use an HTML-WYSIWYG Editor any more.
Markdown enables me to write much faster and is much slimmer (but needs longer to load, though). As Markdown already has an extension for creating a table of contents I just used it.
So I installed it, similar to CodeHilite, and changed my Django-template to this:
{{ object.entry|markdown:"codehilite,toc" }}
The result was incredibly horrible:
the-first-try-syntaxhighlighter-with-tiny-mce__MD_autoTOC_0
So I started rewriting the extension. After hours and hours I just quit on it. I choose to write my own filter.
First of all, I started to think of what my filter need to be able to.
In the next step I needed to set the filter up. In case you know how to set a template filter up, skip the following paragraph.
According to the Django Documentation, we need to set up an folder into an existing application. I use utils/templatetags, where utils is my application (included in your INSTALLED_APPS-setting-variable) and templatetags is predefined by Django.
In my entry about how to set up weblog archives, I created a file called taglib.py, therefore I recommend to create a file called filterlib.py now.
(I'm not sure if this is the best way, if you have another idea, let me know)
Now, we have to write the filter:
from django import template from django.shortcuts import render_to_response from django.template.defaultfilters import stringfilter from django.conf import settings import re register = template.Library() @register.filter(name='toc') @stringfilter def table_of_contents(html, show_toc=None): LINK_CLASS = 'anchor' TOC_ID = 'toc' re_tags = re.compile('<.*?>', re.DOTALL) re_entities = re.compile('&.*?;', re.DOTALL) def slug(text): to_minus = [' ', '-', '_'] text = re.sub(re_tags,'', text.lower()) text = re.sub(re_entities,'', text) cleaned = [] for l in text: if l in to_minus: l = '-' elif not l.isalnum(): continue cleaned.append(l) cleaned = re.sub('-{2,}', '-', ''.join(cleaned)) cleaned = cleaned.startswith('-') and cleaned[1:] or cleaned cleaned = cleaned.endswith('-') and cleaned[:-1] or cleaned return cleaned headlines = re.findall('<h(?P<type>\d)>(.*?)<\/h(?P=type)>', html) if len(headlines) is 0: return html last_weight, toc = min([int(x[0]) for x in headlines]), '' for h in headlines: current_weight = int(h[0]) s = slug(h[1]) html = html.replace('<h%s>%s</h%s>' % (h[0], h[1], h[0]), '<h%s><a href="#%s" id="%s" class="%s">%s</a></h%s>' % (current_weight, s, s, LINK_CLASS, h[1], current_weight)) if show_toc is not None: if (current_weight-last_weight) >= 1: toc = '%s\n\t<ul>\n' % toc[:-6] * (current_weight-last_weight) elif (last_weight-current_weight) >= 1: toc += '\t</ul>\n\t\t</li>\n'*(last_weight-current_weight) toc += '\t\t<li><a href="#%s">%s</a></li>\n' % (s, h[1]) last_weight = current_weight if show_toc is not None: toc += '\t</ul>\n'*(toc.count('<ul>') - toc.count('</ul>')) toc += '\t\t</li>\n'*(toc.count('<li>') - toc.count('</li>')) toc = '<div id="%s">%s\n\t<ul>\n%s\t</ul>\n</div>' % (TOC_ID, show_toc, toc) return '%s %s' % (toc, html)
The Table of Contents will be added above the given HTML. This is a difference compared to the Markdown extension. But this is better for accessibility reason. In case someone browses your website by a screen reader, s/he will be thankful to get an overview of the contents before reading all the contents and hear the contents later on.
I've written my own slug function, but you are welcome to use the slugify-function by Django. Do this:
from django.template.defaultfilters import slugify as slug def table_of_contents(html, show_toc=None): LINK_CLASS = 'anchor' TOC_ID = 'toc' re_tags = re.compile('<.*?>', re.DOTALL) re_entities = re.compile('&.*?;', re.DOTALL) # Remove ``def slug()`` headlines = re.findall('<h(?P<type>\d)>(.*?)<\/h(?P=type)>', html)
Let me know, when you change anything else, so I can add it here, maybe others are interested in your idea as well.
I've used two variables, which you can easily customize:
LINK_CLASS -- Is added to the headlines, so you can modify them using CSS
TOC_ID -- ID of the Table of Content container, also used to change its CSS
After you've added the source code to your templatetags folder, e.g. in the file filterlib.py, you can create your template.
In case you don't want a table of contents above the text, use the filter like this:
{% load filterlib %}
<!-- Don't supply any argument to the filter, this enables the
headlines to be linked back, and click able (referring to themselves),
but there is no table of contents shown above -->
{{ object.html_text|toc }}
<!-- Sample result: -->
<h1><a href="#testing-first-headline" id="testing-first-headline">Testing first headline</a></h1>
<p>Test of first headline</p>
<h2><a href="#just-testing" id="just testing">Just Testing</a></h2>
<p>Test of second headline</p>
In case you'd like to have a table of contents above the text, use the filter like this:
{% load filterlib %}
<!-- Create a Template Tag, which will add a table of contents
above the text titled with "Contents" as headline 2
As I don't know which headline dimension, you prefer, I
choose, to let the HTML around the TOC-Heading be flexible
as well. -->
{{ object.html_text|toc:"<h2>Contents</h2>" }}
<!-- Sample result:
(Note the original HTML-Tags within the headlines are still there.) -->
<div id="toc"><h2>Contents</h2>
<ul>
<li><a href="#link-relarchives-html-tag"><code><link rel="archives" /></code>-HTML-Tag</a>
<ul>
<li><a href="#how-the-link-tag-works">How the <code><link></code>-tag works</a></li>
</ul>
</li>
</ul>
</div>
The only problem, I have with this filter is, that it isn't translation able. This means you have to give the filter one or more words in one language without being able to use i18n.
I hope someone finds a solution for that, and tells me, how to do it.
Congratulations! You just added a real overvalue to your website. Users are now able to link to certain parts of the page and handycaped persons have the ability (in case you use the table of contents) to jump directly to parts of their interest.
And you have the ability to refer to parts of old entries (in case you have a weblog), what provides a better browsing experience to readers of you page.
Python Code is Poetry
© Copyright 1987-2008
Martin Geber
Django | XHTML 1.1 | CSS | Imprint