EFX Content Management System

EFX is a Content Management System (CMS) written in Tcl. I wrote it because I needed something to help my son's school maintain their events, news, newsletters pictures (a process in which they previously employed Frontpage).

I took a look at Bricolage, and while it is really cool (plus it doesn't require you to use any kind of app server or apache plugin -- it produces plain old HTML pages), I found its use of Mason cumbersome and I wasn't ready to brush up on my Perl. Plus, its a really big system that is difficult to install and master. (My target was a fairly moderate site of about 30-40 pages plus 50 or so pictures.)

I am still working on the school website (and EFX), but I found EFX already quite useful.

EFX (so far) consists of

  • EFX engine - A template engine based on ideas behind Zope Page Templates (which I had the pleasure of helping to define back in 2000) where markup is embedded in XML (XHTML) attributes.
  • Navmenu - A Navigational tool that helps you build hierarchical navigational menus and map out your site.
  • Story - A simple news content management tool that allows users to just put text files (with a title and date line) in a directory for inclusion into the website.
  • Calendar - An event management tool that presents a calendar UI interface to an events page.

The EFX engine is about 450 lines of Tcl that uses TclXML to parse and operate on XML documents to produce XHTML pages. It is the most interesting part of the system. Here are just a few of the commands it exposes inside of XML element attributes:

  • efx:eval="command" - Evaluates command. Eats element and children. No output.
  • efx:eval_r="command" - Evaluates command. Replaces element with result of the command.
  • efx:subst="" - Substitutes commands/variables in children. Outputs current node and children.
  • efx:onlyIf="expression" - Evaluates expression (with expr). If true, outputs element and children. If false, eat element and children.
  • efx:foreach="var|command" - Iterates var over result of command, processing children (which may contain additional efx commands) until var is {}.
  • efx:processFile="input_file args" - Process additional XML files.

Commands can be any Tcl expression. All commands and variables are evaluated within a common Tcl namespace.

Each XML file is treated as a closure. Variable bindings introduced in each page is in effect until that page is finished (then any old bindings are restored). You can use efx:processFile to nest closures.

Okay, enough of that, how about an example?

Here is a simple blog that I made in about 20 minutes using the EFX toolkit: http://www.maplefish.com/todd/blog

I intend on improving the blog for doing RSS feeds and a few more features, but since it is a static site, no feedback/comment support is planned.

The source XML that generated all of the blog pages:

   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   <html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
     <head>
       <div efx:eval="source blog_settings.tcl"/>
       <div efx:eval="set BASEURL ."/>
       <div efx:eval="package require story"/>
       <div efx:eval="package require navmenu"/>
       <div efx:eval="navmenu::parse_file navmenu.map"/>
       <div efx:eval="set LinkMenu [navmenu::get Links]"/>
    
       <div efx:markBlock="MetaStuff">
        <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
        <meta efx:subst="" name="keywords" content="$BLOG_KEYWORDS"/>
        <meta name="generator" content="EFX"/>
        <style efx:subst="" type="text/css">@import url("$BASEURL/css/$BLOG_STYLESHEET");</style>
       </div>
       <title efx:subst="">$BLOG_NAME</title>
    
       <div efx:eval="set StoryDir ./entries"/>
     </head>
   <body>
     <div id="Header">
       <div efx:markBlock="BlogHeader">
         <div efx:subst="">$BLOG_HEADER</div>
       </div>
     </div>
   
     <div id="Content">
       <div efx:foreach="s|story::stories $StoryDir -max 10">
         <h4 efx:eval_r="story::title_as_ref $s"/>
         <small efx:eval_r="clock format [story::date $s] -format {%A, %B %d }"/><br />
         <p efx:eval_r="story::body $s"/>
         <hr efx:onlyIf="[llength $s]"/>
       </div>
     </div>

    <div id="Menu">
      <h4>Recent Entries</h4>
      <ul efx:foreach="s|story::stories $StoryDir -max 10">
         <li efx:eval_r="story::title_as_local_link $s">Title</li>
      </ul>
      <hr />
      <h4>Archives</h4>
      <div efx:eval="set MONTH_MENU [navmenu::make]"/>
      <!-- We make two passes, the first builds the menu of months;
       the second renders the month (which will need the menu of months
       for linking) 
       -->
      <div efx:foreach="pass| list make_menu render_month">
        <div efx:eval="set lns [story::stories $StoryDir]"/>
        <div efx:eval="set months [story::months $lns]"/>
        <div efx:foreach="month| set months">
           <div efx:eval="setl {MONTH_STORIES lns} [story::next_by_month $lns]"/>
           <div efx:eval="set THIS_MONTH $month"/>
           <div efx:onlyIf="$pass == {make_menu}">
             <div efx:eval="set MONTH_MENU [navmenu::add $MONTH_MENU $THIS_MONTH $THIS_MONTH.html]"/>
           </div>
           <div efx:onlyIf="$pass == {render_month}">
             <div efx:processFile="./month.xmlt ./$THIS_MONTH.html"/>
           </div>
        </div>
     </div>
     <div efx:eval_r="navmenu::render $MONTH_MENU {}"/>
     <hr />
     <h4>Links</h4>
     <div efx:eval_r="navmenu::render $LinkMenu $HERE"/>
    </div>
   </body>
   </html>

The blog_settings.tcl file just contains a few set commands to introduce BLOG_XXX variables:

   set BLOG_NAME {EFX Development}
   set BLOG_KEYWORDS {Todd Coram, EFX, templates}
   set BLOG_STYLESHEET {simple.css}
   set BLOG_HEADER {<h1> Todd's EFX Development Log </h1>}

Navmenu.map looks like:

   navmenu::menu Links {
        {EFX Development Log} index.html {}
        BREAK - -
        {Maplefish Home} http://www.maplefish.com {}
        {Almost Free Text} http://www.maplefish.com/todd/aft.html {}
   }

I'm hoping to do a code release by next week. If you are interested in just looking at a raw code drop, check out http://www.maplefish.com/todd/efx/efx.tar.gz (not found as of 07 Jun 2007). The blog sources are at http://www.maplefish.com/todd/efx/blog.tar.gz

Comments are most welcome! -- Todd Coram


jcw - Interesting project! I'm wondering why you are using XML as input format. Since only the output gets used, presumably, have you considered using Tcl as language to specify the whole website in? There are tools such as xmlgen / htmlgen which make it easy to generate properly formatted output. For a truly wicked digression from "normal" Tcl notation, see the end of [L1 ], where I added an example that illustrates mixing structure and program flow. Anyway, it's good to see more web-oriented software happening in Tcl, IMO - the web seems to be such a natural match for EIAS (everything is a string).

Todd Coram - The input format is really XHTML, so you can actually edit it with an HTML editor (at least one that respects attributes and namespaces). Also, because it is XML, I get validation (sort-of-syntax checking). One thing that I didn't show in my example is that in many cases you can actually put "dummy" values in between the efx'd elements so that your XHTML editor (dreamweaver, etc) will see actual content (which is then replaced by commands such as efx:eval_r). I shamelessly stole this idea from Zope Templates (which I worked on, so I guess it isn't exactly stealing ;-) Here is an example:

    <p efx:eval_r="set tcl_version"> Dummy text that will show up in an HTML editor, 
          but get replaced with results from "set tcl_version" </p>

will yield (when run through EFX to produce the final HTML):

    <p>8.4</p>

Todd Coram - Regarding EIAS and the Web... strangely enough, I chose to utilize Tcl in a rather FP/Lisp manner. EFX is very list oriented. Very lispy -- and I considered using Lisp to build this, but if I were to have used Lisp I would be missing out on the utility of Tcl!

jcw - Ah, DreamWeaver, etc. Ok, so these pages get created in a markup system, with logic tucked in. Bingo, now I understand.

RLH 2007-02-18: So do you want or need any help on this project?


male 2007-06-08 - Is there a current version somewhere to be used? On your, Todds, page I didn't find your EFX! RLH I emailed him and he said when he got the time he would tarball it up.

tac 2007-06-23 - I've put the source tarball and blog example up again: (http://www.maplefish.com/todd/efx/efx.tar.gz ) and (http://www.maplefish.com/todd/efx/blog.tar.gz ). It's very raw. I haven't touched the blog (or blog code) in a year, but the efx source is recent -- it is a sanitized snapshot of stuff running a small school's website. I removed a bunch of unstable stuff (like the pop3 email retriever used to provide content for EFX to chew on) and the actual bash/Tcl code that starts the process running. It is what it is. I wish I had free time to work on it :-(