<?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 = ¤t($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;
}
?>