Boardunity & Video Forum

Boardunity & Video Forum (https://boardunity.de/)
-   Programmierung und Datenbanken (https://boardunity.de/programmierung-datenbanken-f23.html)
-   -   Tidbits Template Class (habe ich selbst gemacht!) (https://boardunity.de/tidbits-template-class-habe-selbst-gemacht-t2480.html)

Fabchan 17.10.2004 15:38

Tidbits Template Class
 
nachdem ich mich mit Smarty, Smarttemplate und Co. herumgeplagt hatte, bin ich hier im Forum auf das Thema What about Templatescripting? gestoßen und mir gefiehl der dortige Ansatz recht gut. Ich hab emich also hingesetzt und auf der Basis dieses Skripts selbst eien Template-Engine geschrieben, die sich im Endeffekt doch recht stark von diesem Skript abhebt. Nun ist es fertig und ich präsentiere stolz Tidbits Template Class 1.0, teilweise recht eng an Smarttemplate angelehnt, aber mit anderer Syntax und ohne die Möglichkeit, PHP-Code im Quelltext des Templates auszuführen. Getestet habe ich das Skript unter PHP 4.3

Vielleicht hilft es ja irgendwem, der noch ein Template-Skript sucht oder inspiriert irgendwen.

Genug gelabert, lasst einfach mal eure Meinung raus! VErbesserungsvorschläge sind natürlich jederzeit willkommen!

PHP-Code:

<?php

/*******************************************************************************
Tidbits Template Class 1.02 by Fabian Michael (fabian-michael AT gmx DOT de)
Bitte melden sie Fehler/Bugs in der Klasse an obige Email-Adresse

~ Lizenz:

§1 Tidbits Template Class ist Freeware, Verbreitung in unveränderter Form ist
erwünscht, bei Änderungen am Skript muss ebenfalls am Anfang der Datei darauf
hingewiesen werden. Ich Übernehme keinerlei Verantwortung für durch dieses
Skript entstehende Schäden.

§2 Tidbits Template Class kann innerhalb jedes Internetprojekts frei verwendet
werden, das Copyright und die Lizenz müssen am Anfang des Skriptes erhalten bleiben.

§3 Bei Verwendung dieses Skriptes innerhalb einer Software, gleich ob kommerziell
oder nicht bitte ich darum, per Email darauf hingewiesen zu werden.

*******************************************************************************/

class Template {

/*** Einstellungen ************************************************************/

    // Das Verzeichnis, in dem die Templates gespeichert sind
    
var $template_dir       'style/templates';
    
    
// Datei-Erweiterung der Templates
    
var $template_extension '.html';

    
// Sollen Templates zwischengespeichert werden, damit sie nicht jedes Mal
    // erneut geparst werden müssen?
    
var $reuse_code False;
    
    
// Verzeichnis, in dem geparste Templates gespeichert werden
    
var $cache_dir 'cache';
    
    
// In diesen beiden Arrays können Operatoren für IF-Abfragen definiert werden,
    // diese dürfen allerdings keine Leerzeichen enthalten, Steuerzeichen für reguläre
    // Ausdrücke müssen durch einen Backslash geschützt werden.
    
var $operator_searches = array('eq''ne''lt''gt''le''ge''=' '==''!=''>''<''>=''<=''<>'); // Bitte alle in Kleinbuchstaben
    
var $operator_replaces = array('==''!=''<' '>' '<=''>=''==''==''!=''>''<''>=''<=''!=');

    var 
$connector_searches = array('AND''OR''&&''||''|' '&'); // Bitte alle in Großbuchstaben
    
var $connector_replaces = array('&&' '||''&&''||''||''&&');
    
    var 
$math_operators = array('+''-''*''/''%');
    
    var 
$bool_expressions = array('true''false');
    
    var 
$valid_extensions  = array(
        
// Hier können die Funktionen definiert werden, die im Template aufgerufen
        // werden können
        
'htmlspecialchars',
        
'htmlentities'        
    
);

    
// Sollen HTML-Kommentare ebenfalls aus dem Template entfernt werden?
    
var $remove_html_comments True;
        
/*** Diese Variablen dürfen nicht verändert werden ****************************/
    
var $template_name      '';
    var 
$template_code      '';
    var 
$tvars              = array();
    var 
$output             '';
    var 
$section_searches   = array();
    var 
$section_replaces   = array();
    var 
$vars               = array();
    var 
$cache_file         '';
    var 
$template_file      '';

/******************************************************************************/
    
    // Funktion, die beim Erstellen des Template-Objekts aufgerufen wird
    
function Template($template_name) {
        
$this->template_name $template_name;
        
$this->cache_file $this->cache_dir '/' $this->template_name $this->template_extension '.php';
        
$this->template_file $this->template_dir '/' $this->template_name $this->template_extension;
    }
    
        
    
// Function, die das Ergebnis des Templates ausgibt
    
function output() {
        
// Überprüfen, ob Template existiert und entweder geparstes Template laden
        // oder neu parsen
        
if ($this->reuse_code == False OR !file_exists($this->cache_file)) {
            
$this->parse();
        }        
                    
        
// Variablen vorbereiten
        
$_obj = &$this->vars;
        
        
$_stack_count 0;
        
$_stack[$_stack_count++] = $_obj;

        
// Template ausführen
        
ob_start();
        include(
$this->cache_file);
        
$this->output ob_get_contents();
        
ob_end_clean();    

        return 
$this->output;
    }   
    
    
// Template parsen
    
function parse() {
        
$fp fopen($this->template_file'r');
        
$this->template_code fread($fpfilesize($this->template_file));
        
fclose($fp);

    
        
// PHP-Sectionen durch Platzhalter ersetzen
        
$this->template_code preg_replace('/<\?(.*?)\?>/iseU''$this->do_php_sections(\'\\1\')'$this->template_code);
        
// Kommentare
        
if ($this->remove_html_comments == true) {
            
$this->template_code preg_replace('/<!--(.*?)-->/isU'''$this->template_code);
        }
        
$this->template_code preg_replace('/\{\*(.*?)\*\}/isU'''$this->template_code);
        
// IF-Abfragen
        
$this->template_code preg_replace('/\{if (.*?)\}(.*?)\{\/if(.*?)\}/ise''$this->do_if(\'\\1\', \'\\2\')'$this->template_code);
        
// Variablen, auf die vor der Ausgabe eine Funktion angewendet wird.
        
$this->template_code preg_replace('/\{([a-zA-Z0-9_.]+):([a-zA-Z0-9_.]+)\}/ie''$this->do_extension_var(\'\\1\', \'\\2\')'$this->template_code);
        
// Variablen
        
$this->template_code preg_replace('/\{([a-zA-Z0-9_.]+)\}/ie''$this->do_var(\'\\1\')'$this->template_code);
        
// Foreach-Sektionen
        
if (preg_match_all('/\{foreach ([a-zA-Z0-9_.]+)\}/i'$this->template_code$var)) {
            foreach (
$var[1] as $tag) {            
                list(
$parent$block) = $this->var_name($tag);
                
$var_code '$'.$parent.'[\'' $block '\']';                
                
$new_code "<?\n" 
                
"\$_stack[\$_stack_count++] = \$_obj;\n" .
                
"if (!is_array(\${$parent}['$block'])) { \${$parent}['$block'] = array(); }" .
                
"\$tmp_array_keys = array_keys(\${$parent}['$block']);\n" .
                
"if (\$tmp_array_keys[0] != '0') { \$$parent"."['$block'] = array_values(\$$parent"."['$block']); }\n" .             
                
"\${$block}['THIS_LENGTH'] = sizeof(\${$parent}['$block']);\n" .
                
"foreach (\${$parent}['$block'] as \$rowcount => \$$block):\n" .
                
"\${$block}['ALTROW'] = \$rowcount % 2;\n" .
                
"\${$block}['ROWCOUNT'] = \$rowcount + 1;\n" .
                
"\$_obj = &\$$block;\n" .
                
"?>";
                
                
$this->template_code str_replace('{foreach ' $tag '}'$new_code$this->template_code);
            }
            
            
$this->template_code preg_replace('/\{\/foreach(.*?)\}/iU''<? endforeach; $_obj = $_stack[--$_stack_count]; ?>'$this->template_code);
        }
        
        
        if (
sizeof($this->section_searches) > 0) {
            
$this->template_code str_replace($this->section_searches$this->section_replaces$this->template_code);
        }
        
        
$fp fopen($this->cache_file'w');
        
flock($fp,LOCK_EX);
        
fputs($fp$this->template_code);
        
flock($fp,LOCK_UN);
        
fclose($fp);    
    }
    
/*** Funktionen des Parsers ***************************************************/
    
    // replace php-code and xml-declaration in the document, becouse they are not parsed
    
function do_php_sections($section_code) {
        
$unique_hash md5(uniqid(microtime()));
        
$replacement "[php:$unique_hash]";
        
        
$this->section_searches[] = $replacement;
        
$this->section_replaces[] = "<?='<?$section_code?>';?>";
        
        return 
$replacement;
    } 
    
    
// Variablen umwandeln
    
function do_var($tag) {
        list(
$parent$object) = $this->var_name($tag);    
        return 
'<?=$' $parent '[\'' $object '\'];?>';
    }    
    
    
// Variablen, auf die Funktionen angewendet werden umwandeln
    
function do_extension_var($function$tag) {
        if (
in_array($function$this->valid_extensions)) {
            list(
$parent$object) = $this->var_name($tag);    
            return 
'<?=' .$function'($' $parent '[\'' $object '\']);?>';
        } else {
            die(
'<strong>Template Error:</strong> Call to invalid extension in template &quot;' $this->template_file '&quot;: &quot;' $function '&quot;');
        }
    }    
        
    
// if-Abfragen in den Templates durch PHP-Code ersetzen.
    
function do_if($if_cond$if_code) {
        
$if_code stripslashes($if_code);
        
$if_code preg_replace('/\{elseif (.*?)\}/ise''$this->do_else_if(\'\\1\')'$if_code);
        
$if_code str_replace('{else}''<? else: ?>'$if_code);
        
        
$new_code '<? if(' $this->do_condition(stripslashes($if_cond)) . '): ?>' $if_code '<? endif; ?>';
        
        return 
$new_code;
    }
    
    
// Elseif in PHP umwandeln
    
function do_else_if($elseif_cond) {
        
$elseif_cond $this->do_condition(stripslashes($elseif_cond));
        return 
'<? elseif(' $elseif_cond '): ?>';
    }
    
    
    
// IF-Kondition in PHP umwandeln
    
function do_condition($condition) {
        
$condition preg_replace('/(_+)/''_'$condition); // Überflüssige Leerzeichen entfernen
                
        
$pieces explode(' '$condition);
        
$condition '';
        
        foreach (
$pieces as $count => $piece) {
            
            if (
$count != 0) {
                
$condition .= ' ';
            }
            
            switch (
True) {
                case 
in_array($piece$this->math_operators):
                    
$condition .= $piece;
                break;
                case 
in_array(strtoupper($piece), $this->connector_searches):
                    
$condition .= $this->connector_replaces[array_search(strtoupper($piece), $this->connector_searches)];
                break;
                case 
in_array(strtolower($piece), $this->operator_searches):
                    
$condition .= $this->operator_replaces[array_search(strtolower($piece), $this->operator_searches)];
                break;
                case 
in_array(strtolower($piece), $this->bool_expressions):
                    
$condition .= $piece;
                break;
                case 
is_numeric($piece);
                    
$condition .= $piece;
                break;
                case 
preg_match('/"(.*?)"/'$piece) OR preg_match("/'(.*?)'/"$piece):
                    
$condition .= $piece;
                break;
                case 
preg_match('/([a-zA-Z0-9_.]+)/'$piece):
                    list(
$parent$tag) = $this->var_name($piece);
                    
$condition .= '$' $parent '[\'' $tag '\']';
                break;
                default:
                    die(
'invalid expression found in if-condition in template-file &quot;' $this->template_name '&quot;: ' htmlspecialchars($piece));
                break;
            }
        }
        
        return 
$condition;
    }
    

    
// Variable umwandeln
    
function var_name($tag) {
        
$parent_level  =  0;
        
        while (
substr($tag07) == 'parent.') {
            
$tag  =  substr($tag7);
            
$parent_level++;
        }
        
        if (
substr($tag04) == 'top.') {
            
$obj  =  '_stack[0]';
            
$tag  =  substr($tag,4);
        } elseif (
$parent_level) {
            
$obj  =  '_stack[$_stack_cnt-'.$parent_level.']';
        } else {
            
$obj  =  '_obj';
        }
        
        while (
is_int(strpos($tag'.'))) {
            list(
$parent$tag)  =  explode('.'$tag2);
            if (
is_numeric($parent)) {
                
$obj  .=  "[" $parent "]";
            } else {
                
$obj  .=  "['" $parent "']";
            }
        }
        return array(
$obj$tag);
    }    
}

?>


Fabchan 18.10.2004 22:34

Dokumentation zu Tidbits Template Class
 
Achtung! Diese Dokumentation ist für Version 1.03 gedacht, diese ist allerdings noch nicht online

Dokumentation:

~ Grundlegende Syntax:
Variable: {variablenname}
Funktion auf Variable angewendet: {funktionsname:variablenname}

Die erlaubten Funktionen können in demm Array $valid_extensions festgelegt werden.

~ Kontroll-Strukturen:
IF-Abfragen: {if [condition]}code{/if*}
IF-Else-Abfragen: {if [condition]}code{else}code{/if*}

~ [condition]:
Eine IF-Condtion kann entweder eine einzelne Variable sein:
{if variablenname}code{/if*}
Beispiel: {if display_name}Name: {name} {/if*}

Wenn dieser Wert eine Zahl ist, kann er als solche geschrieben werden, sollte
er keine Zahl sein, muss er in doppelten oder einfachen Anführungszeichen
geschrieben werden, sonst wird er als Variable behandelt.
{if display_name eq "yes"}Name: {name}{/if*}

Wichtig ist es auch, bei allen hier aufgeführten Konditionen auf die Leerzeichen
zu achten, von denen mindestens ein zwischen zwei Elementen der IF-Konidtion stehen muss,
da der Parser sich an diesen orientiert.

FOREACH-Struktur: {foreach variablenname}code{/foreach*}
Eine Foreach-Strukur eignet sich, um ein Array auszugeben, beispielsweise, wenn
Daten aus einer Datenbank tabellarisch ausgegeben werdne sollen oder irgendeine
andere Art von Liste ausgegeben werden soll. Tidbits Template unterstützt
verschachtelte FOREACH-Strukturen. Innerhalb einer FOREACH-Struktur kann auf die
Variablen des zugehörigen Arrays durch deren Namen schnell zugegriffen werden,
auf Variablen aus übergeordneten FORACH-Strukturen oder der obersten Template-
Ebene kann per parent.variablenname und top.variablenname zugefgriffen werden.
Außerdem stehen innerhalb der FOREACH-Struktur die Variablen altrow und rowcount
zur Verfügung:
"ROWCOUNT" zählt, die wievielte Zeile der FOREACH-Struktur gerade ausgegeben wird
un startet bei 1.
"ALTROW" ist ungeraden Zeilen 0 und in geraden Zeilen 1. Diese Variable eignet
sich, um zum Beispiel abwechselnd verschiedene Farben für die Zeilen einer Tabelle
zu verwenden.

Beispiel:
<table>
{foreach users}
<tr><td class="alt{ALTROW}">{name}</td></tr>
{/foreach}
</table>

Könnte zum Beispiel folgendes erzeugen:
<table>
<tr><td class="alt0">Peter Schmidt</td></tr>
<tr><td class="alt1">Max Mustermann</td></tr>
</table>



*) Das Sternchen "*" in schließenden Strukturen {/if*}, {/foreach*} steht für jeden
beliebigen Text. Es bietet sich an, aus Gründen der Übersichtlichkeit in IF-Abfragen
folgendes zu schreiben: {if foo}Foo ist toll!{/if foo}.

~ Vergleichsoperatoren
eq, =, == Gleich
ne, !=, <> Ungleich
lt, < Kleiner als
gt, > Größer als
le, <= Kleiner gleich
ge, >= Größer gleich
=== Gleich und vom gleichen Typ

~ Verknüpfungsoperatoren
AND, &&, & Und
OR, ||, | Oder

~ Mathemathische Operatoren
+ Plus
- Minus
/ Geteilt
* Mal
% Rest von Division

~ Kommentare
{* Kommentar *}
Kommentare dienen der Orientierung für den Designer des Templates und werden
beim Parsen des Templates entfernt.

~ Includes
Andere Dateien, die sich im Template-Verzeichnis befinden, können per
{include "dateiname.html"} oder {include 'dateiname.html'}
eingebunden werden. Diese werden anschließend so behandelt, als wären sie ganz
normaler Template-Code

~ Verwendung in PHP

Beispieltemplate seite.html:
<html>
<head>
<title>{htmlspecialchars:title}</title>
</head>
<body>
<table>
{foreach users}
<tr><td>{name}</td></tr>
{/foreach}
</table>
</body>
</html>

PHP-Code dazu:
<?php

require 'class.template.php';

$page = new Template('seite');

$page->vars['title'] = 'Benutzertabelle "Administratoren"';

$page->vars['users'] = array(
array('name' => 'Peter Schmidt'),
array('name' => 'Max Mustermann')
);

echo $page->output();

?>

~ Beschränkungen
Das Ausführen von PHP-Code ist innerhalb eines Templates nicht möglich, beim
Parsen eines Templates werden alle PHP-Sektionen entfent und nach dem Parsen
wieder eingefügt. Hiervon ist auch die XML-Deklaration betroffen, die vom PHP-
Parser fälschlicherweise als PHP erkannt wird und daher in PHP-Dokumenten
normalerweise einen Parse-Error erzeugt!

~ Changelog

v1.03
* Neuer Parser für IF-Conditionen:
* Leerzeichen in von Anführungszeichen eingeschlossenen Strings führen nicht mehr zu Fehlern
* Parser erkennt nun geschützte Anführungszeichen (\" und \') in Strings
* Vergleichsoperator '===' hinzugefügt
* Beim Schreiben von IF-Konditionen ist man nicht mehr an Leerzeichen zwischen
den Elementen gebunden, außer bei bei den Vergleichsoperatoren, die aus
Buchstaben bestehen ('eq', 'ne', ...). Bei diesen Operatoren muss jeweils
davor und danach ein Leerzeichen stehen, damit sie nicht als teil eines Strings interpretiert werden!
* Probleme mit der Variable THIS_LENGTH in FOREACH-Schleifen beseitigt
* Probleme mit verschachtelten IF-Abfragen beseitigt
* Include-Syntax {include 'dateiname.html'} hinzugefügt
* Superglobale innerhalb eines Arrays sind nun möglich. Wenn diese Variablen innerhalb
einer FOREACH-Schleife aufgerufen werden, müssen sie nicht mehr mit top.VARIABLENNAME
oder parent.VARIABLENNAME aufgerufen werden, sondern können einfach per VARIABLENNAME
benutzt werden.
* die Variable ROWCOUNT fängt nun bei 0 an zu zählen, ROW_ID ersetzt die bisherige Funktion
von ROWCOUNT

v1.02a
* Möglichkeit hinzugefügt, beim Laden eines Templates automatisch Variablen aus
einem globalen Array zu laden.

v1.02
* Einige Fehler beseitigt und reguläre Ausdrücke teilweise ersetzt/geändert
* Probleme mit den Variablen ROWCOUNT und ALTROW bei mehreren FOREACH-Schleifen innerhalb eines Templates beseitigt
* Diverse Fehler behoben

v1.01
* Abwärtskompatiblität aufgebessert, indem die Funktion "file_get_contents()" durch
Funktionen ersetzt wurde, die auch in älteren PHP-VersionenVErfügbar sind.
* 'elseif' innerhalb von IF-Strukturen hinzugefügt.

v1.00b
* Nun sind auch erweiterte IF-Abfragen mit den Connectoren AND und OR möglich
* Fehler in Dokumentation beseitigt

v1.00a
* Beim abspeichern von gecachten Dateien werden Dateien nun gesperrt
* Kommentarfunktion hinzugefügt, optional können auch HTML-Kommentare entfernt werden

v1.00

LonelyPixel 18.10.2004 23:07

Mich würden mal Performance-Messungen interessieren zu deiner Template-Klasse. Ich bin für das BlackBoard noch auf der Suche nach einer Template-Klasse, hab's damit aber nicht eilig.

Außerdem hab ich ein paar kleinere mögliche Problemfälle in dem Skript gefunden:
* Das Entfernen der PHP-Teile könnte den PHP-Code zerstören, weil beim preg_replace() die ' und " im \1 teilweise ge-\-t werden.
* Den foreach-Code hab ich jetzt auf Anhieb nicht kapiert, aber bei if/else bin ich mir ziemlich sicher, dass man die nicht verschachteln kann. Erweiterte if/else/else... (als switch-Ersatz) wären dann nicht möglich. Weiß nicht, inwieweit das notwenig werden könnte.

Die Klasse funktioniert nur ab PHP 4.3+, mindestens wegen dem file_get_contents(), das ist mir gleich aufgefallen. Ein join('', file()) schafft da Abhilfe. Ansonsten müsste der Code auch ab PHP 4.1 oder sogar früher laufen.

Der Code ist schön kompakt und scheint doch die Grundlegenden Template-Funktionen bereitzustellen (hab's noch nicht getestet). Wenig Code -> schnelleres Einlesen. ;)

MaMo 19.10.2004 08:58

Hi.

Nett, nur für mich leider 1 Woche zu spät, bin gerade dabei meine Templates mit meinem System einzubauen :(

Wegen dem file_put_contents() verweise ich hierdrauf: http://www.webstyleboard.de/wsb/thre...?threadid=4823 alternativ können Funktionen von PEAR::Compat benutzt werden.

MfG MaMo

Fabchan 20.10.2004 22:56

Danke für eure Bewertungen, ich werde in den nächsten Tagen das mit dem file_get_contents ausbessern und die Möglichkeit hinzufügen, else ifs einzufügen.

@LonelyPixel: An die Sache mit den PHP-Sektionen hatte ich auch erst gedacht, aber da die ge-\-ten Argumente vom Template-Parser in PHP-Strings geschrieben werden, stellt das kein Problem dar, denn wenn die Strings noch nicht in diesem Format wären, dann müssten sie sowies umgewandelt werden, um keinen Parse-Error zu erzeugen. Ich denke aber darüber nach, das ersetzen der PHP-Sektionen nicht mehr mit str_replace, sondern einfach als <?='<? PHP-Code';?> zu machen, das ist sicher performanter.

Die Template-Klasse ist sicherlich sehr schnell, wenn reuse_code auf True gesetzt wird, weil das Template als PHP-Code ausgeführt wird und Variablen nicht per Funktion, sondern per Array zugewiesen werden. Dann muss das Template nur ien einziges Mal in PHP-Code umgewandelt werden und wird von dort an nur noch als PHP-Code ausgeführt, ohne reguläre Ausdrücke! Trotzdem wäre ein solcher Performance-Test sicher recht interessant, hat jemand iene Ahnung, wie man sowas machen könnte?

LonelyPixel 21.10.2004 10:15

Hm, hab sowas noch nicht gemacht, aber mit microtime() bekommst du ne sehr genaue Zeitmessung und dann müsstest du einfach nur die Ausführungszeit (compile + run from cache) mit verschiedenen Systemem messen. Mit einer vergleichbaren Template-Seite natürlich.

Fabchan 24.10.2004 11:26

Update auf Version 1.02!

Arbeite übrigens zurzeit an einem Persormance-Test!

LonelyPixel 20.12.2004 22:41

Gibt's schon Neues zu dem Test? Ich würd in den nächsten Monaten eventuell schon anfangen, eine Template Engine zu verbauen...

Fabchan 21.12.2004 13:25

Achso!

Ja, ich habe das Skript inzwischen für meine Galerie-Software weiterentwickelt.

Ich habe schonmal die Dokumentation für die aktuelle Version gepostet, das Skript kann ich in den nächsten Tagen nachliefern.

Bei meiner Galerie-Software habe ich auf Seiten, die nur ein Template enthalten, bisher die Erfahrung gemacht, dass selbst wenn man die Templates nicht zwischenspeichert, die Ausführungzeiten recht kurz sind. Sie steigen allerdings enorm, wenn mann sehr viele Templates ohne Pause hintereinander parst, was in der Praxis allerdings nie vorkommen wird, weil man sich durch Includes ein entsprechendes Template zusammenbasteln kann, sodass man nur eins pro Seite benötigt.

Einen kleinen Test habe ich bereits unternommen, das Ergebnis lässt sich unter
http://www.draconum.de/files/tidbits_template_test.html (ca 900 KB)
abrufen.

Wenn Interesse besteht, kann ich in den nächsten Tagen mal eine allgemein einsatzbare Datei für v1.03 zusammenstellen, denn bis meine Galerie-Software einsetzbar sein wird, kann es noch dauern.

Gast 21.12.2004 19:40

Ich als Unwissender wollte mal fragen, ob das bisschen Code was du im ersten posting geschrieben hast wirklich alles ist. An welcher Stelle werden denn da beispielsweise die if ( ) und foreach Sachen umgewandelt?

Fabchan 21.12.2004 19:51

@archibald:

Die IF-Abfragen werden durch einen Funktionsaufruf umgewandelt
PHP-Code:

$this->template_code preg_replace('/\{if (.*?)\}(.*?)\{\/if(.*?)\}/ise''$this->do_if(\'\\1\', \'\\2\')'$this->template_code); 

Die oben gepostete Version sollte allerding snoch nicht produktiv eingesetzt werden, weil sie noch einige offensichtliche Fehler enthält. Zum Beispiel kann man in Stings innerhalb von IF-Abfragen keine Leerzeichen verwenden. Inzwischen arbeite ich an einer aktualisierten Version. Die nächsten Tage werde ich diese veröffentlich.

Schau einfach im Changelog des Skripts bei v.103 nach, dort findest du die entsprechenden Neuerungen. Ich plane auch, die Variablen in Zukunft durch ein "$"-Zeichen zu kennzeichnen, um den Zugriff auf Konstanten zu ermöglichen. Außerdem ist ein "Phrase"-modul geplant, um die Verwendung von mehrsprachigen Templates zu unterstüzen. Des weiteren ist auch noch eine "eval"-Funktion geplant, um Variablen als Template-Code parsen zu können.

EDIT:

Das würde bedeuten, dass Variablen von nun an in folgender Schreibweise notiert werden würden:

$variablenname (= variablenname)
oder
$variablenname.schlüssel (= variablenname['schlüssel'])

Ein weiteres Ziel ist, Erweiterungen (Funktionen) auch für Variablen innerhalbvon Funktionen benutzen zu können:

Hierfür gäbe es folgende Möglichkeiten:

erweiterung($variablenname)
oder
variablenname.erweiterung([paramter1[, parameter2]])

Wobei ich eher mit der zweiten Variante liebäugle. Welche würdet ihr bevorzugen?

Ein weiteres Problem beim Template-Caching ist, dass in Version 1.03 zwar das Haupttemplate jedes Mal neu geparst wird, wenn die Template-Datei geändert wurde, allerdings nicht die Includes, falls nur deren Dateien geändert wurden, daher werde ich in Zukunft wohl darauf setzen, dass auch diese überprüft werden.

"Phrase-Modul:"

Einfacher Ausruck:
{$phrase.phrasename}

Ausdruck mit Parametern:
{phrase[ 1="wert1"[, 2="wert2"]]}phrasename{/phrase}


Ihr seht also: es lohnt sich, noch ein wenig zu warten. In den Weihnachtsferien sollte das zu schaffen sein.

An dieser Stelle habt irh auch noch die Möglichkeit, weitere Wünsche zu äußern odr mir zu sagen, was ihr an "Tidbits Template Engine" für Quatch haltet!

LonelyPixel 22.12.2004 16:07

Hier sind meine Anmerkungen dazu:

* Schreib doch bitte ein Datum zu den Versionen ins Changelog mit rein.

* Ich fände eine XML-Syntax wie <if cond="...">...</if> interessant. Ich glaub das vB verwendet diese Schreibweise, hab sie jedenfalls mal irgendwo gesehen. Dann könnte man auch gleich HTML-Kommentare rauswerfen (aufpasse auf die IE conditional comment hacks, vgl. IE7). Meinungen?

* ALTROW bei foreach würde ich genau andersrum definieren, dann stimmt es mit der modulo-Operation überein. Also 0 für gerade und 1 für ungerade. Und das ROW_ID/ROW_NUM finde ich etwas verwirrend. IMO war die vorherige Lösung OK, und wer bei Null anfangen will, kann ja eins abziehen.

* Die Syntax der foreach-Schleifen hab ich nicht verstanden. Wie kann ich da auf das jeweils aktuelle Element zugreifen? Z.B. bei {foreach users} mit users = array(name, id, location), wie kann ich diese drei Werte in der Schleife verwenden? Einfach nur mit {name}? Dazu müssten Variablen in foreach-Schleifen ja anders interpretiert werden als woanders. Und die Verschachtelung wird sicher eine ziemliche komplexe Sache... Wenn man da wie in PHP einfach Namen für die Laufvariablen geben könnte, wär es sicherlich einfacher.

* Du solltest deine UND/ODER/XOR Operatoren nach logisch (&&) und bitweise (&) trennen.

Ansonsten werd ich mir den Code dann demnächst mal anschauen, wie einfach ich ihn bei mir verbauen kann und wie es bei mir mit der Performance aussieht.

Fabchan 22.12.2004 20:06

@LonelyPixel: Gute Argumente, werd emir das ganze Mal zu Herzen nehmen, besonders mit den Bit-Operatoren hatte ich das sowieso vor, weil meine Galerie-Software Bitfelder für Berechtigungen benutzen wird und dafür braucht man nunmal Bit-Operatoren, wenn das Ganze teilweise Template-Gesteuert werden soll.

Eine komplette XML-Syntax wäre natürlich eine überlegung wert, wobei ich noch nicht wüsste, wie ich es dann mit den Variablen machen sollte.

Entweder irgendwas von dem
<var name="variablenname" />
<var>name</var>
<var name="variablenname" function="funktion" 1="argument1" />

oder:
{$variable}
{$variable.funktion()}

mir geällt bei einer XML-Syntax nur nicht, dass ich nicht einfach
<if $a==b>[...]<else />[...]</if>
schreiben kann, sondern in dem fall
<if cond="$a == $b">[...]<else />[...]</if>
schreiben müsste. Wobei das bei IFs und FOREACHs noch zu ertragen wäre, ich hätte nur keine Lust, jedes Mal bei einer Variable <var name="varname" /> zu schreiben, daher werde ich für die Variablen wohl auch weiterhin die geschweiften Klammern verwenden. Man Könnte diese natürlich auch optional ohne die Klammern schreiben. Auch werde ich bei den Variablen bei der Objekt-Schreibweise bleiben. Denn diese ist ein wichtiger Bestandteil der Template-Engine und ist in diesem Falle für mich überzeugender, als die PHP-Schreibweise für Arrays, denn nur mit dem objektorientierten Ansatz kann man so schöne Sachen wie $parent.varname machen.
Das Dollarzeichen ist notwendig, um Variablen von Konstanten unterscheiden zu können.

Aber erstmal folgende Lösungen für Strukturen:
<foreach var="$varname"></foreach>
<if cond=""></if>
<if cond=""><elseif cond="" /><else /></if>
<include file="$string">

Sprachvariablen:
{$phrase.name} (Sprachvariablen ohne Parameter)
<phrase 1="$arg1" 2="arg2">{$phrase.name}</phrase>
Diese schreibweise ermöglicht das Einbinden von Variablen in die Sprachvariablen,
wenn ein Argument nicht von einem "$"-Zeichen angeführt wird, dann wird es als Wert, statt als Variable betrachtet, damit schließt man allerdings aus, dass Konstanten in Sprachvariablen verwendet werden können, dass sollte meiner Meinung nach aber nicht allzu schlimm sein!

Normale Variablen:
{$variable}
{$variable.htmlspecialchars()}

Was haltet ihr von der Syntax?

LonelyPixel 22.12.2004 21:49

Zu der Lokalisierungssache kann ich nicht viel sagen, da ich mir die wahrscheinlich weiterhin in der Anwendung zusammensetzen werde. Dafür ist die Logik stellenweise zu komplex. Und wenn, bei mir ist das nur ein Text-keyed Array, und wenn überhaupt, kann man dafür sonst nur eine normale Funktion einsetzen. Was muss eine Template Engine hierbei besonderes können?

Aber zur XML-Syntax: Wenn, dann hieße es <include file="..." />, da es hier kein schließendes Tag gibt. :D

Das mit den Variablen ist natürlich zu lang. Hier wäre evtl. ein Mischbetrieb sinnvoll. Wobei eine fully bloated XML-Syntax natürlich immer mehr Schreibarbeit bedeutet, da hast du auch recht. Also vielleicht doch nicht so gut?

Für Funktionsaufrufe: Warum machst du nicht einfach {funktion var1 var2 "var3"}? (Berechnungen kommen dann in Klammern.) Dass funktion eine Funktion ist, lässt sich rausfinden, und Variablen mit gleichem Namen kann es nicht geben. (Das sag ich jetzt mal so...) Damit wäre die Sachlage eindeutig. Wozu brauchst du eigentlich Konstanten? Willst du da Textschnipsel mit Inhalten füllen oder ne halbe Programmiersprache mitsamt aller mehr oder weniger sinnvollen Sprachelemente nachbilden? Halte es so einfach wie möglich und code nur das nötigste, um alle erforderlichen Funktionen durchzuführen. Das macht es schneller, kleiner und besser wartbar.

Fabchan 22.12.2004 23:41

Berichtigung
 
@LonelyPixel:

Zu den Funktionen: Ich liebäugele halt mehr mit der "$variable.funktion()"-Variante, weil sie eine besser standartisierte Schreibweise ermöglicht, denn manchmal können Funktionen auch innerhalb von IF-Konditionen nützlich sein, zum Beispiel, wenn man
<if condition="$variable.strlen() > 3"></if>
haben will, kann man die gleiche Schreibweise verwenden, wie bei Variablen, die man einzeln ausgibt:
{$variable.strlen()}

Zum Thema Konstanten:
Ich habe mich gefragt, warum es eigentlich so ein wahnsinniger Aufwand ist, Einstellungen in MySQL zu speichern. Du brauchst für jede Option eine eigene Spalte, das belastet nicht nur die Datenbank, sondern macht auch viel Schreibarbeit.
Dann habe ich mir mal den Quellcode vom vBulletin3 angeschaut. Die Leute, die es programmiert haben, muss ich echt loben. Auch wenn sie anscheinend nicht viel von OOP halten, so muss ich doch sagen, dass sie sich ordentlich Gedanken um dieses Problem gemacht haben.
Und dieses haben es mit Bitfeldern gelödt, ähnlich den Dateiattributen bei Unix.

Ein Beispiel:
PHP-Code:

$_BITFIELD = array(
 
'ausfuehren' => 1,
 
'schreiben' => 2,
 
'lesen' => 4
);

/* Um nicht jedes Mal die Variable ausschreiben zu müssen, definieren alle Werte von $_BITFIELD als Konstenten */
foreach ($_BITFIELD as $key => $val)
{
  
define(strtoupper($key), $val);
}

$attr =  $_BITFIELD['ausfuehren'] + $_BITFIELD['lesen']; // = 5

if ($attr SCHREIBEN// Gibt "False", weil schreiben-Attribut nicht gesetzt 
{
  echo 
'kann schreiben';
}
else
{
  echo 
'kann nicht schreiben';


Diese Methode erspart einem nicht nur viel Programmieraufwand, sie spart auch Ressourcen, weil weniger Speicherplatz und Belastung für MySQL.

Und da ich inzwischen in meinen Skripten auch auf diese Methode setze, sind für meinen privaten Zwecke Konstanten in Templates unumgänglich. Berechtigungen werden zwar größtenteils im "richtigen" PHP-Code bearbeitet, aber bei manchen Optionen braucht man diese Möglichkeit auch in den Templates.

LonelyPixel 23.12.2004 10:31

Ja, OK, so furchtbar genial ist die Bitmasken-Idee nun auch nicht. Ich würd ihren Ursprung mal so grob in die 70er Jahre schieben. Ich verwende sie ja auch teilweise. Aber trotzdem wär ich irgendwie dafür, Variablen ohne das $-Zeichen zu beschreiben. Da mehr Variablen vorkommen, sollte das vereinfacht werden. Kannst du nicht ein %-Zeichen (oder eins, das noch frei ist, # z.B.) vor Konstanten setzen, oder was in der Richtung?

Fabchan 23.12.2004 12:28

Klar, das wär auch ne Idee, wenn man das Machen wollte, dann müsste man aber ein Zeichen nehmen, dass bisher noch nicht in den Formal vorkommt und trotzdem passend ist, daher fällt das "%"-Zeichen schonmal raus. Als Favoriten bleiben bei mir dann
"#", "§", "$" oder "?" übrig. Man kannes natürlich auch als frei definierbar wählen.

Mir gefiel halt das $-Zeichen vor Variablen, weil es der PHP-Schreibweise ähnelt und somit für die Leute, die das Skript einsetzen werden, keine umgewöhnung ist. Bin mir aber selbst noch nicht so ganz sicher, wie ich das nun machen werde...

LonelyPixel 23.12.2004 12:52

Templates sind ja dafür da, dass grade nicht-PHP-Programmierer das Aussehen einer Oberfläche verändern können. Wir sollten also mal nicht zu viel PHP/Perl-Wissen voraussetzen. Das $ könnte auch daher so manchem seltsam anmuten. Ich kenn mich jetzt aber mit anderen Template Lösungen nicht so aus, vielleicht ist das ja doch Standard so?

Mit dem §-Zeichen weiß ich net so recht. Das hat bei mir mal Probleme mit verschiedenen Codierungen verursacht, wohl weil es im Extended ASCII Bereich liegt. Und es sieht nach Gesetzen aus... ;)

bacon 01.01.2005 00:33

Hallo zusammen,
dies ist mein erster Beitrag hier. Ich bin ebenfalls über diesen link hier hineingestolpert, da ich mich auf einem Google-Kreuzzug nach Algorithmen, die sich zum Parsen von Dokumenten/Baumstrukturen bewährt haben, befinde.

Wie auch immer, interessehalber habe ich mich durch diesen und den älteren Thread mal gehangelt und auch das Script einmal ausprobiert (die Syntax – ebenfalls wie die von Smarty – finde ich wohl grauenhaft :D... ist wohl Geschmackssache). Viel gravierender fand ich, dass folgender Codeschnippsel im Template:
PHP-Code:

{if test eq "Hallo"}
    
Test ist Hallo
    
{if test eq "Hallo"}
        
Test2 ist Hallo2
    
{/if*}
{/if*} 

mit folgenden Variablen gefüttert:
PHP-Code:

<?php
require 'class.template.php';
$page = new Template('seite');

$page->vars["test"]="Hallo";
$page->vars["test2"]="Hallo";

echo 
$page->output();

?>

Folgende Ausgabe produziert:

Code:

Test ist Hallo {if test eq "Hallo"} Test2 ist Hallo2 {/if*}
Versuche doch als Tipp einfach mal, nicht jedes Einzelelement zu regexen, das wird bei komplexen Strukturen ja der blanke Horror. Splitte vielmehr die Blockelemente allgemein, indem du nach Open-Tags, Close-Tags und dem "Rest" (meinethalben CDATA) suchst und diese einfach auch nen Stack wirfst.

Willst du – wie vorgeschlagen – auf eine Art XSL-Syntax umsteigen (finde ich auch ganz hübsch), so kannst du ja sogar den eingebauten PHP-XML Parser verwenden, um deinen kompletten Baum in ein Array zu werfen, welches du dann nur noch auswerten musst. Du kannst deine "Spezialelemente" ja dann mit einem Namespace versehen:
Code:

<TIDBITS:IF condition="var eq 'test'">
o.Ä.

Viel Spaß beim Knobeln!!!!

LonelyPixel 01.01.2005 02:10

Du suchst Parser-Algos? Schau doch mal bei meinem Advanced BBCode vorbei. Das ist eine (leider recht veraltete) Version dessen, was ich in meinem Board verbaut hab, um BBCode-Tags (bislang) fehlerfrei zu erkennen.

Was mich auf die Idee bringt, dass ich ja eigentlich auch meine eigene optimierte Parser-Engine für Templates hernehmen könnte... Templates für ein Forum in BBCode verfasst... Das hätte doch was? ;)

Und die Sache mit dem XML von PHP: 1. würde das IIRC PHP5 erfordern, und 2. noch dazu eine grammatikalisch korrekte Vorlage des Template-Authors, richtig? Das wird dann doch etwas fehleranfällig. (Andererseits ist eine korrekte XML-Syntax sowieso erstrebenswert, wenn nicht gar erforderlich.)

bacon 01.01.2005 09:42

:eek: direkt eine Antwort :D
Richtig, ich bin davon auch wieder abgekommen, aber aus einem anderen Grund: Viel zu viel Tipparbeit (ich verwende bspw. meist auch lieber schnell mal'n Perlscript, als ein XSL-Sheet, da schreibt man sich ja die Finger wund...). Die XPat-Funktionen (SAX-Parser) sind ja auch bereits in der PHP 4 Distribution vorhanden. Dass die Quelle wohlgeformt sein muss sehe ich auch eher als Vorteil, ist aber vor allem Geschmacks- und Gewöhnungssache.

Ich habe für meinen eigenen Templateparser immerhin erst einmal das (abgeleitete) Prinzip des von PHP bereitgestellten XML-Array geklaut (das, was die Methode xml_parse_into_struct() zurückliefert). Aber vielleicht finde ich hinter deinem Link ja noch Besseres? Danke jedenfalls!!!

Fabchan 04.01.2005 01:49

Liste der Anhänge anzeigen (Anzahl: 1)
So, hier ist mal ein neuer Anfang, die Dokumentation ist leider noch sehr schlecht und es stecken wahrscheinlich noch sehr viele Fehler drin, aber schaut es euch einfach mal an und sagt, was ihr davon haltet!

LonelyPixel 04.01.2005 07:04

Hm, die XML-Schreibweise hat irgendwie noch einen kleinen Nachteil, fällt mir grade auf: Es sieht hässlich aus, wenn man Bedingungen oder Funktionen innerhalb von HTML-Elementen verwenden will.

Fabchan 04.01.2005 11:21

@LonelyPixel: Das stimmt natürlich, das Problem lässt sich allerdings lösen, indem man einfach die Funktion "iif" (wird in template.php definiert) erlaubt und diese mit den entsprechenden Parametern in geschwungene Klammern schreibt:
<a href="test.php"{iif(setting.opennew, ' target="_blank"')}>

Damit wirft du zwar immer noch die XML-Kopatiblität über Board, aber diese ist in Templates sowieso letztendlich nichts als reine Illusion.
Die Funktion "iif" werde ich in der nächsten Version von vornherein erlauben. Ein letztes Problem bleibt allerdings bei Phrasen innerhalb von HTML-Elementen, dort muss man nach wie vor HTML in HTML-Elementen schreiben oder
"tt_construct_phrase" mit dem Alias "phrase" erlauben und folgendes schreiben:
<img src="" alt="{phrase(pharse.imgalt, filename, 'param2' [,...])}" />

Wie gesagt, TT ist da sehr variabel!
Wie ist sonst euer Eindruck von der überarbeiteten Version?

PS: Die Klassen habe ich weggelassen, weil es manchmal sehr nervig sein kann, wenn man mal mehrere Templates innerhalb einer Datei braucht und man jedes Mal eine neue Klasse erstellen muss, in der dann letztendlich nur eine Funktion aufgerufen wird, bis sie wieder Geschichte ist! Daher habe ich mich jetzt für eine non-OOP-Lösung entschieden, das AFAIK schneller und IHMO praktischer ist.

bacon 04.01.2005 11:32

Weiß nicht... kommt mir immer noch ein wenig unausgegoren vor, v.A. die riesigen RexExprs... das ganze scheint mir nicht so sehr flexibel zu sein in der Hinsicht, neue Funktionen einzufügen.

Was machst du bspw., wenn du Schleifen implementierst? Dann musst du doch sowieso (Beispiel: <while condition="[..]"> [...] </while>) den mittleren Anweisungsblock wiederholen, ohne, dass du u.U. im Vorfeld die Stop-Bedingung kennst. Hier kommst du ohne Rekursionen gar nicht aus...


Alle Zeitangaben in WEZ +1. Es ist jetzt 23:33 Uhr.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25