π
debug
xhtml5
source files
validators
server variables

xhtml5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><title>Main</title><link rel="stylesheet" href="/style/" type="text/css"/><link rel="icon" href="/favicon.png" type="image/png"/><meta name="viewport" content="width=device-width,initial-scale=1"/></head><body><span style="position:fixed;bottom:0;right:0"><a href="?debug">π</a></span><h1>Main</h1><br/><fieldset><table><tbody><tr><td><a href="radio/">Radio</a></td></tr><tr><td><a href="chinese/">Chinese</a></td></tr><tr><td><a href="network/">Network</a></td></tr><tr><td><a href="game/">Nebula</a></td></tr><tr><td><a href="sounds/">Test Sounds</a></td></tr></tbody></table></fieldset></body></html>

source files
/srv/http/index.php
/srv/http/basedoc.php
/srv/http/libs/htmldoc.php
/srv/http/libs/xmldoc.php
/srv/http/libs/doc.php
/srv/http/libs/text.php
/srv/http/libs/xml.php
/srv/http/libs/html_utils.php
/srv/http/libs/css.php
/srv/http/libs/style.php
/srv/http/libs/http.php
/srv/http/debug.php
/srv/http/debugdoc.php
/srv/http/highlightdoc.php
/srv/http/highlight.php

/srv/http/index.php, 2020-04-15 18:28:32 CEST
<?php
require_once('basedoc.php');

$title = 'Marcoen’s';
$title = 'Chez Marcoen';
$title = 'Main';

$items = array(
    array('link' => 'radio/', 'title' => 'Radio'),
//    array('link' => 'dutchtv/', 'title' => 'Dutch TV'),
    array('link' => 'chinese/', 'title' => 'Chinese'),
    array('link' => 'network/', 'title' => 'Network'),
    array('link' => 'game/', 'title' => 'Nebula'),
    array('link' => 'sounds/', 'title' => 'Test Sounds'),
);

$doc = makeBasedoc($title);

$table = makeTable($items, array(
    function($item) { return makeLink($item['link'], $item['title']); },
));

$blocks = array(
    makeTag('h1', $title),
    makeFieldset()->add($table),
);

$doc->body()->add(makeLines($blocks));

$doc->render();

/srv/http/basedoc.php, 2018-10-19 11:53:24 CEST
<?php
require_once('libs/htmldoc.php');
require_once('libs/html_utils.php');
require_once('libs/style.php');
require_once('libs/http.php');
require_once('debug.php');

class basedoc extends htmldoc {
    function __construct($title, $style = '/style/', $favicon = '/favicon.png') {
        parent::__construct($title);
        $this
            ->setLanguage('en')
            //->add(makeStyleAttribute(makeStyle()->set('background-color', '#000')))
            ;
        $this->head()
            //->add(makeCSSLink('/normalize.css'))
            ->add(makeCSSLink($style))
            ->add(makePngIconLink($favicon))
            ->add(makeMeta('viewport', 'width=device-width,initial-scale=1'))
            //->add(makeHttpEquiv('Cache-control', 'public'))
            //->add(makeHttpEquiv('Cache-control', 'max-age=120'))
            ;
        $bottom_right = makeStyle()->set('position', 'fixed')->set('bottom', '0')->set('right', '0');
        $this->body()->add(makeTag('span', makeLink('?debug', 'π'))->set('style', $bottom_right));

    }

    public function render() {
        makeHttp()
            ->addHeader('Cache-Control', array('public, max-age=120'))
            ->render(makeDebuggable($this, 'xhtml5', 'xml'));
    }
}

function makeBasedoc($title, $style = '/style/', $favicon = '/favicon.png') {
    return new basedoc($title, $style, $favicon);
}

/srv/http/libs/htmldoc.php, 2016-10-16 12:02:05 CEST
<?php
include_once('libs/xmldoc.php');
include_once('libs/html_utils.php');

class htmldoc extends xmldoc {
    private $_head;
    private $_body;

    public function __construct($title) {
        parent::__construct('html', 'application/xhtml+xml');
        $this
            ->setDoctype('html')
            ->set('xmlns', 'http://www.w3.org/1999/xhtml')
            ->add($this->_head = makeTag('head'))
            ->add($this->_body = makeTag('body'))
            ;

        $this->_head
            //->add(makeTag('meta')->set('charset', 'UTF-8'))
            //->add(makeMeta('generator', 'MarcoenMedia'))
            ->add(makeTag('title', $title))
            ;
    }

    public function setLanguage($language) {
        parent::setLanguage($language);
        $this->root()->set('lang', $language);
        return $this;
    }

    public function head() {
        return $this->_head;
    }

    public function body() {
        return $this->_body;
    }
}

function makeHtmldoc($title) {
    return new htmldoc($title);
}


/srv/http/libs/xmldoc.php, 2018-08-02 11:29:36 CEST
<?php
include_once('libs/doc.php');
include_once('libs/xml.php');
include_once('libs/text.php');

class xmldoc extends doc {
    private $_root;
    private $_doctype;

    public function __construct($root, $mimetype = 'application/xml') {
        parent::__construct($mimetype);
        $this->_root = makeTag($root);
    }

    public function setDoctype($doctype) {
        $this->_doctype = $doctype;
        return $this;
    }

    public function setLanguage($language) {
        parent::setLanguage($language);
        $this->_root->set('xml:lang', $language);
        return $this;
    }

    public function root() {
        return $this->_root;
    }

    public function text() {
        $text = array();
        $text[] = '<?xml version="1.0" encoding="UTF-8"?>';
        if ($this->_doctype) {
            $text[] = '<!DOCTYPE ' . $this->_doctype . '>';
        }
        $text[] = $this->_root->text();
        return implode("\n", $text) . "\n";
    }

    public function set($key, $value) {
        $this->_root->set($key, $value);
        return $this;
    }

    public function add($item) {
        $this->_root->add($item);
        return $this;
    }
}

function makeXmldoc($root) {
    return new xmldoc($root);
}

/srv/http/libs/doc.php, 2016-10-17 21:13:58 CEST
<?php
require_once('libs/text.php');

abstract class doc implements toText {
    private $_type;
    private $_encoding;
    private $_language;
    private $_name;

    public function __construct($type /*= 'application/octet-stream'*/, $encoding = 'UTF-8') {
        $this->_type = $type;
        $this->_encoding = $encoding;
    }

    public function setLanguage($language) {
        $this->_language = $language;
        return $this;
    }

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

    public function type() {
        return $this->_type;
    }

    public function encoding() {
        return $this->_encoding;
    }

    public function language() {
        return $this->_language;
    }

    public function name() {
        return $this->_name;
    }
}


/srv/http/libs/text.php, 2016-01-03 22:34:55 CET
<?php
interface toText {
    public function text();
}

class text implements toText {
    private $_text;

    function __construct($text) {
        $this->_text = $text;
    }

    public function text() {
        return $this->_text;
    }
}

function makeText($value) {
    return new text($value);
}


/srv/http/libs/xml.php, 2021-03-12 19:09:16 CET
<?php
require_once('libs/text.php');

interface container {
    public function isempty();
}

abstract class xml implements toText {
}

class nodelist extends xml implements container {
    private $_children = array();

    public function text() {
        $text = '';
        foreach ($this->_children as $node) {
            if (!is_a($node, 'toText')) continue;
            $text .= is_a($node, 'xml') ? $node->text() : htmlspecialchars($node->text());
        }
        return $text;
    }

    public function add($node) {
        switch (gettype($node)) {
        case 'object':
            if (is_a($node, 'xml') || is_a($node, 'toText')) {
                $this->_children[] = $node;
                break;
            }
            // throw error?
            break;
        case 'array':
            foreach ($node as $subnode) {
                $this->add($subnode);
            }
            break;
        case 'string':
        case 'double':
        case 'integer':
            $this->_children[] = makeText($node);
            break;
        default:
            // throw error?
        }
        return $this;
    }

    public function isempty() {
        return empty($this->_children);
    }
}

class xmlAttribute implements toText {
    private $_name;
    private $_value;

    function __construct($name, $value) {
        $this->_name = $name;
        $this->_value = $value;
    }

    public function name() {
        return $this->_name;
    }

    public function value() {
        return $this->_value;
    }

    public function text() {
        return $this->_name . '="' . htmlspecialchars($this->_text($this->_value)) . '"';
    }

    private function _text($content) {
        switch(gettype($content)) {
        case 'array':
            $text = '';
            foreach ($content as $element) {
                $text .= $this->_text($element);
            }
            return $text;
        case 'object':
            if (is_a($this->_value, 'toText')) {
                return $content->text();
            }
        default:
            return $content;
        }
    }
}

class attributes implements toText, container {
    private $_attributes = array();

    public function text() {
        $text = array();
        foreach ($this->_attributes as $xmlAttribute) {
            $text[] = $xmlAttribute->text();
        }
        return implode(' ', $text);
    }

    public function add(xmlAttribute $xmlAttribute) {
        $this->_attributes[$xmlAttribute->name()] = $xmlAttribute;

        return $this;
    }

    public function isempty() {
        return empty($this->_attributes);
    }
}

class tag extends xml {
    private $_children;
    private $_name;
    private $_attributes;

    function __construct(string $name) {
        $this->_name = $name;
        $this->_attributes = new attributes();
        $this->_children = new nodelist();
    }

    public function add() {
        foreach (func_get_args() as $node) {
            if (is_array($node)) {
                foreach ($node as $subnode) {
                    $this->add($subnode);
                }
            } else if (is_a($node, 'xmlAttribute')) {
                $this->_attributes->add($node);
            } else {
                $this->_children->add($node);
            }
        }
        return $this;
    }

    public function set(string $name, $value = null) {
        $this->_attributes->add(new xmlAttribute($name, $value));
        return $this;
    }

    public function attributes() {
        return $this->_attributes;
    }

    public function text() {
        return
            '<' . $this->_name .
            ($this->_attributes->isempty() ? '' : ' ' . $this->_attributes->text()) .
            ($this->_children->isempty() ? '/>' : '>' . $this->_children->text() . '</' . $this->_name . '>');
    }
}

class raw extends xml {
    private $_text;

    function __construct(string $text) {
        $this->_text = $text;
    }

    public function text() {
        return $this->_text;
    }
}

function makeTag($name, $content = null) {
    $tag = new tag($name);
    if (!is_null($content)) {
        $tag->add($content);
    }
    return $tag;
}

function makeRaw($content) {
    return new raw($content);
}

function makeAttribute($name, $value) {
    return new xmlAttribute($name, $value);
}

function makeTaglist() {
    return new nodelist();
}

function glueTags($glue, array $tags) {
    $glued = array();
    if (empty($tags)) return;
    $glued[] = array_shift($tags);
    foreach($tags as $tag) {
        $glued[] = $glue;
        $glued[] = $tag;
    }
    return $glued;
}

/srv/http/libs/html_utils.php, 2020-05-16 23:50:28 CEST
<?php
require_once('libs/xml.php');
require_once('libs/css.php');

function makeMeta($name, $content) {
    return makeTag('meta')->set('name', $name)->set('content', $content);
}

function makeHttpEquiv($name, $content) {
    return makeTag('meta')->set('http-equiv', $name)->set('content', $content);
}

function makeHyperlink($path, $content = null) {
    return makeTag('a', $content)->set('href', $path);
}

function makeAnchor($id) {
    return makeTag('a')->set('id', $id);
}

function makeRelLink($rel, $uri) {
    return makeTag('link')->set('rel', $rel)->set('href', $uri);
}

function makeCSSLink($path) {
    return makeRelLink('stylesheet', $path)->set('type', 'text/css');
}

function makeIconLink($path) {
    return makeRelLink('icon', $path);
}

function makePngIconLink($path) {
    return makeIconLink($path)->set('type', 'image/png');
}

function makeFieldset($title = null, $content = null) {
    return makeTag('fieldset', array(
        is_null($title) ? null : makeTag('legend', $title),
        $content,
    ));
}

function makeDiv($class = null, $content = null) {
    return makeTag('div', array(
        is_null($class) ? null : makeAttribute('class', $class),
        $content,
    ));
}

function makeSpan($content = null) {
    return makeTag('span', $content);
}

function makeTableHead(array $items) {
    $addTh = function($tag, $item) { return $tag->add(makeTag('th', $item)); };
    return makeTag('thead', array_reduce($items, $addTh, makeTag('tr')));
}

function makeTableRow(array $items) {
    $addTd = function($tag, $item) { return $tag->add(makeTag('td', $item)); };
    return array_reduce($items, $addTd, makeTag('tr'));
}

function makeTableRows(array $items, array $transforms) {
    $makeRow = function($item) use ($transforms) {
        $applyTransform = function($trans) use ($item) { return $trans($item); };
        return makeTableRow(array_map($applyTransform, $transforms));
    };
    return array_map($makeRow, $items);
}

function makeTableBody(array $items, array $transforms) {
    return makeTag('tbody')->add(makeTableRows($items, $transforms));
}

function makeComplexTable(array $items, array $transforms) {
    return makeTag('table', array(
        makeTableHead(array_column($transforms, 'title')),
        makeTableBody($items, array_column($transforms, 'transform')),
    ));
}

function makeTable(array $items, array $transforms) {
    return makeTag('table', makeTableBody($items, $transforms));
}

function makeUrl($url, array $params, $anchor = null) {
    if (!empty($params)) {
        $encodeParams = function ($key, $value) {
            return $value === '' ? urlencode($key) : urlencode($key) . '=' . urlencode($value);
        };
        $url .= '?' . implode('&', array_map($encodeParams, array_keys($params), $params));
    }

    if (!is_null($anchor)) {
        $url .= '#' . $anchor;
    }

    return empty($url) ? '?' : $url; // link to '?' to clear current query parameters
}

function makeLink($baseUrl, $label = null, array $params = array(), $anchor = null) {
    return makeHyperlink(makeUrl($baseUrl, $params, $anchor), $label);
}

function makeAnchorLink($anchor, $label = null) {
    return makeLink('', $label, array(), $anchor); 
}

function makeExternalLink($baseUrl, $label = null, array $params = array()) {
    return makeLink($baseUrl, $label, $params)->set('target', '_blank');
}

function makeScript($content = null) {
    return makeTag('script', $content);
}

function makeScriptLink($url) {
    return makeScript()->set('src', $url);
}

function makeImg($src, $alt = null) {
    $tag = makeTag('img')->set('src', $src);
    if ($alt) {
        $tag->set('alt', $alt);
    }
    return $tag;
}

function makeListItems(array $items) {
    return array_map(function($item) { return makeTag('li', $item); }, $items);
}

function makeList(array $items) {
    return makeTag('ul', makeListItems($items));
}

function makeNumberedList(array $items) {
    return makeTag('ol', makeListItems($items));
}

function makeCssTag(css $css) {
    return makeTag('style')
        //->set('type', 'text/css') // not needed according to validator.w3.org and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style
        ->add($css);
}

function makeStyleAttribute(style $style) {
    return makeAttribute('style', $style->text());
}

function makeSpaceList(array $items) {
    return glueTags(' ', $items);
}

function makeCommaList(array $items) {
    return glueTags(', ', $items);
}

function makeSerialList($delim, array $items) {
    if (count($items) <= 1) {
        return $items;
    }
    if (count($items) == 2) {
        return makeSpaceList(array($items[0], $delim, $items[1]));
    }
    // With or without serial comma? https://en.wikipedia.org/wiki/Serial_comma
    $last = array_pop($items);
    //return makeSpaceList(array(makeCommaList($items), $delim, $last)); // no serial comma
    $items[] = makeSpaceList(array($delim, $last));
    return makeCommaList($items);
}

function makeAndList(array $items) {
    return makeSerialList('and', $items);
}

function makeOrList(array $items) {
    return makeSerialList('or', $items);
}

function makeSentence(array $items) {
    return array(makeSpaceList($items), '.');
}

function makeBraces($data) {
    return array('(', $data, ')');
}

function makeLines(array $items) {
    return glueTags(makeTag('br'), $items);
}

function makeParagraphs(array $items) {
    $br = makeTag('br');
    return glueTags(array($br, $br), $items);
    //return array_map(function($item) { return makeTag('p', $item); }, $items);
}

function makeQuote($content) {
    return array('“', $content, '”');
}

/srv/http/libs/css.php, 2016-10-16 12:21:33 CEST
<?php
require_once('libs/doc.php');
require_once('libs/style.php');

class css extends doc {
    private $_entries = array();

    public function __construct() {
        parent::__construct('text/css');
    }

    public function add($selector, $style) {
        if (!array_key_exists($selector, $this->_entries)) {
            $this->_entries[$selector] = makeStyle();
        }
        $this->_entries[$selector]->add($style);
        return $this;
    }

    public function text() {
        $text = '';
        foreach ($this->_entries as $selector => $style) {
            $text .=  $selector . '{' . $style->text() . '}';
        }
        return $text;
    }
}

function makeCss() {
    return new css();
}

/srv/http/libs/style.php, 2016-07-14 11:23:33 CEST
<?php
require_once('libs/text.php');

class styleAttribute implements toText {
    private $_name;
    private $_value;

    public function __construct($name, $value) {
        $this->_name = $name;
        $this->_value = $value;
    }

    public function name() {
        return $this->_name;
    }

    public function value() {
        return $this->_value;
    }

    public function text() {
        return $this->_name . ':' . $this->_value;
    }
}

class style implements toText {
    private $_attributes = array();

    public function add($style) {
        switch (gettype($style)) {
        case 'object':
            switch (get_class($style)) {
            case 'style':
                $this->addStyle($style);
                break;
            case 'styleAttribute':
                $this->addAttribute($style);
                break;
            }
            // throw error?
            break;
        case 'array':
            foreach ($style as $child) {
                $this->add($child);
            }
            break;
        }
        return $this;
    }

    public function set($name, $value) {
        $this->add(new styleAttribute($name, $value));
        return $this;
    }

    public function addStyle(style $style) {
        foreach ($style->_attributes as $attribute) {
            $this->addAttribute($attribute);
        }
        return $this;
    }

    public function addAttribute(styleAttribute $attribute) {
        $this->_attributes[$attribute->name()] = $attribute;
        return $this;
    }

    public function text() {
        return implode(';', array_map(function($attribute) { return $attribute->text(); }, $this->_attributes));
    }
}

function makeStyle() {
    return new style();
}

/srv/http/libs/http.php, 2017-01-21 19:54:14 CET
<?php
require_once('libs/doc.php');

class http {
    private $_headers;

    private function makeHeader($name, $params = null) {
        if (empty($params)) {
            return $name;
        }
        if (is_array($params)) {
            $params = implode(';', $params);
        }
        return $name . ':' . $params;
    }

    public function addHeader($name, $params) {
        $this->_headers[] = $this->makeHeader($name, $params);
        return $this;
    }

    private function addDocHeaders(doc $doc) {
        $this
            ->addHeader('Content-type', array(
                $doc->type(),
                'charset=' . $doc->encoding(),
            ))
            ;

        if ($language = $doc->language()) {
            $this->addHeader('Content-Language', $language);
        }

        if ($name = $doc->name()) {
            $this->addHeader('Content-Disposition', array(
                'attachment',
                //($inline ? 'inline' : 'attachment'),
                //'filename="' . $filename . '"', // unescaped, unsafe?
                //'filename=' . rawurlencode($filename), // url-encoded, for IE?
                'filename*=UTF-8\'\'' . rawurlencode($name), // special UTF-8 format supported by modern browsers, but not cURL :/
            ))
            ;
        }
        return $this;
    }

    public function render(doc $doc) {
        $content = $doc->text();
        $etag = sha1($content);
        //header('etag:' . $etag);
        //header('x-compute-time-ms:' . round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 100000) / 100) ;

        $this
            ->addHeader('etag', $etag)
            ->addHeader('x-compute-time-ms', round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 100000) / 100)
            ->addDocHeaders($doc)
            ->addHeader('Access-Control-Allow-Origin', '*')
            //->addHeader('Cache-Control', 'public, max-age=120')
            //->addHeader('Cache-Control', 'public')
            //->addHeader('Pragma', 'cache')
            ;

        foreach ($this->_headers as $header) { header($header); }

        if (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
            header('HTTP/1.0 304 Not Modified');
            return;
        }

        ob_start('ob_gzhandler');
        echo $content;
        ob_end_flush();
    }
}

function makeHttp() {
    return new http;
}

/srv/http/debug.php, 2017-05-01 18:03:21 CEST
<?php
require_once('libs/doc.php');

function makeDebuggable(doc $doc, $title, $style = null) {
    $type = $doc->type();
    if (!$style) $style = $type;

    if (array_key_exists('debug', $_GET)) {
        $_GET = array();
        require_once('debugdoc.php');
        $uri = makeUrl('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['DOCUMENT_URI'], $_GET);
        return makeDebugdoc($title)
            ->addFileBlock($title, $style, $doc)
            ->addSources()
            ->addValidators($uri, $type)
            ->addServerVars()
            ->build()
            ->highlight()
            ;
    }

    return $doc;
}

/srv/http/debugdoc.php, 2018-10-18 18:00:40 CEST
<?php
require_once('highlightdoc.php');

class debugdoc extends highlightdoc {
    protected $_debuggers = array();

    function __construct($title) {
        parent::__construct($title, '/style-debug/');
    }

    protected function addDebugger($id, $title, $tag) {
        $this->_debuggers[$id][] = array('id' => $id, 'title' => $title, 'tag' => $tag);
        return $this;
    }

    protected function makeFileBlock($title, $style, $content) {
        return makeFieldset($title, $this->makeHighlight($style, $content));
    }

    public function addFileBlock($title, $style, $content) {
        return $this->addDebugger('file', $title, $this->makeFileBlock($title, $style, $content));
    }

    protected function makeSources() {
        $blocks = array(
            makeFieldset('source files', $table = makeTag('table')),
        );
        foreach(get_included_files() as $id => $file) {
            $anchor = 'source' . $id;
            $table->add(makeTableRow(array(makeAnchorLink($anchor, $file))));
            $modified = date('Y-m-d H:i:s T', filemtime($file));
            $blocks[] = array(
                makeAnchor($anchor),
                $this->makeFileBlock($file . ', ' . $modified, 'php', file_get_contents($file)),
            );
        }
        return makeLines($blocks);
    }

    public function addSources() {
        return $this->addDebugger('sources', 'source files', $this->makeSources());
    }

    private function makeValidators($uri, $type) {
        $validators = array();
        //$validators[] = $type;
        if (in_array($type, array('text/css', 'application/xhtml+xml'))) {
            $validators[] = makeExternalLink('https://jigsaw.w3.org/css-validator/validator', 'W3C CSS Validator', array('uri' => $uri));
        }
        if (in_array($type, array('application/atom+xml'))) {
            $validators[] = makeExternalLink('https://validator.w3.org/feed/check.cgi', 'W3C Feed Validator', array('url' => $uri));
        }
        if (in_array($type, array('application/xhtml+xml'))) {
            $validators[] = makeExternalLink('https://validator.w3.org/nu/', 'Nu Html Checker', array('doc' => $uri));
            $validators[] = makeExternalLink('https://validator.w3.org/i18n-checker/check', 'W3C Internationalization Checker', array('uri' => $uri));
            //makeExternalLink('https://ssldecoder.org/', 'SSL Decoder', array('host' => $uri)),
            //makeExternalLink('https://validator.w3.org/checklink', 'Link Checker', array('uri' => $uri)),
            //makeExternalLink('https://validator.w3.org/mobile/check', 'Mobile Checker', array('docAddr' => $uri)),
            //makeExternalLink('https://html5.validator.nu/', 'html5.validator.nu', array('doc' => $uri)),
        }
        return empty($validators) ? null : makeFieldset('validators', makeTable($validators, array(function($tag) { return $tag; })));
    }

    public function addValidators($uri, $type) {
        $validators = $this->makeValidators($uri, $type);
        return $validators ? $this->addDebugger('validators', 'validators', $validators) : $this;
    }

    public function addServerVars() {
        $title = 'server variables';
        $tag = makeFieldset($title, $table = makeTag('table'));
        foreach ($_SERVER as $key => $value) {
            $table->add(makeTableRow(array(makeTag('strong', $key), $value)));
        }
        return $this->addDebugger('vars', $title, $tag);
    }

    public function build() {
        $flatten = array();
        foreach ($this->_debuggers as $id => $debuggers) {
            if (count($debuggers) == 1) {
                $flatten[] = array('id' => $id, 'debugger' => $debuggers[0]);
            } else {
                foreach ($debuggers as $num => $debugger) {
                    $flatten[] = array('id' => ($id . $num), 'debugger' => $debugger);
                }
            }
        }
        $menu = makeFieldset('debug', makeTable($flatten, array(function($debugger) {
            return makeLink('', $debugger['debugger']['title'], array(), $debugger['id']);
        })));
        $blocks = array();
        $blocks[] = $menu;
        foreach($flatten as $debugger) {
            $blocks[] = array(
                makeAnchor($debugger['id']),
                $debugger['debugger']['tag'],
            );
        }
        $this->body()->add(makeLines($blocks));
        return $this;
    }
}

function makeDebugdoc($title = 'debug') {
    return new debugdoc($title);
}

/srv/http/highlightdoc.php, 2016-04-03 16:31:18 CEST
<?php
require_once('basedoc.php');
require_once('highlight.php');

class highlightdoc extends basedoc {
    use highlight;
}

function makeHighlightdoc($title) {
    return new highlightdoc($title);
}

/srv/http/highlight.php, 2016-10-31 23:24:15 CET
<?php
require_once('libs/html_utils.php');

trait highlight {
    private $_highlight = false;

    public function makeHighlight($class, $content) {
        $this->_highlight = true;
        return makeTag('pre', makeTag('code', $content)->set('class', $class))->set('class', 'wrap');
    }

    public function highlight() {
        if (!$this->_highlight) {
            return;
        }
        require_once('libs/css.php');
        $css = makeCss()
            //->add('.hljs', makeStyle()->set('background', 'inherit'))
            //->add('.hljs', makeStyle()->set('color', 'Black'))
            ->add('.hljs-variable,.hljs-attr,.hljs-attribute', makeStyle()->set('color', 'DarkBlue'))
            ->add('.hljs-title,.hljs-selector-tag,.hljs-selector-class,.hljs-meta,.hljs-pi', makeStyle()->set('font-weight', 'bold'))
            ->add('.hljs-keyword,.hljs-name,.hljs-built_in', makeStyle()->set('color', 'DarkBlue')->set('font-weight', 'bold'))
            ->add('.hljs-value,.hljs-string,.hljs-number', makeStyle()->set('color', 'DarkRed'))
            ->add('.hljs-comment', makeStyle()->set('color', 'DarkSlateGray')->set('font-style', 'italic'))
            //->add('.hljs-function', makeStyle()->set('font-style', 'italic'))
            ->add('pre.wrap', makeStyle()->set('white-space', 'pre-wrap'))
            ;
        //$highlightLocation = '//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.8.0/';
        //$highlightLocation = '//cdn.jsdelivr.net/highlight.js/8.8.0/';
        //$highlightLocation = '//cdn.jsdelivr.net/highlight.js/9.1.0/';
        $this->head()->add(array(
            //makeCSSLink($highlightLocation . 'styles/default.min.css'),
            makeCssTag($css),
        ));
        $this->body()->add(array(
            //makeScriptLink($highlightLocation . 'highlight.min.js'),
            makeScriptLink('/highlight.pack.js'),
            //makeScript('hljs.initHighlightingOnLoad();'),
            makeScriptLink('/highlight_init.js'),
        ));
        return $this;
    }
}

validators
W3C CSS Validator
Nu Html Checker
W3C Internationalization Checker

server variables
USERhttp
HOME/srv/http
HTTP_CONNECTIONKeep-Alive
HTTP_HOSTmarcoen.dev
HTTP_ACCEPT_ENCODINGbr,gzip
HTTP_ACCEPT_LANGUAGEen-US,en;q=0.5
HTTP_ACCEPTtext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
HTTP_USER_AGENTCCBot/2.0 (https://commoncrawl.org/faq/)
REDIRECT_STATUS200
SERVER_NAMElocalhost
SERVER_PORT8000
SERVER_ADDR::1
REMOTE_PORT34550
REMOTE_ADDR::ffff:3.230.173.188
SERVER_SOFTWAREnginx/1.25.5
GATEWAY_INTERFACECGI/1.1
REQUEST_SCHEMEhttp
SERVER_PROTOCOLHTTP/1.1
DOCUMENT_ROOT/srv/http
DOCUMENT_URI/index.php
REQUEST_URI/?debug
SCRIPT_NAME/index.php
CONTENT_LENGTH
CONTENT_TYPE
REQUEST_METHODGET
QUERY_STRINGdebug
SCRIPT_FILENAME/srv/http/index.php
FCGI_ROLERESPONDER
PHP_SELF/index.php
REQUEST_TIME_FLOAT1718991768.2964
REQUEST_TIME1718991768