The example CGI program uses 2 template files. See the template processor page for the details on template processor and template statements.

index.tpl, loaded directly from the program:

<(if $main)>
	<(include $main)>
<(else)>
	<(include index.tpl)>
<(fi)>

<(print a=, $a, b=, $b)>

<(if $a!=$b)>
	a != b
<(else)>
	a == b
<(fi)>

test.tpl, loaded from index.tpl using include statement:

<form enctype="multipart/form-data" method="POST">
	<input type="file" name="file">
	<input type="hidden" name="hidden" value="post">
	<input type="text" name="text" value="text">
	<input type="submit" value="push file">
</form>
<form method="POST">
	<input type="hidden" name="hidden" value="post">
	<input type="text" name="text" value="text">
	<input type="submit" value="push">
</form>

<pre>
	<(* test template *)>

	<(assign a = 1)>
	<(assign b = 2)>

	<(print test=, $test)>

	<(> <( not )> parsed area <)>

	<(* simple 'if' statement *)>

	<(if $test!=test)>
		$test != 'test'
	<(else)>
		$test == 'test'
	<(fi)>

	<(* nested 'if' statements *)>

	<(if $test)>
		$test
		<(if $test==test)>
			$test == 'test'
		<(fi)>
		<(if $test!=test)>
			$test != 'test'
		<(fi)>

		<(if $test)>
			test
			<(if $test==test)>
				$test == 'test'
			<(else)>
				$test != 'test'
			<(fi)>
		<(else)>
			<(if $test!=test)>
				$test != 'test'
			<(fi)>
			<(if false)>
				WTF?
			<(fi)>
		<(fi)>

		<(if 3>2)>
			3>2
		<(fi)>

		<(if 3<2)>
			WTF?
		<(fi)>

		<(if!false)>!false<(fi)>
	<(fi)>

	<(* simple 'for' statement *)>

	<(for $i in $strarr)>
		<(for $j in $i)><(print $j)><(rof)>
	<(rof)>

	<(for $b in $strmaparr)>
		<(for $c in $b)>
			|<(print $c)>|<(print $b.one)>
		<(rof)>
	<(rof)>

	<(for $c in $strmaparr.1)>
		|<(print $c)>
	<(rof)>

	<(* namespace *)>

	<(for $i in $strmaparr)>
		<(for $i in $i)> <(* HERE : '$i in $i' is OK because of the namespace scope *)>
			|<(print $i)>
			<(print $dodo.iterator)>
		<(rof)>
		|<(print $i.one)>
		<(print $dodo.iterator)>
	<(rof)>

	<(assign a = test1)>

	<(if true)>
		<(assign a = test2)>

		<(for $i in $strmaparr)>
			<(for $i => $j in $i)>
				<(if $i == three)>
					<(break 3)>
				<(fi)>
				<(print $i)> - <(print $j)>

				<(assign a = test3)>
				<(print a= ', $a,' (must be equal to 'test3'))>
			<(rof)>
			-------
		<(rof)>
		<(print a= ', $a,' (must be equal to 'test2'))>
	<(fi)>
	<(print a= ', $a,' (must be equal to 'test1'))>

	<(ns)>
			<(ns)>
				<(assign a = in namespace)>
				<(print a= ', $a,' (must be equal to 'in namespace'))>
			<(sn)>
		<(print a= ', $a,' (must be equal to 'test1'))>
	<(sn)>

	<(* print statement and variable resolution *)>

	<(print string literal)>
	<(print {$strmaparr.{0}.{$one}})>
	<(print $strmaparr.1.{$strmap.{$one}})>
	<(print $strarr.0.0)>
	<(print $strmap.one)>
	<(print $strmaparr.0.one)>
	<(print $strmaparr.1.one)>
	<(print $strmaparr.1.{$strmap.{$one}}, " -- ", {$strmaparr.{0}.{$one}})>
	<(print $dodo.version, " : ", $dodo)>
</pre>

The main CGI program looks like:

#include <libdodo/dodo.h>

using namespace dodo;
using namespace data::tpl;
using cgi::exchange;

void
handler(exchange &ex)
{
        using namespace cgi;

        // First type: pass headers and force constructor print them immediately
        // In this case you will not be able to pass cookies(though you can embed them in headers)
        //      dodoStringMap head;
        //      head[cgi::RESPONSE_HEADER_CONTENTTYPE] = "text/html";
        //      dialogue d(&ex, head, false);
        //

        // Second type: use default headers and do not print them immediately
        dialogue d(ex, true);

        dodoString user = d.authenticationResponse().user;

        if (d.GET["status"] == "forbidden") {
                d.setResponseStatus(cgi::STATUS_CODE_FORBIDDEN);

                d.printString("FORBIDDEN");

                return ;
        } else if (d.GET["status"] == "notfound") {
                d.setResponseStatus(cgi::STATUS_CODE_NOTFOUND);

                d.printString("NOT FOUND");

                return ;
        }
        /**
         * A workaround for apache web server to get auth headers:
         *
         * RewriteEngine on
         * RewriteBase /
         * RewriteCond %{HTTP:Authorization}  ^(.*)
         * RewriteRule ^(.*)$ $1 [e=HTTP_AUTHORIZATION:%1
         */
        else if (d.GET["status"] == "basic_auth") {
                if (user.size() == 0 || !d.isAuthenticated("libdodo", "password")) {
                        d.requestAuthentication("libdodo", cgi::AUTH_BASIC);

                        return ;
                }
        } else if (d.GET["status"] == "digest_auth") {
                if (user.size() == 0 || !d.isAuthenticated("libdodo", "password")) {
                        d.requestAuthentication("libdodo", cgi::AUTH_DIGEST);

                        return ;
                }
        }

        d.HEADERS[cgi::RESPONSE_HEADER_CONTENTTYPE] = "text/html";

        d.setCookie(cookie("test", "cookie"));

        exchange *io = d;
        io->writeString("The headers should have been already printed.<br>");

        d.printString("User: " + user + "<br>");
        d.printString("GET[\"argument\"]: " + d.GET["argument"] + "<br>");
        d.printString("POST[\"hidden\"]: " + d.POST["hidden"] + "<br>");
        d.printString("POST[\"text\"]: " + d.POST["text"] + "<br>");
        d.printString("ENVIRONMENT[CGI_ENVIRONMENT_QUERYSTRING]: " + d.ENVIRONMENT[cgi::ENVIRONMENT_QUERYSTRING] + "<br>");
        d.printString("COOKIES[\"test\"]: " + d.COOKIES["test"] + "<br>");
        d.printString("FILES[\"file\"].size: " + tools::string::iToString(d.FILES["file"].size) + "<br>");
        d.printString("FILES[\"file\"].mime: " + d.FILES["file"].mime + "<br>");
        d.printString("charset: " + d.charset() + "<br>");
        d.printString("tpl::processor:<br>");

        try {
                processor p;
                
                // Define template variables
                p.assign("main", "test.tpl");

                p.assign("test", "test");
                p.assign("show", "show");
                p.assign("one", "one");

                dodoStringArray strarr;
                strarr.push_back("one");
                strarr.push_back("two");
                strarr.push_back("three");
                p.assign("strarr", strarr);

                dodoStringMap strmap;
                strmap["one"] = "one";
                strmap["two"] = "two";
                strmap["three"] = "three";
                p.assign("strmap", strmap);

                dodoArray<dodoStringMap> strmaparr;
                strmaparr.push_back(strmap);
                strmap["one"] = "three";
                strmaparr.push_back(strmap);
                p.assign("strmaparr", strmaparr);

                // Process the file
                p.processFile("index.tpl", *io);
        } catch (dodo::exception::basic ex) {
                d.printString((dodoString)ex + " " + tools::string::lToString(ex.line) + " " + ex.file + " " + ex.message );
        }
}

int main(int argc, char **argv)
{
        using namespace cgi::basic;

        // Create CGI server instance
        server s;
        // Define request handler and start to listen for request
        s.serve(&handler);

        return 0;
}

For Fast-CGI everything remains the same except *main* function. In the case of Fast-CGI it looks like:

int main(int argc, char **argv)
{

        try {
                using namespace cgi::fast;
                
                // Create CGI server instance 
                // Server will process 5 requests and 
                // Separate threads for processing each request will not be used
                server c(5, false);
                if (!c.isFastCgi()) {
                        cout << "Not a fastCGI.";
                        return 1;
                }
                
                // Define request handler and start to listen for request
                c.serve(&handler);
        } catch (dodo::exception::basic &ex) {
                cout << (dodoString)ex << "\t" << ex.line << "\t" << ex.file << endl;
        }

        return 0;
}