Using the groundwork class

The groundwork class provides methods for processing HTML templates. A template is simply an HTML file with a few extra markups. Following the philosophy that anything complex should be done in your CGI or Apache module using C++, Groundwork templates are fairly simple.

Hello World

Lets say we set up the following "hello world" application, consisting of a single module "hi" containing 1 cgi "helloworld.cgi" and 2 skins: "graphics" and "textonly".

The simplest implementation of helloworld.cgi creates an instance of groundwork and calls the execute() method.

The constructur determines which skin to use from the path info of the URL. The execute() method performs a security check, sends an HTTP header and calls parseTemplate(), passing it the corresponding HTML template file name. parseTemplate() calls parseSegment() and passes it the contents of the file. parseSegment() writes the contents of the file to the browser.

Directory Structure:
  • /usr/local/apache/htdocs
    • helloworld.gif
    • helloworld
      • textonly
        • hi
          • helloworld.html
      • graphics
        • hi
          • helloworld.html
      • default -> graphics
      • modules
        • hi
          • helloworld.cgi
textonly/helloworld.html
<html>
<body>
Hello world
</body>
</html>
graphics/helloworld.html
<html>
<body>
<img src="/helloworld.gif">
</body>
</html>
helloworld.cgi
#include <groundwork/groundwork.h>
#include <groundwork/cgimodule.h>

MAIN {
        groundwork      g(APISTRUCT);
        g.execute();
}

In this example:

Substitution Variables

Substitution variables can be used wherever you need the CGI or Apache module to fill in a discrete piece of information such as a field from a database or the result of a calculation. Substitution variables use the following syntax: $(variable)

The execute() method calls parseTemplate() which in turn calls parseSegment(). While processing the template, if parseSegment() finds a substitution variable, it looks for a value to replace it with.

User-Defined Variables

First, parseSegment() checks the list of user-defined variable/value pairs set during the call. See the next section for more information on using those methods.

Built-In Variables

Next, parseSegment() checks several built-in variables that make it easier to keep from hardcoding file names and local URL's in your templates.

In our helloworld application, when helloworld.cgi runs, these variables have the following values independent of which skin is selected:

$(applicationFileDir)=/usr/local/apache/htdocs/helloworld
$(applicationUrlDir)=/helloworld
$(application)=helloworld
$(moduleFileDir)=/usr/local/apache/htdocs/helloworld/modules/hi
$(moduleUrlDir)=/helloworld/modules/hi
$(module)=hi
$(programFileName)=helloworld.cgi
$(program)=helloworld
$(templateFileName)=helloworld.html

The following variables depend on which skin is selected.

URL: /helloworld/modules/hi/helloworld.cgi/textonly
skin: textonly
$(skin)=textonly
$(templateFileDir)=/usr/local/apache/htdocs/helloworld/textonly/hi
$(templateUrlDir)=/helloworld/textonly/hi
URL: /helloworld/modules/hi/helloworld.cgi/graphics
skin: graphics

$(skin)=graphics
$(templateFileDir)=/usr/local/apache/htdocs/helloworld/graphics/hi
$(templateUrlDir)=/helloworld/graphics/hi
URL: /helloworld/modules/hi/helloworld.cgi
(without passing in any path info)
skin: default

$(skin)=default
$(templateFileDir)=/usr/local/apache/htdocs/helloworld/default/hi
$(templateUrlDir)=/helloworld/default/hi

Two more built-in variables can be used to pass along whatever form variables were passed in to the CGI.

$(formEntriesAsGetString)
$(formEntriesAsHiddenVariables)

For example: page1.html passes values to page2.cgi which processes page2.html.

page1.html
<html>
<body>
<form name="page2.cgi" action="post">
<input type="hidden" name="firstname" value="David">
<input type="hidden" name="lastname" value="Muse">
<input type="hidden" name="city" value="Atlanta">
<input type="hidden" name="state" value="Georgia">
<input type="submit">
</form>
</body>
</html>
page2.html
<html>
<body>
<a href="page3.cgi?$(formEntriesAsGetString)">page3</a><br><br>
<form action="page3.cgi" action="post">
$(formEntriesAsHiddenVariables)
</form>
</body>
</html>
page2.cgi
#include <groundwork/groundwork.h>
#include <groundwork/cgimodule.h>

MAIN {
        groundwork      g(APISTRUCT);
        g.execute();
}
Output of page2.cgi
<html>
<body>
<a href="page3.cgi?firstname=David&lastname=Muse&city=Atlanta&state=Georgia&submit=Submit+Query">page3</a><br><br>
<form action="page3.cgi" action="post">
<input type="hidden" name="firstname" value="David">
<input type="hidden" name="lastname" value="Muse">
<input type="hidden" name="city" value="Atlanta">
<input type="hidden" name="state" value="Georgia">
<input type="hidden" name="submit" value="Submit+Query">
</form>
</body>
</html>


Form Variables

Next, parseSegment() will look for a match in the list of form variables passed in from the previous page.

For example: page1.html passes values to page2.cgi which processes page2.html.

page1.html
<html>
<body>
<form name="page2.cgi" action="post">
<input type="hidden" name="firstname" value="David">
<input type="hidden" name="lastname" value="Muse">
<input type="hidden" name="city" value="Atlanta">
<input type="hidden" name="state" value="Georgia">
<input type="submit">
</form>
</body>
</html>
page2.html
<html>
<body>
Name: $(firstname) $(lastname)<br>
City: $(city)<br>
State: $(state)
</body>
</html>
page2.cgi
#include <groundwork/groundwork.h>
#include <groundwork/cgimodule.h>

MAIN {
        groundwork      g(APISTRUCT);
        g.execute();
}
Output of page2.cgi
<html>
<body>
Name: David Muse<br>
City: Atlanta<br>
State: Georgia
</body>
</html>


Environment Variables

Then, parseSegment() checks the environment variables set by the web server. Here is a list of common environment variables and values:

SERVER_PORT=80
GATEWAY_INTERFACE=CGI/1.1
PWD=/usr/local/web
HTTP_USER_AGENT=Mozilla/5.0 Galeon/1.2.0 (X11; Linux i686; U;) Gecko/20020308
REMOTE_PORT=1354
HTTP_HOST=localhost.firstworks.com
HTTP_CONNECTION=keep-alive
HTTP_ACCEPT_ENCODING=gzip, deflate, compress;q=0.9
SERVER_PROTOCOL=HTTP/1.1
SERVER_ADMIN=[no address given]
SCRIPT_FILENAME=/usr/local/web/env.cgi
SERVER_ADDR=127.0.0.1
HTTP_KEEP_ALIVE=300
SCRIPT_NAME=/env.cgi
REMOTE_ADDR=127.0.0.1
QUERY_STRING=
SERVER_NAME=localhost.localdomain
SERVER_SIGNATURE=
SHLVL=1
HTTP_ACCEPT=text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,text/css,*/*;q=0.1
REQUEST_URI=/env.cgi
DOCUMENT_ROOT=/usr/local/web
HTTP_ACCEPT_LANGUAGE=en
REQUEST_METHOD=GET
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin
HTTP_ACCEPT_CHARSET=ISO-8859-1, utf-8;q=0.66, *;q=0.66
SERVER_SOFTWARE=Apache/1.3.20 (Unix)  (Red-Hat/Linux) PHP/4.1.2
_=/usr/bin/env
Skin Variables

Finally, parseSegment() checks the skin variables.

If after all these checks, parseSegment() finds no matches, it displays the variable as-is.

Segment Grouping Directives

Sometimes you only want part of a page to be displayed under a certain set of circumstances. If you put segment grouping directives around that part of the page, the CGI or Apache module can be coded to decide whether or not to display it. Segment grouping directives use the syntax <!-- start name --> ... <!-- end name -->

For example:

<html>
<body>

<p>And the results of our drawing:</p>

<!-- start win --><p>You are and instant winner!!!</p><!-- end win -->
<!-- start lose --><p>You lose!!!</p><!-- end lose -->

</body>
</html>

Segment grouping directives are also useful if you want part of a page to be diplayed over and over such as a table row or a list element. You can enclose that chunk of code in a set of segment grouping directives and write your program to iterate over it. You can use substitution variables inside of segment grouping directives.

For example:

<html>
<body>

<p>Phone list:</p>

<table>
<tr><td>Name</td><td>Address</td><td>Phone Number</td></tr>
<!-- start phonelistentry -->
<tr><td>$(name)</td><td>$(address)</td><td>$(phone)</td></tr>
<!-- end phonelistentry -->
</table>

</body>
</html>

Segment grouping directives can be nested to any level. For example, if you wanted to list all the phone numbers that a given individual has, you could do it like this.

<html>
<body>

<p>Phone list:</p>

<table>
<tr><td>Name</td><td>Address</td><td>Phone Number</td></tr>
<!-- start phonelistentry -->
<tr><td>$(name)</td><td>$(address)</td><td><!-- start phone -->$(phone)<br><!-- end phone --></td></tr>
<!-- end phonelistentry -->
</table>

</body>
</html>

Of course, in each case, you have to write your program to respond to these segment groupings. Fortunately, groundwork makes that easy to do.

execute() calls parseTemplate() which in turn calls parseSegment(). parseSegment() calls handleSegment() whenever it runs into a segment grouping tag. By default, handleSegment() just writes out the segment. Your program can override the default handleSegment() method to perform whatever action you like when it's called for a particular segment.

handleSegment() takes 3 arguments: the current output stream, the name of the segment and the segment text itself. The name of the segment is extracted from the segment grouping tag, the text of the segment is all of the HTML between the start and end tags.

Consider the following HTML template which combines our previous examples:

<html>
<body>

<p>And the results of our drawing:</p>

<!-- start win --><p>You are and instant winner!!!</p><!-- end win -->
<!-- start lose --><p>You lose!!!</p><!-- end lose -->

<p>Phone list:</p>

<table>
<tr><td>Name</td><td>Address</td><td>Phone Number</td></tr>
<!-- start phonelistentry -->
<tr><td>$(name)</td><td>$(address)</td><td>$(phone)</td></tr>
<!-- end phonelistentry -->
</table>

</body>
</html>

In this example, handleSegment() will get called 3 times; once for each segment:

To process this page, you could write a program like the following:

#include <groundwork/groundwork.h>
#include <groundwork/cgimodule.h>
#include <string.h>

class example : public groundwork {
        public:
                        example(void *apistruct);
                int     handleSegment(strstream *container,
                                        char *name, char *segment);
};

example::example(void *apistruct) : groundwork(apistruct) {}

int example::handleSegment(strstream *container,
                                char *name, char *segment) {

        if (!strcmp(name,"win")) {

                ... look up the user in the database, see if he wins ...

                // write out the segment if the user is a winner
                if (userwins) {
                        return parseSegment(container,segment,NULL,NULL);
                } else {
                        return 1;
                }

        } else if (!strcmp(name,"lose")) {

                ... look up the user in the database, see if he loses ...

                // write out the segment if the user loses
                if (userloses) {
                        return parseSegment(container,segment,NULL,NULL);
                } else {
                        return 1;
                }

        } else if (!strcmp(name,"phonelist")) {

                ... look up the phone list in the database ...

                // write out the segment for each row in the result set
                for (int i=0; i<rows; i++) {
                        parseSegment(container,segment,
                                        columns,row[i],
                                        NULL);
                }

                // return 1 for success
                return 1;
        }

        // return 0 for failure
        return 0;
}

MAIN {
        example e(APISTRUCT);
        e.execute();
}

In this example, handleSegment() figures out which segment it's handling, performs some kind of database lookup and calls parseSegment() to write the segment out. For the "win" and "lose" segments, the segment is only written if a condition is met. For the "phonelist" segment, the segment is written out for each entry in the list.

parseSegment() is used to write the segments to the browser. parseSegment() accepts lists of variable/value pairs that are used as substitution variables in addition to the built-in variables, environment variables and skin variables that parseSegment() substitutes by default. See the groundwork class documentation for more information about this method.

Include Directives

If you don't care about using a GUI HTML editing tool to develop your templates, you can cut out commonly used fragments (such as headers, footers and borders) and use the include tag to include them in other pages.

parseSegment() method automatically follows include directives by calling parseTemplate() on the file. You can even use substitution variables in the name of the file that you want to include. Note that the filename should be the full pathname to the file, not a relative pathname or URL. Path variables are commonly used with include directives.

For example:

<html>
<!-- include $(skinFilePath)/header.html -->

<p>Here's some random information...</p>

<!-- include $(skinFilePath)/footer.html -->
</html>
Overriding groundwork class Methods

If you need to override or extend the groundwork class, you can do so by creating your own class that inherits from groundwork and overrides certain methods. The most common methods to override are the handleSegment() method, the execute() method and the sendHeader() method from the cgi class. Since the groundwork class inherits from the cgi class, a class that inherits from the groundwork class can override cgi class methods too.

We've already seen an example that overrides the handleSegment() method.

The following example overrides the sendHeader() method of the cgi class to send a text/plain header instead of the standard text/html header.

#include <groundwork/groundwork.h>
#include <groundwork/cgimodule.h>

class example : public groundwork {
        public:
                        example(void *apistruct);
                void    sendHeader();
};

example::example(void *apistruct) : groundwork(apistruct) {}

void example::sendHeader() {
        
        // send a text/plain header
        outputHeader(apistruct,"Content-type","text/plain");
        outputHeader(apistruct,"\r\n");
}

MAIN {
        example e(APISTRUCT);
        e.execute();
}

This example overrides the execute() method so that it doesn't perform any of the security checks.

#include <groundwork/groundwork.h>
#include <groundwork/cgimodule.h>

class example : public groundwork {
        public:
                        example(void *apistruct);
                int     execute();
};

example::example(void *apistruct) : groundwork(apistruct) {}

int example::execute() {
        sendHeader();
        return parseTemplate(NULL,templateFileDir(),
                                        templateFileName(),
                                        NULL,NULL);
}

MAIN {
        example e(APISTRUCT);
        e.execute();
}