Location: PHPKode > scripts > Phptempt > phptempt/phptempt.php
<?php

// if the phptempt file has been included before then skip
// it (to advert fatal error message):
//echo "phptempt_INCDLUDED==$phptempt_INCDLUDED<br>"; // debug
if (isset($phptempt_INCDLUDED))
    return;
$phptempt_INCDLUDED = true;

// seed random number generator:
 srand ((double) microtime() * 1000000);



/********************************************************************
* GLOBAL DEFINITIONS OF FUNCTIONS AND CONSTANTS
********************************************************************/


function error ($msg)
{
    // print error message:
    echo "<b>Error</b>: $msg.<br>";
}


// return value of methods Txxx::getType()
define ("PLACEHOLDER", 1);
define ("BLOCK", 2);
define ("PURE", 3);
define ("CONDITIONAL", 4);
define ("MULTIBLOCK", 5);
define ("PHREFERENCE", 6);
define ("CHECKBLOCK", 7);
define ("BLREFERENCE", 8);

// set undefined placeholders to the following value; to test
// for an undefined placeholder value, use the function
// isundefined() below:
//
define ("UNDEFINED", false);
function isundefined ($val)
{
    if (!isset($val))
        return true;
    else if (is_bool($val) && $val==UNDEFINED)
        return true;
    else
        return false;
}

function array_size ($arr)
{
    // returns the size of an array indexed with numbers by
    // returning the biggest index value incremented by one:
    $keys = array_keys($arr);
    if (sizeof($keys)>0)
        return $keys[sizeof($keys)-1] + 1;
    else
        return 0;
}

function arr2str ($arr)
{
    if (!is_array($arr))
        fatal ("printArray: Argument must be an array");

    // convert the contents of an array $arr into a
    // readable string:
    //
    $s = "[";
    if (array_size($arr)>0)
        $s .= "'" . $arr[0] . "'";
    for ($i=1; $i<array_size($arr); $i++)
        $s .= ",'" . @$arr[$i] . "'";
    $s .= "]";

    return $s;
}

function elem2name ($elem)
{
    switch ($elem->getType()) {
    case BLOCK:
    case PLACEHOLDER:
        return $elem->getIdent() . "(id=" . $elem->name . ")";
    case PURE:
        return "anonymous";
    case BLREFERENCE:
        return "rec";
    default:
        return "unknown";
    }
}

function int2type ($elem)
{
    switch ($elem->getType()) {
	case PLACEHOLDER:
        return "TPlaceholder";
	case BLOCK:
        return "TBlock";
	case PURE:
        return "TPure";
	case CONDITIONAL:
        return "TCondit";
    case MULTIBLOCK:
        return "TMultiBlock";
    case PHREFERENCE:
        return "TPlaceholderRef";
	case CHECKBLOCK:
        return "TCheckBlock";
	case BLREFERENCE:
        return "TBlockRef";
    default:
        return "Unknown";
    }
}

function elem2str ($arr)
{
    if (!is_array($arr))
        fatal ("printArray: Argument must be an array");

    // convert the contents of an array $arr of block elements into a
    // list of element types:
    //
    $s = "[";
    if (array_size($arr)>0)
//        $s .= int2type($arr[0]);
        $s .= elem2name($arr[0]);
    for ($i=1; $i<array_size($arr); $i++)
//        $s .= ", " . int2type(@$arr[$i]);
        $s .= ", " . elem2name(@$arr[$i]);
    $s .= "]";

    return $s;
}

function mat2str ($matrix)
{
//    echo "matrix==" . arr2str($matrix) . "<br>"; // debug
    $s = "[";
    if (array_size($matrix)>0)
        $s .= " " . arr2str($matrix[0]) . " ";
    for ($i=1; $i<array_size($matrix); $i++)
        $s .= ", " . arr2str($matrix[$i]) . " ";
    $s .= "]";

    return $s;
}

function repeat ($val, $times)
{
    // repeat value $val $times times in an array:
    //
    $res = array();
    for ($i=0; $i<$times; $i++)
        $res[] = $val;

    return $res;
}



/********************************************************************
* CLASS TBasicBlock
********************************************************************/


class TBasicBlock
{
    // the list of elements that the block contains:
    var $elements;

    // will be true iff this block contains only nested blocks or pure
    // HTML code:
    var $isPure;


    function TBasicBlock ()
    {
        $this->elements = array();
        $this->isPure = true;
    }

    function getValArray ($idc)
    {
        // return array of values; since only placeholder instances
        // can contain values, (conditional) blocks always return
        // an empty array:
        return array();
    }

    function elementsOut ($indices)
    {
//        echo "elementsOut called for elements==" . elem2str($this->elements) . "<br>"; // debug
        // print block contents for current iteration as specified
        // in $indices:
        for ($i=0; $i<sizeof($this->elements); $i++) {
            $this->elements[$i]->out($indices);
        }
    }

    function add (&$element)
    {
        $this->elements[] = &$element;

        // note: the following statement assumes that the elements
        // of the block be added before the block itself; verify
        // that the phptempt parser will produce appropriate code!

        // check if the block also contains placeholders:
        $this->isPure = $this->isPure && ($element->getType()!=PLACEHOLDER);
//        echo "TBlock::add ... isPure==" . $this->isPure . "<br>"; // debug
    }

    function setName ($name)
    {
        $this->name = $name;
    }
}



/********************************************************************
* CLASS TCheck
********************************************************************/


class TCheck extends TBasicBlock
{
    // array of references to placeholders that will be checked to
    // determine if the block should be printed or not:
    var $ph;

    function TCheck ()
    {
        $this->TBasicBlock();
    }

    function & create () { return new TCheck(); }

    function & copy (&$ptempt, $superblock)
    {
        $cp =& $this->create ();

        // copy placeholders as listed in the $ph array:
        //
        for ($j=0; $j<sizeof($this->ph); $j++) {
            $cp->addPlaceholder ($this->ph[$j]->copy (&$ptempt, $superblock));
        }

        // copy elements contained in this block:
        //
        for ($i=0; $i<sizeof($this->elements); $i++) {
//            echo "i==$i, " . $this->elements[$i]->getType() . "<br>"; // debug
            $cp->add ($this->elements[$i]->copy (&$ptempt, $superblock));
        }

        return $cp;
    }

    function addPlaceholder (&$ph)
    {
        // add reference to the specified placeholder $ph to the
        // reference array:
        $this->ph[] = &$ph;
    }

    function out ($idc)
    {
        // print the block if at least one of the placeholders
        // associated with this block has been defined:
        //

        $cond = false;
//        echo "sizeof(\$this->ph)==" . sizeof($this->ph) ."<br>"; // debug
        for ($i=0; $i<sizeof($this->ph); $i++) {
//            $this->name .= "+"; // debug
//            echo "TCheck.out: value[$i]==" . $this->ph[$i]->getVal($idc) . $this->name . "<br>"; // debug
            if (!isundefined($this->ph[$i]->getVal($idc))) {
                $cond = true;
                break;
            }
        }

        if ($cond)
            $this->elementsOut ($idc);
    }

    function getType()
    {
        return CHECKBLOCK;
    }
}



/********************************************************************
* CLASS TCheckNeg
********************************************************************/


class TCheckNeg extends TCheck
{
    function TCheckNeg ()
    {
        $this->TCheck ();
    }

    function & create () { return new TCheckNeg(); }

    function out ($idc)
    {
        // print the block if none of the placeholders associated with this
        // block has been defined (negation of the condition for TCheck):
        //

        $cond = true;
        for ($i=0; $i<sizeof($this->ph); $i++) {
            if (!isundefined($this->ph[$i]->getVal($idc))) {
                $cond = false;
                break;
            }
        }

        if ($cond)
            $this->elementsOut ($idc);
    }

}



/********************************************************************
* CLASS TCondit
********************************************************************/


class TCondit extends TBasicBlock
{
    // array of references to the conditional identifiers that
    // control this conditional block:
    var $cids;

    function TCondit ()
    {
        $this->TBasicBlock ();
    }

    function setCids ($cids)
    {
        $this->cids = &$cids;
    }

    function & create () { return new TCondit(); }

    function & copy ($ptempt, $superblock)
    {
        $cp =& $this->create();

        // "flat" copy of conditional identifiers:
        $cp->setCids (&$this->cids);

        // copy elements of this block:
        //
        for ($i=0; $i<sizeof($this->elements); $i++) {
            $cp->add ($this->elements[$i]->copy (&$ptempt, $superblock));
        }

        return $cp;
    }

    function out ($idc)
    {
//        echo "TCondit::out called with idc==" . arr2str($idc) . "<br>"; // debug
        // check conditions that control this block; print block
        // contents only iff at least one of these conditions
        // hold:
        $cond = false;
        for ($i=0; $i<sizeof($this->cids); $i++) {
            if ($this->cids[$i]) {
                $cond = true;
                break;
            }
        }

        if ($cond)
            $this->elementsOut ($idc);
    }

    function getType ()
    {
        return CONDITIONAL;
    }
}



/********************************************************************
* CLASS TConditNeg
********************************************************************/


class TConditNeg extends TCondit
{
    function TConditNeg ()
    {
        $this->TCondit();
    }

    function create () { return new TConditNeg(); }

    function out ($idc)
    {
        // print contents of this block only iff none of the control conditions
        // has been set:
        $cond = true;
        for ($i=0; $i<sizeof($this->cids); $i++) {
            if ($this->cids[$i]) {
                $cond = false;
                break;
            }
        }

        if ($cond)
            $this->elementsOut($idc);
    }
}



/********************************************************************
* CLASS TMultiBlock
********************************************************************/


class TMultiBlock extends TBasicBlock
{
    var $cards;

    // current index (how many times has the "begin" method of the
    // phptempt class been called on an embedded block of the multi
    // block?):
//    var $index;

    function TMultiBlock ()
    {
        $this->TBasicBlock();

        $this->cards = new RecArray();
    }

    function resetNestedIndex()
    {
        // resets the indices of the nested alternative blocks to
        // 0:
        for ($i=0; $i<sizeof($this->elements); $i++)
            $this->elements[$i]->setIndex(0);
    }

    function & copy (&$ptempt, $superblock)
    {
        $cp = new TMultiBlock ();

        // copy blocks contained in this multi-block:
        //
        for ($i=0; $i<sizeof($this->elements); $i++) {
//            echo "i==$i, " . $this->elements[$i]->getType() . "<br>"; // debug
            $cp->add ($this->elements[$i]->copy (&$ptempt, $superblock));
        }

        return $cp;
    }

    function nestedIncIndex ()
    {
//        echo "nestedIncIndex called on '" . $this->name . "'<br>"; // debug
        // increment the nested blocks; these take part in an alternative
        // block construct:
        for ($i=0; $i<sizeof($this->elements); $i++)
            $this->elements[$i]->setIndex($this->elements[$i]->getIndex() + 1);
    }
/*
    function getIndex () { return $this->index; }
*/
    function add ($block)
    {
        // TMultiBlock instances may not contain placeholders or
        // pure block; they are used to model alternative blocks:
        if ($block->getType()!=BLOCK)
            fatal ("TMultiBlock may only contain instances of TBlock");

        TBasicBlock::add (&$block);
    }

    function incCard ($idc)
    {
//        echo "TMultiBlock::incCard called on '" . $this->name . "' with idc==" . arr2str($idc) . "<br>"; // debug
        // increment cardinality for the iteration specified by
        // indices $idc:
        $card = $this->cards->getVal ($idc);
        if (isundefined($card))
            $card = 0;
        $this->cards->addVal ($card+1, $idc);

//        echo "TMultiBlock::incCard(" . arr2str($idc) . ")==" . $this->cards->getVal($idc) . "<br>"; // debug
    }

    function getCard ($idc)
    {
        $card = $this->cards->getVal($idc);
//        echo "TBlock::getCard called on '" . $this->name . "' with idc==" . arr2str($idc) . ", card==$card<br>"; // debug
        if (isundefined($card))
            return 0;
        else
            return $card;
    }

/*
    function getCard ($idc)
    {
        // the cardinality of this multi block equals the maximum
        // cardinality of all the nested blocks for the specified
        // index $idc:
        //
//        echo "TMultiBlock::getCard called on '" . $this->name . "' with idc==" . arr2str($idc) . "<br>"; // debug
        if (sizeof($this->elements)>0) {
            $card = $this->elements[0]->getCard($idc);
//            echo "elements[0].getCard(" . arr2str($idc) . ")==$card<br>"; // debug

            for ($j=1; $j<sizeof($this->elements); $j++) {
                $indices = $idc; $indices[] = $j;
                $tCard =$this->elements[$j]->getCard($idc);
//                echo "elements[$j].getCard(" . arr2str($idc) . ")==$tCard<br>"; // debug
                if ($tCard>$card)
                    $card = $tCard;
            }
        }

//        echo "TMultiBlock::getCard(" . arr2str($idc) . ")==$card<br>"; // debug

        return $card;
    }
*/

    function getActMatrix ($idc)
    {
        $actMatrix = array();
        for ($i=0; $i<sizeof($this->elements); $i++) {
            $actMatrix[$i] = $this->elements[$i]->getActArray($idc);
        }

        return $actMatrix;
    }

    function out ($idc)
    {
        $actMatrix = $this->getActMatrix ($idc);
//        echo "TMultiBlock::out called on '" . $this->name . "' with actMatrix==" . mat2str($actMatrix) . " and idc==" . arr2str($idc) . "<br>"; // debug

        // first, compute how many times the contents of the nested
        // block must be printed:
        $card = $this->getCard($idc);

        // print embedded blocks; each embedded block will decide
        // for itself wether it will be printed or not:
        for ($j=0; $j<$card; $j++) {

            $indices = $idc; $indices[] = $j;
            for ($i=0; $i<sizeof($this->elements); $i++) {
                $actArray = $this->elements[$i]->getActArray($idc);
                if ($this->elements[$i]->getCard($idc)>0)
                    if ($actMatrix[$i][$j])
                        $this->elements[$i]->elementsOut ($indices);
            }
        }
    }

    function getType()
    {
        return MULTIBLOCK;
    }
}



/********************************************************************
* CLASS TBlock
********************************************************************/


class TBlock extends TBasicBlock
{
    // reference to the master block if this block appears in an
    // alternative block construct:
    var $masterBlock;

    // current element index for the block (only valid while filling
    // in values in placeholders, ie. before method "out" is called
    // in class phptempt):
    var $index;

    // indices mapped to cardinalities; use this array to retrieve
    // the number of times that method "begin" of class phptempt
    // has been called on this block:
    var $cards;

    // identifier for the block:
    var $ident;

    // index of recursive reference; also used as flag indicating
    // a recursion (see method "add"):
    var $refIndex;


    function TBlock ($ident)
    {
        $this->TBasicBlock();
        $this->ident = $ident;

        $this->index = 0;
        $this->refIndex = -1;

        $this->cards = new RecArray();
    }

    function getIdent() { return $this->ident; }

    function setMaster ($master)
    {
        // set the master block to a TMultiBlock instance if this
        // block appears in an alternative block construct:
        $this->masterBlock = &$master;
    }

    function getCard ($idc)
    {
        $card = $this->cards->getVal($idc);
//        echo "TBlock::getCard called on '" . $this->name . "' with idc==" . arr2str($idc) . ", card==$card<br>"; // debug
        if (isundefined($card))
            return 0;
        else
            return $card;
    }

    function incCard ($idc)
    {
        // to synchronise placeholder indices and cardinality indices
        // of blocks; indice [] would be associated with the root (or
        // "doc") block, which will be printed, inadvertedly, exactly
        // once anyway:
        array_pop($idc);
        if (sizeof($idc)<=0)
            return;

//        echo "TBlock::incCard called on '" . $this->name . "' with idc==" . arr2str($idc) . "<br>"; // debug
        // increment cardinality for the iteration specified by
        // indices $idc:
        $card = $this->cards->getVal ($idc);
        if (isundefined($card))
            $card = 0;
        $this->cards->addVal ($card+1, $idc);

        if (isset($this->masterBlock))
            $this->masterBlock->incCard ($idc);

//        echo "TBlock::incCard(" . arr2str($idc) . ")==" . $this->cards->getVal($idc) . "<br>"; // debug
    }

    function incIndex ()
    {
        // increment index; placeholder
        // values will be added to a new iteration

        // if the block is part of an alternative block construct then
        // the master block will control index incrementation:
//        echo "incIndex (" . elem2name($this) . "): elements==" . elem2str($this->elements) . "<br>"; // debug
        $this->setName ($this->name . "+");
        if (isset($this->masterBlock))
            $this->masterBlock->nestedIncIndex();
        else {
            $this->setIndex ($this->getIndex() + 1);
        }
    }

    function getIndex () { return $this->index; }

    function setIndex ($idx) { $this->index = $idx; }

    function resetNestedIndex ()
    {
//        echo "resetNestedIndex: elements==" . elem2str($this->elements) . "<br>"; // debug
        // reset index of nested blocks to 0:
        //
        for ($i=0; $i<sizeof($this->elements); $i++) {
            if ($this->elements[$i]->getType()==BLOCK) {
//                echo "resetNestedIndex: reset(" . elem2name($this->elements[$i]) . ")<br>"; // debug
                $this->elements[$i]->setIndex(0);
                //$this->elements[$i]->resetNestedIndex(); // debug
//                $this->elements[$i]->setName($this->elements[$i]->name . "*"); // 4
            }
            else if ($this->elements[$i]->getType()==MULTIBLOCK) {
                $this->elements[$i]->resetNestedIndex();
            }
        }
    }

    function & copy (&$ptempt, $superblock)
        // $ptempt is reference to phptempt instance; $superblock
        // is name of the embedding block
    {
        // creates new instance of this class
        $cp =& $ptempt->addBlock ($superblock, $this->ident);

        // recursively make copies of the elements contained in this
        // block:
        //
        $qualified = $superblock . "." . $this->ident;
//        echo "TBlock.copy(0): begin ('$qualified')<br>"; // debug
//        echo "TBlock.copy(1): elements==" . elem2str($this->elements) . "<br>"; // debug
        for ($i=0; $i<sizeof($this->elements); $i++) {

//	        echo "TBlock.copy(2): qualified=='$qualified', type==" . int2type($this->elements[$i]) ."<br>"; // debug
//            $cp->elements[$i] =& $this->elements[$i]->copy (&$ptempt, $qualified);
            $cp->add ($this->elements[$i]->copy ($ptempt, $qualified));

        }
//        echo "TBlock.copy(3): end ('$qualified')<br>"; // debug

        return $cp;
    }

    function isRecursive ()
    {
//        echo "TBlock.isRecursive (" . $this->ident . "): elements==" . elem2str($this->elements) . "<br>"; // debug
    	return $this->refIndex>=0;
    }

    function setRec (&$ptempt, $blockname)
    {
//        echo "setRec: blockname==$blockname, ident==" .
//        	$this->elements[$this->refIndex]->block->ident . "<br>"; // debug
        if ($this->isRecursive()) {
            $this->elements[$this->refIndex] =&
            	$this->elements[$this->refIndex]->block->copy ($ptempt, $blockname);
            $this->refIndex = -1;
        }
        else
            fatal ("Use of TBlock.setRec only allowed in recursive blocks");
    }

    function add (&$element)
    {
        // superclass adds the element to the instance:
        TBasicBlock::add (&$element);

        // if the element is a recursive block:
        if ($element->getType()==BLREFERENCE) {
            if ($this->isRecursive())
                fatal ("Only one recursive reference allowed in a block.");
            else
	            $this->refIndex = sizeof($this->elements)-1;
        }
    }

    function getType ()
    {
        // return type of the class:
        return BLOCK;
    }

    function combArray (&$res, $arr)
    {
        // logical operator for arrays; the arrays are combined elementwise
        // using the logical and operator; the result of the combination
        // is returned in $res by reference;
        // the arrays should, of course, contain elements of type boolean:
        //
        $sz = max (array_size($res), array_size($arr));
//        echo "combArray: res==" . arr2str($res) . ", arr==" . arr2str($arr) . "<br>"; // debug
        for ($i=0; $i<$sz; $i++) {
//            echo "res[$i]==" . $res[$i] . ", arr[$i]==" . $arr[$i] . "<br>"; // debug
            if (isundefined($res[$i]) && isundefined($arr[$i]))
                $res[$i] = UNDEFINED;
            else
                $res[$i] = true;

// this is a thing that I hate with PHP: UNDEFINED is a constant that
// is declared to be 'false' (see above); if a $res[$i] equals "", the
// empty string, a comparation $res[$i]==UNDEFINED will yield 'true'
// and thus the following assignment will not at all do what I
// expected it to:
//            $res[$i] = ($left!=UNDEFINED) || ($right!=UNDEFINED);
// why are the PHP designers so fucked up with respect to typing?

        }
    }

    function getActArray ($idc)
    {
        // returns an array of boolean values; true at position n
        // of the array iff in iteration n in method out values are
        // defined for any of the placeholders in the block;
        // NOTE: if there are no placeholders defined in the block
        // then the resulting array will be empty!
        //
//        echo "idc==" . arr2str ($idc) . "<br>"; // debug
        $actArray = array();
        for ($i=0; $i<sizeof($this->elements); $i++) {
            if ($this->elements[$i]->getType()==PLACEHOLDER) {
                $tmpArray = $this->elements[$i]->getValArray($idc);
//                echo "array size is " . array_size($tmpArray) . "<br>"; //debug
//                echo "getActArray 0: tmpArray[0]==" . is_string($tmpArray[0]) . "<br>"; // debug
//                echo "getActArray 1: actArray==" . arr2str($actArray) . ", tmpArray==" . arr2str($tmpArray) . "<br>"; // debug
                $this->combArray ($actArray, $tmpArray);
//                echo "getActArray 2: actArray==" . arr2str($actArray) . ", tmpArray==" . arr2str($tmpArray) . "<br><br>"; // debug
            }
        }

        if (sizeof($actArray)<=0) {

            // if the block contains no placeholders at all then
            // print the (pure HTML) contents of the block exactly
            // as many times as the "begin" method has been called
            // on the block:
//            echo "isPure==" . $this->isPure . "<br>"; // debug
            if ($this->isPure) {
                $card = $this->getCard($idc);
//                echo "TBlock.getActArray: card==$card<br>"; // debug
                $actArray = repeat ('1', $card);
            }
        }

        return $actArray;
    }

    function out ($idc)
    {
//        echo "out: called on '" . $this->name . "' with idc==" . arr2str($idc) . "<br>"; // debug
        // takes as argument the indices of the placeholders to
        // be output

        // returns an array of flags, each element indicating if at least
        // on placeholder is set for the specified iteration (1) or not
        // (UNDEFINED):
        $actArray = $this->getActArray($idc);

        // $card denotes the number of iterations for the block and
        // is retrieved from the $actArray above; $card==0 means that
        // no value is defined for a placeholder of the block or
        // no placeholder at all exists in the block:
        $card = sizeof($actArray);
//        echo "ident==" . $this->ident . ", card==$card, actArray==" . arr2str ($actArray) .
//        	", idc==" . arr2str ($idc) . "<br>"; // debug
//        echo "index==" . $this->index . "<br>"; // debug

        // if none of the conditions above holds then repeatedly print
        // the block contents with the different placeholder values
        // for each iteration:
        //
        for ($j=0; $j<$card; $j++) {

            $indices = $idc; $indices[] = $j;

            // does the block define placeholders in the current
            // iteration? If so, print the block contents:
            if (!isundefined($actArray[$j]))
                $this->elementsOut ($indices);
        }
    }
}



/********************************************************************
* CLASS TBlockRef
********************************************************************/


class TBlockRef
{
    // reference to the block that shall be inserted recursively:
    var $block;


    function TBlockRef ($block)
    {
        $this->block = &$block;
    }

    function & copy () { return $this; }

    function out ($idc)
    {
/*
        echo "<table bgcolor=\"#D0D0D0\" width=\"100%\">
            <tr><td>
        		This is the recursive area
            </td></tr>
            </table"; // debug
 */
    }

    function getType () { return BLREFERENCE; }
}



/********************************************************************
* CLASS RecArray
********************************************************************/


class RecArray
{
    // array of values in the queued array:
    var $values;

    function RecArray ()
    {
        $this->values = array();
    }

    function addVal ($value, $indices)
    {
//        echo "RecArray::addVal called for '$value' with indices== ". arr2str($indices) . "<br>"; // debug
        if (sizeof($indices)<=0) {
            fatal ("An array of indices for RecArray::addVal ".
                "must at least contain one element");
        }

        else if (sizeof($indices)>1) {
            //
            // recursively descend to placeholders of nested blocks ...

            $idx = array_shift ($indices);
            if (isset($idx)) {

                // make sure the nesting level exists:
                if (!isset($this->values[$idx]))
                    $this->values[$idx] = new RecArray();

                // ... before descending:
                $this->values[$idx]->addVal ($value, $indices);
            }
        }

        else /* $indices==1 */ {
            //
            // ... until the innermost block has been reached:

            $idx = array_shift ($indices);
            $this->values[$idx] = $value;
        }
    }

    function getValues ($idc)
    {
//        echo "RecArray::getSize with idc==" . arr2str($idc) . "<br>"; // debug
        if (!is_array($idc)) {
            fatal ("RecArray::getSize expects an array of indices");
        }

        else if (sizeof($idc)>0) {
            //
            // recursively descend to placeholders of nested blocks ...

            $idx = array_shift ($idc);
            if (isset($idx)) {

                // make sure the nesting level exists:
                if (!isset($this->values[$idx]))
                    return array();
                        // I'm not sure if this is a
                        // fatal error...

                // ... before descending:
                return $this->values[$idx]->getValues ($idc);
            }
        }

        else /* sizeof($idc)==1 */ {
            //
            // ... until the innermost block has been reached:
//            echo "RecArray::getSize values==" . arr2str($this->values) . "<br>"; // debug
            if (!isset($this->values))
                error ("No values. Did You set a placeholder in the block?");
//            echo "RecArray.getSize returns " . is_string($this->values[0])  . "<br>"; // debug
            return $this->values;
        }
    }

    function getVal ($indices)
    {
//        echo "getVal: indices==" . arr2str ($indices) . ", values==" . arr2str($this->values) . "<br>"; // debug
        if (sizeof($indices)<=0) {
            fatal ("An array of indices for RecArray::getVal ".
                "must at least contain one element");
        }

        else if (sizeof($indices)>1) {
            //
            // recursively descend to placeholders of nested blocks ...

            $idx = array_shift ($indices);
            if (isset($idx)) {

                // make sure the nesting level exists:
                if (!isset($this->values[$idx]))
                    return UNDEFINED;

                // ... before descending:
                return $this->values[$idx]->getVal ($indices);
            }
        }

        else /* $indices==1 */ {
            //
            // ... until the innermost block has been reached:

            $idx = array_shift ($indices);
            if (isset($this->values[$idx]))
                return $this->values[$idx];
            else
                return UNDEFINED;
        }
    }
}


/********************************************************************
* CLASS TPlaceholder
********************************************************************/


class TPlaceholder
{
    // a multidimensional array that contain placeholder values for
    // subsequent repetitions of the container block:
    var $valueArray;

    // reference to the value queue to which placeholder values will
    // be added until the next call to the flush method:
    //var $current;

    // identifier for this placeholder (the portion of the fully qualified
    // placeholder name that follows a "#"):
    var $ident;


    function TPlaceholder ($ident)
    {
        $this->valueArray = new RecArray();
        $this->ident = $ident;
    }

    function getType () { return PLACEHOLDER; }

    function getIdent() { return $this->ident; }

    function & copy (&$ptempt, $superblock)
    {
        // add placeholder to the list:
        return $ptempt->addPlaceholder ($superblock, $this->ident);
    }

    function addVal ($value, $indices)
    {
        // values of placeholders are always stored in arrays; if a
        // placeholder has n values, the container block will be
        // printed n times.

        // the container block itself may be nested in a block in which
        // case the container will itself be repeated multiple times;
        // for this to accomplish the placeholder organizes its values
        // in nestes RecArray instances, one instance for each
        // repetition of the container block:

//        echo "indices==<b>" . arr2str ($indices) . "</b>, value==$value<br>"; // debug
        $this->valueArray->addVal ($value, $indices);
    }

    function setName ($name)
    {
        $this->name = $name;
    }

    function getIsPureRec ()
    {
        // by definition, a placeholder is not pure HTML; thus this
        // method always returns false:
        return false;
    }

    function getValArray ($idc)
    {
        // return array of values of the placeholder that are stored
        // at the current position $idc:
        return $this->valueArray->getValues($idc);
    }

    function getVal ($idc)
    {
        // return value of placeholder for the iteration specified
        // in $idc:
        return $this->valueArray->getVal($idc);
    }

    function out ($idc)
    {
//        echo "TPlaceholder called with idc==" . arr2str($idc) . "<br>"; // debug
//        $this->name .= "+"; // debug
        print $this->getVal($idc);
    }
}



/********************************************************************
* CLASS TPlaceholderRef
********************************************************************/


class TPlaceholderRef
{
    // a reference to a placeholder instance:
    var $placeholder;

    // nesting level of the block in which $placeholder is declared;
    // used in method out:
    var $level;

    function TPlaceholderRef ($placeholder, $level)
    {
        $this->placeholder = &$placeholder;
        $this->level = $level;
    }

    function & copy (&$ptempt, $superblock)
    {
        return $this;
    }

    function getVal ($idc)
    {
        // use only the leading $level elements of index array $idc:
        $indices = array_slice ($idc, 0, $this->level);

        return $this->placeholder->getVal ($indices);
    }

    function out ($idc)
    {
        // use only the leading $level elements of index array $idc:
        $indices = array_slice ($idc, 0, $this->level);

        // output referenced placeholder:
        $this->placeholder->out($indices);
    }

    function getType () { return PHREFERENCE; }
}



/********************************************************************
* CLASS TPure
********************************************************************/


class TPure
{
    var $pureText;

    function TPure ($pureText)
    {
        $this->pureText = $pureText;
    }

    function getType ()
    {
        return PURE;
    }

    function & copy () { return $this; }

    function out ()
    {
        print $this->pureText;
    }

    function getValArray ()
    {
        // since only placeholders can store values, a TPure instance
        // always returns empty array:
        return array();
    }
}



/********************************************************************
* MAIN CLASS phptempt
********************************************************************/


class phptempt
{
    // hashtable of blocks in the template; $blocks["doc"]
    // will be the root block of the template:
    var $blocks;

    // hashtables of anonymous blocks that embed alternative
    // blocks:
    var $sBlocks;

    // hashtable of placeholders:
    var $idents;

    // hastable of the conditional blocks:
    var $condits;

    // hastable of the conditional identifiers:
    var $cids;

    // stack for the block names that have been introduced in
    // subsequent calls to begin():
    var $blockStack;

    // array of indices for the storage of placeholders:
    var $indices;

    // debug flag; if set then some additional warnings will be
    // printed:
    var $debug;

    function phptempt ($includeFileName)
        // load template from preprocessed file $includeFileName;
        // $errorinc denotes an optional error page that will be
        // shown in case
    {
        // open specified include file to check for existence:
        //
        $fp = @fopen($includeFileName, "r", 1);
        if (is_bool($fp) && !$fp)
            fatal ("phptempt: No such include file '$includeFileName'");
        fclose ($fp);

        // initialize stack of block names:
        $this->blockStack = array ();

        // initialize placeholder index array:
        $this->indices = array();

        // initialize placeholder current index:
        $this->index = array();

        // by default, debug mode is turned off:
        unset ($this->debug);

        // include the generated template file which contains the
        // definitions of the $blocks and $idents hashmaps:
        include ($includeFileName);

        // initially, the "doc" block which comprises all elements of
        // the document will be opened; a subsequent call to method
        // begin for block "doc" is optional and will be ignored:
        $this->begin ("doc");
    }

    function setDebug ($toOn)
    {
        // sets the debug flag to true iff $toOn is true; if $toOn
        // is false then the debug flag is unset; NOTE: the exact
        // value of the flag is of no importance since the flag
        // is checked using PHP's isset facility:
        //
        if ($toOn)
            $this->debug = "debug";
        else
            unset ($this->debug);
    }

    function addVal ($varName, $val)
    {
//        echo "phptempt::addVal called with varName==$varName, val=='$val' for indices==" . arr2str($this->indices) . "<br>"; // debug
        // get current block name:
        $blockName = $this->getCurrentBlockName();

        $varPathname = $blockName . "#" . $varName;

        // add a value to the list of values for the specified
        // placeholder $varName:
        if (isset($this->idents[$varPathname])) {

            // add value to the list of values for the specified
            // palceholder in $varName:
            $this->idents[$varPathname]->addVal($val, $this->indices);
        }
        else
            error ("Unknown variable '$varPathname'");
    }

    function & addBlock ($superblock, $ident)
    {
        $blockname = $superblock . "." . $ident;

        // the block will only be created if it does not yet exists;
        // otherwise, a reference to the existing block will be returned:
        //
        if (!isset($this->blocks[$blockname])) {
//            echo "addBlock: creating block '$blockname'<br>"; // debug
	        $this->blocks[$blockname] = new TBlock ($ident);
//            $this->blocks[$blockname]->setName (rand());
        }

        return $this->blocks[$blockname];
    }

    function & addPlaceholder ($blockname, $ident)
    {
        $phname = $blockname . "#" . $ident;

        // if the placeholder with the specified name already
        // exists in the list of placeholders $idents then return
        // the existing placeholder:
        //
        if (!isset($this->idents[$phname])) {
	        $this->idents[$phname] = new TPlaceholder ($ident);
            $this->idents[$phname]->setName (rand());
        }

        return $this->idents[$phname];
    }

    function begin ($blockName)
    {
        // disallow qualified block names:
        if (ereg("[^a-zA-Z_0-9]", $blockName))
            fatal ("Illegal block name '$blockName' in begin");

        $bn = $this->getNestedBlockName ($blockName);
//        echo "<b>begin</b> ('$bn')<br>"; // debug

        // a call to the begin method for the document root block
        // "doc" is optional:
        if ($bn=="doc.doc")
            return;

        // if no such block with name $blockName is defined then
        // issue an error message and terminate:
        if (!isset($this->blocks[$bn])) {

            // is the current block recursive?
            //
            $current = $this->getCurrentBlockName();
//	        echo "not defined: '$bn', current='$current'<br>"; // debug
            if ($this->blocks[$current]->isRecursive()) {
                // copy recursive block:
                $this->blocks[$current]->setRec ($this, $current);
//                $this->blocks[$current]->copy (&$this, $current);
            }
            else
            	fatal ("No such block defined: '$bn'");
        }

        // otherwise, push new block name on stack:
        array_push ($this->blockStack, $bn);
//        echo "begin: blockstack==" . arr2str ($this->blockStack) . "<br>"; // debug

        // get current index for the block; initialize index
        // if it has not been set before; finally push index on
        // top of the $indices array:
        //
        $idx = $this->blocks[$bn]->getIndex();
        $this->indices[] = $idx;
//        echo "... begin ('$bn', id=" . elem2name($this->blocks[$bn]) . ") with indices==" . arr2str ($this->indices) . "<br>"; // debug

        // increment cardinality of the block for current index to
        // indicate that "begin" method has been called one more time:
//        echo "calling incCard on '" . $bn . "' for indices==" . arr2str($this->indices) . "<br>"; // debug
        $this->blocks[$bn]->incCard ($this->indices);
    }

    function getNestedBlockName ($blockName)
    {
        if (sizeof($this->blockStack)<=0)
            $bn = $blockName;
        else
            $bn = $this->blockStack[sizeof($this->blockStack)-1] .
                "." . $blockName;

        return $bn;
    }

    function getCurrentBlockName ()
    {
        // if the stack is empty, return an empty string:
        if (sizeof($this->blockStack)<=0)
            return "";

        // otherwise, get current block name from the stack top:
        return $this->blockStack[sizeof($this->blockStack)-1];
    }

    function end ($blockName)
    {
        // disallow qualified block names:
        if (ereg("[^a-zA-Z_0-9]", $blockName))
            fatal ("Illegal block name '$blockName' in end");

        // pop current block name from stack:
//        echo "... end: blockstack==" . arr2str ($this->blockStack) . ", blockName==$blockName<br>"; // debug
        $current = array_pop ($this->blockStack);
//        echo "<b>end</b> ('$current')<br>"; // debug

        // check if the specified block name and the name
        // of the outer make up the previous block name:
        $prev = $this->getNestedBlockName($blockName);
        if ($prev != $current)
            error ("Expected end of block " . $prev);

        // return to lower block nesting level:
        //
        array_pop ($this->indices);
        $this->blocks[$current]->incIndex();
        $this->blocks[$current]->resetNestedIndex();

//        echo "... end ('$current') with new indices==" . arr2str($this->indices) . "<br>"; // debug

//        echo "end: $blockName, indices==" . arr2str($this->indices) . "<br>"; // debug
    }

    function debugPrint ()
    {
        $keys = array_keys ($this->blocks);
        for ($i=0; $i<sizeof($keys); $i++) {

            // current key:
            $key = $keys[$i];

            echo "blocks[$key]->index==" . $this->blocks[$key]->name . "<br>";

        }
    }

    function debugPrintRec ($elem, $key)
    {
        $quali = $key . "." . $elem->ident;
        echo "block ($quali)=='" . $elem->name . "'<br>";

        // recursively print names of embedding elements:
        //
        for ($i=0; $i<sizeof($elem->elements); $i++) {
            switch ($elem->elements[$i]->getType()) {
            case BLOCK:
                $this->debugPrintRec ($elem->elements[$i], $quali);
                break;
            //case PLACEHOLDER:
            //    echo "placeholder '" . $elem->name . "'<br>";
            }
        }
    }

    function out ()
    {
        // a block stack that is not empty indicates that the
        // programmer forgot tailing calls to the end() method;
        // we will do this now, but issue a warning:
        //
        if (sizeof($this->blockStack)>0) {
            if (isset($this->debug))
                error ("The block stack is not empty (list of blocks ".
                    "to end will follow)");
            while (sizeof($this->blockStack)>0) {
                $blockName =
                    $this->blockStack[sizeof($this->blockStack)-1];
                if (isset($this->debug))
                    print "Calling method end for block '" . $blockName . "'<br>";
                $this->end ($blockName);
            }
        }

        // print the "doc" block exactly once; recursively print the
        // contents of the nested blocks:
        $indices = array (0);
        $this->blocks["doc"]->elementsOut($indices);
    }

    function write ($filename)
        // execute method out but redirect all output to the file
        // with specified filename.
        // the method returns "" (empty string) if no error occurred or a
        // short description of the error that occurred.
    {
        // start output buffering, then perform output with a call
        // to the out method above:
        ob_start ();
        $this->out();

        // open a file with the specified file name; existing file
        // will be overwritten without warning; if no file could be
        // opened for writing then return with error indication:
        $fp = fopen ($filename, "w");
        if (is_bool($fp) && !$fp) {
            ob_end_clean ();
            return "Could not open file $filename.";
        }

        // retrieve contents of the output buffer and write it to
        // the file; return with error indication if no contents could
        // be retrieved from the buffer:
        $buf = ob_get_contents();
        if (is_bool($buf) && !buf) {
            ob_end_clean ();
            return "Could not retrieve output buffer contents.";
        }

        fwrite ($fp, $buf);

        // close the file and turn off buffering:
        fclose ($fp);
        ob_end_clean ();

        // no errors occured:
        return "";
    }

    function setCond ($cid)
    {
        // sets conditional identifier ("cid") to the specified value

        // does a conditional identifier exist with the specified name?
        // If so, set it to the specified value:
        if (isset($this->cids[$cid]))
            $this->cids[$cid] = true;

        // otherwise, if it does not exist then issue a warning:
        else
            error ("Unknown conditional identifier '$cid'.");
    }

    function &getNestedBlocks ($blockName)
        // warning: this method has not yet been tested!
    {
        $nestedBlocks = array();

        // go through the list of blocks and add references to
        // those blocks nested in the block with the specified
        // name:
        for (
            reset ($this->blocks);
            $block = &current($this->blocks);
            next ($this->blocks)
        ) {
            // found a match (note that all blocks nested into the
            // given block have $blockName as prefix for their
            // index string):
            if ($blockName == substr (key($block), 0, strlen($blockName)))
                $nestedBlocks[] = &$block;
        }

        return $nestedBlocks;
    }
}


/********************************************************************
* FUNCTIONS THAT RELY (PARTIALLY) ON PHPTEMPT CLASSES
********************************************************************/


function fatal ($msg, $errorinc="")
{
    if ($errorinc=="") {
	    // print error message and terminate immediately:
	    die ("<b>Fatal error</b>: $msg. Terminating.<br>");
    }

    // an error page (include) has been defined; use this include
    // to echo the error:
    //

    $e = new phptempt ($errorinc);
    $e->addVal ("ERRORMESSAGE", $msg);

    $e->out();
    exit;
}

?>
Return current item: Phptempt