php/envClass.php

<?php
/*
    output handling using a class structure
  
    *** unfinished draft ***********
*/

class PutEcho {
    public function put($txt) {
        echo $txt;
    }
}

class PutMem {
    public $out;
    function __construct() {
        $this->out  = fOpen('php://memory', 'w+');
    }

    public function put($txt) {
        fwrite($this->out, $txt);
    }

    public function close() {
        fclose($this->out);
    }

    public function cont() {
        $sz = ftell($this->out);
        rewind($this->out);
        $c = fread($this->out, $sz);
        ftruncate($this->out, 0);
        rewind($this->out);
        return $c;
   }
}


class Out {
    public static $out ;
    static $list = [];
    public static function outAdd($nm, $o) {
        Out::$out = Out::$list[$nm] = $o;
    }
}

class OutStack {
    protected $stack = [];
    protected $oFun;
    protected $popped;
    const Lipop = ['Li' => 1]
        , List  = ['Li']
        , LLpop = ['Li' => 1, 'LL'  => 1]
        , TRpop = ['Li' => 1, 'LL' => 1, 'TD' => 1, 'TR' => 1]
        , TRst  = ['TR']
        , TBok = ['Tb' => 1]    
        , TDpop = ['Li' => 1, 'LL' => 1, 'TD' => 1, ]
        , TRok = ['TR' => 1]    
        , TDok = ['TD' => 1]    
        , THok = ['TH' => 1]    
    ;
    function __construct() {
        $this->o = new PutEcho();
    }

    protected function pop($pop, $to = null, $toPop = 0) {
        while ((false !== $tos = end($this->stack)) and isset($pop[$tos[0]]))
            $this->{"xyt$tos[0]End"}();
        if ($to !== null and ($tos === false or $tos[0] !== $to))
            err("pop tos = ", $tos, 'and not ', $to);
        return $toPop ? $this->popped = array_pop($this->stack) : $tos;
    }

    public function o($txt) {
        ($this->oFun)($txt);
    }

    public function xyt($txt) {
        $this->oo($txt);
    }

    public function xytW($txt) {
        $this->oW($txt);
    }

    public function xytNL($txt) {
        $this->oNL($txt);
    }

    public function xytH($txt) {
        $this->pop(self::LLpop);
        $this->oH($txt);
    }

    public function xytLL($fmt) {
        array_push($this->stack, ['LL', $fmt]);
        $this->oLL($fmt);
    }

    public function xytLLEnd() {
        $this->oLLEnd($this->pop(self::Lipop, 'LL', 1)[1]);
    }

    public function xytLi($txt) {
        $tos = $this->pop(self::Lipop, 'LL');
        array_push($this->stack, self::List);
        $this->oLi($txt);
    }

    public function xytLiEnd() {
        $tos = $this->pop(null, 'Li', 1);
        $this->oLiEnd();
    }


    public function xytTb() { # table begin     
        array_push($this->stack, ['Tb', -1, -1, null]);
        $this->oTb();
    }

    public function xytTbEnd() { # table end        
        $this->pop(self::TRpop, 'Tb', 1);
         $this->oTbEnd();
    }

    public function xytTCF($fmt) { # table row formats      
        if ('Tb' !== ($tb = &$this->stack[array_key_last($this->stack)])[0])
            err("xytTCF top of stack", $tb, "not table");
        $tb[3] = $fmt;
    }

    public function xytTR() { # table row        
        $this->pop(self::TRpop, 'Tb');
        $tb = &$this->stack[array_key_last($this->stack)];
        $tb[1]++ ;
        $tb[2] = -1 ;
        array_push($this->stack, self::TRst);
        $this->oTR();
    }

    public function xytTREnd() { # table row        
        $this->pop(self::TDpop, 'TR', 1);
        $this->oTREnd();
    }

    public function xytTD($fmt, $tx) { # table data      
        if ('Tb' === end($this->stack)[0])
            $this->xytTR();
        else
            $this->pop(self::TDpop, 'TR');
        if ('Tb' !== ($tb = &$this->stack[array_key_last($this->stack)-1])[0])
            err("xytTD stack has", $tb, "not table");
        $tb[2]++ ;
        $fmt .= $tb[3][$tb[2]] ?? '';
        $fmt = (strpos($fmt, '!') === false ? '' : '!') . (strpos($fmt, 'r') === false ? 'l' : 'r');
        array_push($this->stack, $tos = ['TD', $fmt[0] === '!' ? 'th' : 'td', $fmt]);
        $this->oTD($tos, "r$tb[1]c$tb[2] $tx");
    }

    public function xytTDEnd() { # table row        
        $this->oTDEnd($this->pop(self::LLpop, 'TD', 1));
    }
}


class OutHtml extends OutStack {
    public function oo($txt) { $this->o->put("$txt\n<br>"); }

    public function oNL($txt) { $this->o->put("\n<br>$txt"); }

    public function oW($txt) { $this->o->put($txt); }

    public function oH($txt) { $this->o->put("\n<h2>$txt</h2>"); }

    public function oLL($fmt) { $this->o->put("\n<$fmt>"); }

    public function oLLEnd($fmt) { $this->o->put("\n</$fmt>"); }

    public function oLi($txt) { $this->o->put("\n<li>$txt"); }

    public function oLiEnd() { $this->o->put("</li>"); }

    public function oTb() { $this->o->put("\n<table border=2>"); }

    public function oTbEnd() { $this->o->put("\n</table>"); }

    public function oTR() { $this->o->put("\n<tr>"); }

    public function oTREnd() { $this->o->put("\n</tr>"); }

    public function oTD($e, $txt) { $this->o->put("\n<$e[1]" . ($e[2][-1] === 'r' ? " style='text-align: right;'" : '') . ">$txt"); }

    public function oTDEnd() { $this->o->put("</{$this->popped[1]}>"); }
}

class OutCli extends OutStack {
    protected $lvl = 0;
    protected $intend = '';
    protected $lastNL = false;

    public function level($l) {
        $this->lvl = $l >= 0 ? $l : $this->lvl + 10 + $l;
        $this->intend = str_pad('', 4 * $this->lvl);        
    }

    public function o($txt) {
        ($this->oFun)($txt);
        $this->lastNL = false;
    }

    public function oo($txt) { $this->o->put("$txt\n$this->intend"); }

    public function oNL($txt) { $this->o->put("\n$this->intend$txt"); }

    public function oW($txt) { $this->o->put($txt); }

    public function oH($txt) {$this->o->put("\n\n--- $txt ---\n"); }

    public function oLL($fmt) { 
        $this->level(-9); // +1
        array_push($this->stack[array_key_last($this->stack)], $this->lvl, 0);
    }

    public function oLLEnd() { 
        if ($this->popped[2] !== $this->lvl)
            err('oOLEnd lvl mismatch');
        $this->level(-11); // -1
    }

    public function oLi($txt) { 
        $ll = &$this->stack[array_key_last($this->stack)-1];
        $rp = max(1, 4-$this->lvl);
#    echo "\noLi $iL ". a2str($this->stack);
        $this->o->put(($this->lastNL ? '' : "\n") . substr($this->intend, 0, -2) 
            . ($ll[1] === 'OL' ? (++$rp[3]) . str_repeat('.', $rp) : str_repeat('*', $rp)) . " $txt");
    }

    public function oLiEnd() {
        if (! $this->lastNL) {
            $this->o->put("\n");
            $this->lastNL = true;            
        } 
    }

    public function oTb() { 
        if ('Tb' !== ($tb = &$this->stack[array_key_last($this->stack)])[0] or count($tb) !== 4)
            err("oTb stack has", $tb, "not table with 4 eles");
        array_push($tb, [], [], $this->o, $this->lvl);
        $this->o = new PutMem();
        $this->level(0);
    }
    public function oTbEnd() { 
        if ('Tb' !== ($tb = $this->popped)[0] or count($tb) !== 8)
            err("oTbEnd popped", $tb, "not table with 8 eles");
        if (0 !== ftell($this->o->out))
            err("oTbEnd o not empty but", $this->o->cont());
        $this->o->close();
        $this->o = $tb[6]; 
        $this->level($tb[7]);
        $this->tbGen($this->popped[4], $this->popped[5]);
    }

    public function oTR() { 
        if ('Tb' !== ($tb = &$this->stack[array_key_last($this->stack)-1])[0] or count($tb) !== 8)
            err("oTR stack -1 has", $tb, "not table with 8 eles");
        $tb[4][] = [];
        $tb[5][] = [];
    }

    public function oTREnd() { 
    }

    public function oTD($e, $txt) {
        if ('Tb' !== ($tb = &$this->stack[array_key_last($this->stack)-2])[0] or count($tb) !== 8)
            err("oTD stack -2 has", $tb, "not table with 8 eles");
        $tb[5][$tb[1]][$tb[2]] = $e[2];
        $this->o->put($txt);
    }

    public function oTDEnd() { 
        if ('Tb' !== ($tb = &$this->stack[array_key_last($this->stack)-1])[0] or count($tb) !== 8)
            err("oTb stack  has", $tb, "not table with 8 eles");
        $tb[4][$tb[1]][$tb[2]] = $this->o->cont();
    }

    function tbGen($tb, $fm) {
        xyt('tbGen table', $tb, 'fmt', $fm);
        $cLen = [];
        $rLin  = [];
        foreach ($tb as $rx => $rw) {
            foreach ($rw as $cx => $ce) {
                $tb[$rx][$cx] = preg_split("/\R/", $ce);
                if (count($tb[$rx][$cx]) > ($rLin[$rx] ?? -1))
                    $rLin[$rx] = count($tb[$rx][$cx]);
                $l = array_reduce($tb[$rx][$cx], fn($r, $e) => ($r > $l = strlen($e)) ? $r : $l, 0);
                if ($l > ($cLen[$cx] ?? -1))
                    $cLen[$cx] = $l;
            }
        }
        xyt('tbGen table', $tb, 'cLen', $cLen, 'rLin', $rLin);
        $sep = '+';
        foreach ($cLen as $cx => $cl)  
            $sep .= str_repeat('-', $cl+2) . '+';
        $this->xyt('');
        for ($rx=0; $rx<count($rLin); $rx++)  {
            $this->xyt($sep);
            for ($lx=0; $lx< $rLin[$rx]; $lx++)  {
                $tx = '| ';
                foreach ($cLen as $cx => $cl)  {
                    $tx .= str_pad($tb[$rx][$cx][$lx] ?? '', $cl) . ' | ';
                }       
                $this->xyt($tx);
            }       
        }          
        $this->xyt($sep);
    }
}

Out::outAdd('before', EnvCL ? new OutCli : new OutHtml());

function xyt() { # data and nl
    Out::$out->xyt(func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : ''));
}

function xytNL() { #nl and data
    Out::$out->xytNL(func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : ''));
}

function xytEC() { # data in format $ = $, ... and nl
    Out::$out->xyt(func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2strEC(func_get_args()) : ''));
}

function xytW() { # data (without any nl)
    Out::$out->xytW(func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : ''));
}

function xytH() { # header
    Out::$out->xytH(func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : ''));
}

function xytOL() { # ordered=numbered list, optional with first item   
    Out::$out->xytLL('ol');
    if (func_num_args() > 0)
        Out::$out->xytLi(func_num_args() === 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function xytOLEnd() { # end ordered list      
    if (func_num_args() > 0)
        Out::$out->xytLi(func_num_args() === 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
    Out::$out->xytLLEnd();
}

function xytLi() { # listitem (in ol or ul)
    Out::$out->xytLi(func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : ''));
}

function xytUL() { # undordered= bulleted list, optional with first item
    Out::$out->xytLL('ul');
    if (func_num_args() > 0)
        Out::$out->xytLi(func_num_args() === 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function xytULEnd() { # end undordered list       
    if (func_num_args() > 0)
        Out::$out->xytLi(func_num_args() === 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
    Out::$out->xytLLEnd();
}

function xytTb() { # table begin, optional with first td       
    Out::$out->xytTb();
    if (func_num_args() > 0)
        Out::$out->xytTD('', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function xytTbEnd() { # table end        
    if (func_num_args() > 0)
        Out::$out->xytTD('', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
    Out::$out->xytTbEnd();
}

function xytTCF() { # table column format, currently only l (left align default) and r (right align)
    $f = func_get_args();
    foreach ($f as $e)
            if ($e !== 'l' and $e !== 'r' and $e !== '!' and $e !== '!l' and $e !== '!r' )
                err("bad format $e in xytTCF(", $f, ')'); 
    Out::$out->xytTCF($f);
}

function xytTR() { # row begin, optional with first td       
    Out::$out->xytTR();
    if (func_num_args() > 0)
        Out::$out->xytTD('', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function xytTRD() { # row begin, with 0 to n datacells
    Out::$out->xytTR();
    foreach (func_get_args() as $d)
        Out::$out->xytTD('', $d);
}

function xytTRH() { # row begin, with 0 to n headercells
    xytTR();
    foreach (func_get_args() as $h)
        xytTH('!', $h);
}

function xytTD() { # td = dataCell begin, optional with first words        
    Out::$out->xytTD('', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : ''));
}

function xytTDD() { # tdd = 0 to n dataCells
    foreach (func_get_args() as $d)
        xytTD('', a2str($d));
}

function xytTH() { # th = headerCell begin, optional with first words
    Out::$out->xytTD('!', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : ''));
}

function xytTHH() { # 0 to n headerCell
    foreach (func_get_args() as $h)
        xytTD('!', a2str($h));
}


echo a2str(OutStack::TRpop);

xyt('wie gehts?', 'oder nicht');
xytNL('outNL', 'vorher und nicht nachher');
xyt('zweitens wie gehts?', 'oder nicht');
xytH('und', 'outH');
xytEC('eins', 1, 'zwei', 2);
xyt('und schluesxs gehts?', 'oder nicht');
xytH('Listen');
xyt('ordered');
xytOL('ordered list eins');
xytLi('zwei');
xytNL('nach nl zwei');
    xytOL('nested ordered list drei');
    xytLi(32);
    xytOLEnd();
xytOLEnd('vier');
xytUL();
xytLi('unordered list eins');
xytLi('ul zwei');
xytNL('+ nl');
xytULEnd();

xytH('table');
xytTb();
xytTCF('l','r');
xytTR();
xytTD('eins' , 'zwei');
xytNL(11, 12, 13);
xytNL(14, 15, 16);
xytTD('dreri' , 'vier');
xytTrD(21,23,24);
xytTbEnd();
xyt('ente');