Croutons is a presentation templating library for HTML, written in Python. Croutons allows HTML pages' look-and-feel to be reused without any tedious mucking about with server-side includes or equivalent technology.
Croutons provides mechanisms for:
Croutons makes this as easy as possible without the need to modify any existing pages or to create specially coded templates or include files.
Croutons can be installed as an apache handler so that HTML files containing croutons markup can be processed automatically by the server. Croutons also contains a WSGI middleware component so that it may be combined with other server-side applications.
Croutons is based on Beautiful Soup, a library for parsing HTML. Croutons allows you to sprinkle new content into your tag soup – hence the name.
Croutons is available under a BSD Licence. The croutons software distribution contains Beautiful Soup. Beautiful Soup is licensed under the same terms as Python itself.
% python setup.py install
Note that you may need to have administrator or root access to
your system for this command to complete successfully.
To configure the Apache web server to display crouton pages, use the following steps. If you do not use Apache, you will have to find the equivalent instructions for your web server
# Enable croutons support
AddHandler cgi-script .cgi
ScriptAlias /crouton-handler /path/to/crouton.cgi
Action crouton-page /crouton-handler
AddHandler crouton-page .crouton
The ScriptAlias directive will need to change depending on your
system configuration. "/path/to/crouton-cgi.py"
will usually be /usr/local/bin/crouton-cgi.py or
/usr/bin/crouton-cgi.py on unix-like systems. On
other systems examine the output of the python setup.py
install command to find where the script has been
installed.
You can also add or change the file extensions associated with crouton files by modifying the AddHandler line.
If you have installed mod_fastcgi, you can substitute the following configuration for improved performance.
# Enable croutons support
AddHandler fastcgi-script .fcgi
ScriptAlias /crouton-handler /path/to/crouton.fcgi
Action crouton-page /crouton-handler
AddHandler crouton-page .crouton
Let's start with an example. For this we need two files: one ordinary static HTML page and a croutons template page. For the purposes of this example, we're going to create a new and rather simplistic HTML page, although you could equally well use an existing page of your choice.
We will use this as the template, providing a look and feel that we can reuse in our croutons page later on.
In your website's document root, create a new HTML page with the following content, and save it with the filename template.html:
<html>
<head>
<title>A static html page</title>
<style type="text/css">
body { font-family: sans-serif; }
#Header { background-color: #eee; }
#Footer { background-color: #eee; }
#Content { padding: 2em; }
</style>
</head>
<body>
<div id="Header">
<h1>This is the header section</h1>
</div>
<div id="Content">
<h1>This is the main content area.</h1>
<p>
The content in this area will change from page to page, while the
header and footer should remain static.
</p>
</div>
<div id="Footer">
<h1>This is the footer section</h1>
</div>
</body>
</html>
We can now create a croutons page based on the template.html page you created above. The aim will be to reuse the style, header and footer but to replace the content and page title with new content. Create a second file in the same directory, newpage.crouton, with the following content:
<html crouton:based-on="virtual('template.html')">
<head>
<title crouton:replace="title">A croutons page</title>
</head>
<body>
<div crouton:replace="#Content">
<h1>This is the main content area, but this time with croutons.</h1>
<p>
The "crouton:replace" attribute above means the contents of the div
with id "Content" in the original template will be replaced by
whatever's put here.
</p>
</div>
</body>
</html>
If you now view newpage.crouton in a web browser, you should
see that the header, footer and style have been pulled from the original
template page, but the page title and content area have been replaced. Here's a tag-by-tag explanation of what's going on:
<html crouton:based-on="virtual('template.crouton')">The "crouton:based-on" attribute instructs the crouton parser to fetch the content from the file 'template.crouton' and use this as the basis for the page being rendered. The contents of the crouton:based-on attribute may be any valid python expression, and the functions path,
virtual and url have been provided to fetch content from any file path, virtual path (ie relative to the website's documentroot) and url respectively.
<title crouton:replace="title">A croutons page</title>The "crouton:replace" attribute must always contain a CSS2 selector expression. This attribute instructs the crouton parser to locate the element from the template page identified by the CSS selector expression, with the contents of the element in the element. In this case the upshot is that the content of the <title> tag is replaced by "A croutons page".
expr is parsed and evaluated as a python expression. The resulting value (which must be either a Crouton object or a BeautifulSoup object, see the API documentation for details) will be used to replace the element containing the crouton:based-on attribute in its entirety.
<!-- Base page on another HTML page, referenced by file path. --> <html crouton:based-on="path('/home/oliver/www/index.html')"> <html crouton:based-on="path('../index.html')"> <!-- Base page on another HTML page, referenced by virtual paths. --> <html crouton:based-on="virtual('/index.html')"> <!-- Base page on another HTML page, referenced by url. --> <html crouton:based-on="url('http://www.example.org/index.html')"> <!-- Base part of a page on another --> <html crouton:based-on="virtual('/index.html')"> <div crouton:replace="#photo"> <img crouton:based-on="virtual('/products/sprocket.html').select('table#productinfo img')"/> ></div> </html>
Although expr may be any valid python expression, three functions are exposed that allow content to be loaded from a variety of sources:
path('filesystem_path')The path function loads content from any filesystem path. This may be specified relatively or absolutely, and is not restricted to being within the document root.
virtual('virtual_path')The virtual function loads content from any virtual path (a path relative to the website's document root). This may be specified relatively or absolutely, and is restricted to being within the document root.
url('url')The url function loads content from any valid and accessible URL.
The result of the path, virtual or url functions will be python object with a select method that may be used to limit the portion of the returned document by specifying a CSS-2 selector, for example:
crouton:based-on="virtual('/products/sprocket.html').select('table#productinfo img')".
Any tag containing a crouton:based-on attribute may also contain a crouton:rewrite-links attribute. This attribute, if present, instructs the crouton parser to search for links within the based-on code (images, linked stylesheets, anchors etc) and rewrite the targets of those links as absolute URLs. The links will then work if the crouton page is in a different directory or server from the original.
<!--
Base this page on http://www.example.org/index.html, rewriting links.
For example, a link to "./photo.jpg" in the original page would become
"http://www.example.org/photo.jpg" when used in this page.
-->
<html crouton:based-on="url('http://www.example.org/index.html')" crouton:rewrite-links="">
crouton:replace is an alias for crouton:replace-inner.
expr must be a CSS-2 selector expression, which will select elements in the based-on document. The first matching element found will have its contents replaced with the contents of the element containing the crouton:replace attribute. Unlike replace-outer, only the contents of the selected tag is replaced.
Given a document soup.html, with the following content:
<html>
<body>
<h1>Soup menu</h1>
<ul id="Flavours">
<li>Mushroom</li>
<li>Tomato</li>
<li>Minestrone</li>
</ul>
</body>
</html>
The following results may be had:
<html crouton:based-on="path('soup.html')">
<li crouton:replace="ul#Flavours li">Vegetable</li>
</html>
<html>
<body>
<h1>Soup menu</h1>
<ul id="Flavours">
<li>Vegetable</li>
<li>Tomato</li>
<li>Minestrone</li>
</ul>
</body>
</html>
<html crouton:based-on="path('soup.html')">
<li crouton:replace="ul#Flavours li+li">Cream of asparagus</li>
</html>
<html>
<body>
<h1>Soup menu</h1>
<ul id="Flavours">
<li>Mushroom</li>
<li>Cream of asparagus</li>
<li>Minestrone</li>
</ul>
</body>
</html>
expr must be a CSS-2 selector expression, which will select elements in the based-on document. The first matching element found will be replaced by the element containing the crouton:replace-outerattribute. Unlike replace-inner, the replacement includes the containing tags.
For example
<html crouton:based-on="path('soup.html')">
<li crouton:replace-outer="ul#Flavours li">Vegetable</li>
</html>
<html>
<body>
<h1>Soup menu</h1>
<ul id="Flavours">
<li>Vegetable</li> <!-- note that entire <li> is replaced – not just the contents -->
<li>Tomato</li>
<li>Minestrone</li>
</ul>
</body>
</html>
expr must be a CSS-2 selector expression, which will select elements in the based-on document. The first matching element found will have the contents of the element containing the crouton:append attribute appended to its contents.
Using the soup menu example above:
<html crouton:based-on="path('soup.html')">
<ul crouton:append="ul#Flavours"><li>Broccoli and stilton</li></ul>
</html>
<html>
<body>
<h1>Soup menu</h1>
<ul id="Flavours">
<li>Mushroom</li>
<li>Tomato</li>
<li>Minestrone</li>
<li>Broccoli and stilton</li>
</ul>
</body>
</html>
expr must be CSS-2 selector expression, which will select elements in the based-on document. The first matching element found will have the contents of the element containing the crouton:replace attribute prepended.
Using the soup menu example above:
<html crouton:based-on="path('soup.html')">
<ul crouton:prepend="ul#Flavours"><li>Broccoli and stilton</li></ul>
</html>
<html>
<body>
<h1>Soup menu</h1>
<ul id="Flavours">
<li>Broccoli and stilton</li>
<li>Mushroom</li>
<li>Tomato</li>
<li>Minestrone</li>
</ul>
</body>
</html>
expr must be a CSS-2 selector expression, which will select elements in the based-on document. The element containing the crouton:insert-before attribute will be inserted, in its entirety, directly before the first element that matches expr.
For example:
<html crouton:based-on="path('soup.html')">
<img crouton:insert-before="ul#Flavours" src="soup.png" />
</html>
<html>
<body>
<h1>Soup menu</h1>
<img src="soup.png" />
<ul id="Flavours">
<li>Mushroom</li>
<li>Tomato</li>
<li>Minestrone</li>
</ul>
</body>
</html>
expr must be a CSS-2 selector expression, which will select elements in the based-on document. The element containing the crouton:insert-before attribute will be inserted, in its entirety, directly after the first element that matches expr.
For example:
<html crouton:based-on="path('soup.html')">
<img crouton:insert-after="ul#Flavours" src="soup.png" />
</html>
<html>
<body>
<h1>Soup menu</h1>
<ul id="Flavours">
<li>Mushroom</li>
<li>Tomato</li>
<li>Minestrone</li>
</ul>
<img src="soup.png" />
</body>
</html>
expr must be a CSS-2 selector expression, which will select elements in the based-on document. The first matching element found will be removed from the based-on document. Nothing will be inserted.
Using the soup menu example above:
<html crouton:based-on="path('soup.html')">
<span crouton:remove="ul#Flavours li"/>
</html>
<html>
<body>
<h1>Soup menu</h1>
<ul id="Flavours">
<li>Mushroom</li>
<li>Tomato</li>
<li>Minestrone</li>
</ul>
</body>
</html>
Work is still ongoing to support the full CSS-2 selector syntax as described in the CSS2 specification.
If you find croutons useful or have a question about it, please email the author, oliver@thelettero.co.uk.