Boardunity & Video Forum

Boardunity & Video Forum (https://boardunity.de/)
-   Programmierung und Datenbanken (https://boardunity.de/programmierung-datenbanken-f23.html)
-   -   What about Templatescripting? (https://boardunity.de/what-about-templatescripting-t1463.html)

chief 28.02.2004 13:57

What about Templatescripting?
 
Hallo und Hi,

kennt jemand ein paar Seiten, wo es Tutorials für Templatescripting gibt? Google gibt nix her. ;)
Ich hab das jetzt schon in einigen Boardsoftwares gesehen und frage mich, wie man so etwas umsetzen kann.

Mir kam schon der Gedanke mit regulären Ausdrücken. :confused:

Naja, vielleicht kann mich ja jemand aufklären!

Danke im voraus! :)

exe 28.02.2004 14:59

Reguläre Ausdrücke sind schon das richtige Stichwort.
Du musst dir Ausdrücke schreiben die auf die anvisierte Scripting Syntax passen. Mit diesen holst du dir die scripting Anweisungen aus dem Template, wertest sie aus und schreibst das Ergebniss ins Template zurück. Anstatt die scripting Anweisungen ins Template zurückzuschreiben könntest du sie beispielsweise auch in PHP Anweisungen übersetzen und aus dem Template ein PHP Script erzeugen. Der Vorteil ist das du dann das Template danach nur noch via include(); einbinden musst was einige Geschwindigkeitsvorteile mit sich bringt.
Ich hab mal schnell eine simple Klasse als Beispiel geschrieben mit der du ein einfaches IF-Konstrukt auswerten kannst. Die Syntax im Template lautet

{if Bedingung}
HTML-Code der angezeigt werden soll wenn die Bedingung wahr ist.
{/if}

Bedingung ist eine normale Bedingung in der von PHP gewohnten Syntax wie '$a == $b' oder '!$b'.
Ich hab die Klasse ausführlich kommentiert, trotzdem solltest du dir die Syntax der regulären Ausdrücke in PHP anschauen.
Die Klasse:

PHP-Code:

<?PHP
class template
{
    var 
$template_code null;    // In dieser Variable wird der HTML-Code des Templates zwischengespeichert.
    
    /**
     * Der Klassenkonstruktor der den HTML-Code welcher
     * via Parameter uebergeben wird zwischenspeichert.
     **/
    
function template($template_code)
    {
        
$this->template_code $template_code;
    }
    
    
/**
     * Parsen des HTML-Codes mit regulaeren Ausdruecken.
     **/
    
function parse()
    {
        
/**
         * Der Ausdruck sucht nach IF-Konstrukten mit der Syntax {if Bedingung} HTMl-Code{/if}
         * Der 's' Modifier gibt an das der Ausdruck ueber mehrere Zeilen passen soll, 'i' das 
         * Gross-/Kleinschreibung ignoriert wird und 'e' das der Ersetzungscode als PHP ausgefuehrt wird
         * und mit dem Ergebniss ersetzt wird. Das bedeutet das bei einem Treffer des Ausdrucks die Funktion
         * $this->do_if() aufgerufen und als ersten Parameter die Bedingung des IF-Konstrukts und als zweiten
         * Parameter den Code zwischen den {if}...{/if} Tags uebergeben bekommt.
         **/
        
$this->template_code preg_replace('/\{if (.*?)\}(.*?)\{\/if\}/ise''$this->do_if(\'\1\', \'\2\')'$this->template_code);
        
        
/**
         * Geparstes template zurueckgeben.
         **/
        
return $this->template_code;
    }
    
    
/**
     * Wertet eine IF-Abfrage aus dem Template aus.
     * Der erste Parameter enthaelt die Bedingungen der Abfrage,
     * der Zweite den HTML-Code der ins Template zurueckgeschrieben wird wenn
     * die Auswertung 'wahr' ergibt.
     **/
    
function do_if($if_cond$if_code)
    {
        
extract($GLOBALS); // Globale Variablen verfuegbar machen
        
        /**
         * Die Bedingung der Abfrage wird einfach als PHP evaluiert.
         * Das bedeutet aus der Bedingung '$a == $b' wird 
         * $if_result = $a == $b; womit das Ergebniss der Bedingung (true/false)
         * in $if_result gespeichert wird.
         **/
        
eval('$if_result = '.stripslashes($if_cond).';');
        
        
/**
         * Ueberprufen ob $if_result true oder false ist.
         * Falls true wird der HTML-Code zwischen den {if}..{/if} Tags
         * zurueckgegeben. Falls false wird ein leerer String zurueckgegeben
         * was das IF-Konstrukt aus dem Template entfernt.
         **/
        
if($if_result) {
            return 
stripslashes($if_code);
        } else {
            return 
'';
        }
    }
}
?>

Aufrufen kannst du die Klasse beispielsweise folgendermaßen:

PHP-Code:

<?PHP
include './template.class.php';

$a 'foo';
$b 'bar';

$template_code '<html>
<head>
<title>Test</title>
</head>

<body>

{if $a == "foo"}
Variable a hat den Wert "foo"
{/if}

</body>
</html>'
;

$template = new template($template_code);
echo 
$template->parse();
?>

Vielleicht hilft dir das ja um ein bisschen in das Prinzip einzusteigen :)
Echte Tutorials zu dem Thema kenne ich nicht, dass beste Tutorial lautet wohl "Reguläre Ausdrücke" und findet sich im PHP-Manual ;)

chief 28.02.2004 17:50

Vielen vielen Dank. Ich werde es mich heute Abend damit mal auseinandersetzen. =)

MaMo 24.03.2004 11:48

Hi.

Geht dieses
Code:

                /**
                * Die Bedingung der Abfrage wird einfach als PHP evaluiert.
                * Das bedeutet aus der Bedingung '$a == $b' wird
                * $if_result = $a == $b; womit das Ergebniss der Bedingung (true/false)
                * in $if_result gespeichert wird.
                **/
               
eval('$if_result = '.stripslashes($if_cond).';'
);
               
               
/**
                * Ueberprufen ob $if_result true oder false ist.
                * Falls true wird der HTML-Code zwischen den {if}..{/if} Tags
                * zurueckgegeben. Falls false wird ein leerer String zurueckgegeben
                * was das IF-Konstrukt aus dem Template entfernt.
                **/
               
if($if_result
) {
                        return
stripslashes($if_code
);
                } else {
                        return
''
;
                }

auch mit Perl? Wäre mir was neues...

MfG MaMo

Patrick Gotthardt 24.03.2004 13:26

Zum Thema Template-Scripting in PHP habe ich ein kleines Tutorial geschrieben:
klick

@MaMo: Ich würde das ganze mit Regulären Ausdrücken in Perl umwandeln, und diesen dann in deinen Code einbinden (sowas wie include bei PHP).
Das ist von der Performance her auch besser.
Wenn perl sowas wie eval hat geht es aber auch damit. Ist aber in jedemfall langsamer.

MaMo 24.03.2004 15:41

Zitat:

Zitat von TheDragonMaster
@MaMo: Ich würde das ganze mit Regulären Ausdrücken in Perl umwandeln, und diesen dann in deinen Code einbinden (sowas wie include bei PHP).
Das ist von der Performance her auch besser.
Wenn perl sowas wie eval hat geht es aber auch damit. Ist aber in jedemfall langsamer.

Jo. Wird wohl so laufen (ohne eval). Als alternative könnte man noch Text::Template nehmen :)

MfG MaMo

MrNase 10.05.2004 19:47

Super, läuft :)

Ich hab noch 3 Sachen:
- else hinzufügen
- Variablen innerhalb } {/if}
- Variablen anstelle von z.B. "1"

So z.B.
{if $userid == $guestuserid}
$headergust
{else}
$header
{/if}

Ist sowas machbar?

Akira 10.05.2004 20:39

ist ja alles schön und gut was hier gepostet wurde. aber ich frage mich was es für einen sinn hat.

wenn ich das ganze per PHP schreibe anstatt per sogennanten script in templates dann ist es bedeutend schneller. und übersichtlicher.

MrNase 10.05.2004 20:46

Ich benutze dies um ganz kleine Dinge in die Templates zu packen... Z.B ein Hinweis für alle die weniger als 5 Beiträge haben oder spezielle Hinweise in diversen Themen.

Patrick Gotthardt 11.05.2004 09:34

So müsste es gehen:

PHP-Code:

<?PHP
 
class template
 
{
     var 
$template_code null;    // In dieser Variable wird der HTML-Code des Templates zwischengespeichert.
     
     /**
      * Der Klassenkonstruktor der den HTML-Code welcher
      * via Parameter uebergeben wird zwischenspeichert.
      **/
     
function template($template_code)
     {
         
$this->template_code $template_code;
     }
     
     
/**
      * Parsen des HTML-Codes mit regulaeren Ausdruecken.
      **/
     
function parse()
     {
         
/**
          * Der Ausdruck sucht nach IF-Konstrukten mit der Syntax {if Bedingung} HTMl-Code{/if}
          * Der 's' Modifier gibt an das der Ausdruck ueber mehrere Zeilen passen soll, 'i' das
          * Gross-/Kleinschreibung ignoriert wird und 'e' das der Ersetzungscode als PHP ausgefuehrt wird
          * und mit dem Ergebniss ersetzt wird. Das bedeutet das bei einem Treffer des Ausdrucks die Funktion
          * $this->do_if() aufgerufen und als ersten Parameter die Bedingung des IF-Konstrukts und als zweiten
          * Parameter den Code zwischen den {if}...{/if} Tags uebergeben bekommt.
          **/
         
$this->template_code preg_replace('/\{if (.*?)\}(.*?)\{else\}(.*?)\{\/if\}/ise''$this->do_if(\'\\1\', \'\\2\', \'\\3\')'$this->template_code);
         
$this->template_code preg_replace('/\{if (.*?)\}(.*?)\{\/if\}/ise''$this->do_if(\'\\1\', \'\\2\')'$this->template_code);
         
         
/**
          * Geparstes template zurueckgeben.
          **/
         
return $this->template_code;
     }
     
     
/**
      * Wertet eine IF-Abfrage aus dem Template aus.
      * Der erste Parameter enthaelt die Bedingungen der Abfrage,
      * der Zweite den HTML-Code der ins Template zurueckgeschrieben wird wenn
      * die Auswertung 'wahr' ergibt.
      **/
     
function do_if($if_cond$if_code$else_code='')
     {
         
extract($GLOBALS); // Globale Variablen verfuegbar machen
         
         /**
          * Die Bedingung der Abfrage wird einfach als PHP evaluiert.
          * Das bedeutet aus der Bedingung '$a == $b' wird
          * $if_result = $a == $b; womit das Ergebniss der Bedingung (true/false)
          * in $if_result gespeichert wird.
          **/
         
eval('$if_result = '.stripslashes($if_cond).';');
         
         
/**
          * Ueberprufen ob $if_result true oder false ist.
          * Falls true wird der HTML-Code zwischen den {if}..{/if} Tags
          * zurueckgegeben. Falls false wird ein leerer String zurueckgegeben
          * was das IF-Konstrukt aus dem Template entfernt.
          **/         
         
if($if_result) {
             eval(
'$if_code = "'.stripslashes($if_code).'";');
             return 
$if_code;
         } else {
             eval(
'$else_code = "'.stripslashes($else_code).'";');
             return 
$else_code;
         }
     }
 }
 
?>


exe 11.05.2004 18:08

Wenn $if_code und $else_code in einem eval()-Aufruf innerhalb eines Strings gepackt werden sollten sie nicht mit stripslashes() behandelt werden da sonst eventuelle Anführungszeichen zwischen den {if ...} ... {/if}-Tags einen Parse Error beim eval()-Aufruf erzeugen.
Kurz gesagt:
PHP-Code:

if($if_result) {
            eval(
'$if_code = "'.$if_code.'";');
            return 
$if_code;
        } else {
            eval(
'$else_code = "'.$else_code.'";');
            return 
$else_code;
        } 

anstatt

PHP-Code:

if($if_result) {
            eval(
'$if_code = "'.stripslashes($if_code).'";');
            return 
$if_code;
        } else {
            eval(
'$else_code = "'.stripslashes($else_code).'";');
            return 
$else_code;
        } 

Zitat:

Zitat von Akira
ist ja alles schön und gut was hier gepostet wurde. aber ich frage mich was es für einen sinn hat.

Der Sinn ist es viele kleine Aufgaben die lediglich das Layout betreffen aus dem normalen Programmcode zu entfernen und in Templates unterzubringen. Das macht die Templates zwar etwas komplizierter, erleichtert die Programmierung aber ungemein da nicht für jede kleine Bedingung die ein Fitzelchen vom Layout verändert eine extra Routine ins Programm geschrieben werden muss.

codethief 29.05.2004 19:39

Templates waren aber ursprünglich dafür gedacht, Code & Design strikt voneinander zu trennen.
So ein Fitzelchen kann zwar ganz schön lästig sein, allerdings muss man auch bedenken, dass so ein regulärer Ausdruck viel Leistung kostet und es ohne eine solche if-in-Template-Implementierung um einiges schneller geht.



Bis dann,

codethief

TRS 29.05.2004 19:59

Auch bei größeren Projekten kann ich in diesem System durchaus einen Sinn erkennen. Wieso muss man das ganze durch eine Templateengine künstlich aufblähen, wenn man auch diese Methode wählen kann: Schnell, einfach und simpel.

exe 29.05.2004 21:45

Ich finde Templatescripting (und natürlich Templateengines) haben schon ihren Sinn.

Als erstes machen einfache if/else-Konstrukte und Schleifen die eigentliche Programmierung um einiges einfacher. Man muss nicht ständig in seinem Programm if/else-Konstrukte, Schleifen und andere Anweisungen setzen um das Layout zu kontrollieren. Man pumpt die Daten in die Templateengine und die kümmert sich um die Darstellung.

Der zweite, und viel wichtigere Punkt, ist die Trennung von Programmlogik und Darstellungslogik. Der Programmcode an sich sollte sich nicht darum kümmern wie die Daten dargestellt werden. Sobald ich aber das Layout in x Schnippsel aufteile ist genau das der Fall.

Ein Beispiel: beim WBB (ich glaube es war Version 2.0.3) wollte ich einmal die Datumsangabe im Header ein Stück weiter nach oben versetzen und bin gleich auf ein Problem gestossen: zum Zeitpunkt wo der Schnippsel, der für den Anfang des Headers zuständig war, abgearbeitet wurde, war die Datumsvariable von der Software noch nicht definiert.
Das wäre ein Beispiel von vielen Verschiedenen dafür, wie beim "einfach und simpel"-Prinzip die Programmlogik in die Darstellungslogik eingreift.
Anderes Beispiel: nehmen wir an ich programmiere eine Benutzerliste und möchte die Benutzer in 3 Spalten auflisten. Mit einfachen HTML-Schnippseln würde ich in etwa sowas programmieren:
PHP-Code:

$users = array('Hans''Petern''Andreas''Florian''Laura''Felix');
for(
$i 0$i ceil(count($users) / 3); $i++)
{
    for(
$j 0$j 3$j++)
    {
        echo 
$users[($i $j)].' '// Hier stünde dann ein Template ...
    
}
    echo 
"<br>"// Hier noch eins ...


Was würde ich jetzt aber machen wenn ich die Benutzer plötzlich in 2 oder 4 Spalten darstellen möchte? Richtig, ich müsste am Programmcode arbeiten. Mit einer ordentlichen Templateengine muss ich in dem Fall nur ein bisschen an meinem Template schrauben - so wie es sein soll.

Der Sinn von Templates ist meiner Meinung nach nicht strikte Trennung von Layout und Programmcode - wegen mir können Templates selber in PHP geschrieben werden (auch wenn das einige Nachteile mit sich bringt). Der Sinn liegt vielmehr darin die Programmlogik, also Verwalten und Aufbereitung der Daten, von der Darstellungslogik zu trennen - mit "einfach und simpel" gar nicht so einfach.

Und wenn die Engine gut ist bringt das nicht mal Geschwindigkeitsnachteile: ob ein if/else-Konstrukt im Programmcode oder Template steht macht von der Geschwindigkeit her keinen Unterschied wenn die Engine, wie das so üblich ist, die Templates nach PHP kompiliert.
Im Gegenteil: ein PHP-Template kann von Performance-Tools wie dem Zend Accelerator gecached werden, was die Ausführung noch beschleunigt.

codethief 30.05.2004 10:47

Hm, stimmt aus dieser Perspektive (Trennung von Programmlogik und Darstellungslogik) habe ich das noch gar nicht betrachtet. =)
Hm, nunja, aber dies würde trotzdem pro Template mindestens 1 Regexp brauchen.... ;)

Philipp Gérard 30.05.2004 11:32

TS macht TPLS flexibler, das ist der Vorteil. Damit hat es sich aber auch ;)

exe 30.05.2004 13:03

Zitat:

Zitat von codethief
Hm, nunja, aber dies würde trotzdem pro Template mindestens 1 Regexp brauchen.... ;)

Nicht unbedingt. Nimm dir Smarty (smarty.php.net) als Beispiel: dort wird das Template beim ersten Aufruf mit regulären Ausdrücken geparsed und das Template inklusive Scripting-Anweisungen in ein PHP-Script übersetzt. Bei den folgenden Aufrufen wird dann nur noch das PHP-Template via include(); eingebunden. Wird ein Template geändert wird es beim nächsten Aufruf automatisch neu übersetzt.
Das Verfahren geht recht schnell. Auf meinem lokalen Webserver (450MHZ, 256MB RAM) kann ich mit Smarty keinen wirklich messbaren Geschwindigkeitsverlust feststellen. Nur wenn ein Template nach PHP übersetzt wird steigt die Ausführungszeit für einen Aufruf halt um 0,1 - 0,3 Sekunden.
Letztendlich sind reguläre Ausdrücke auch nicht unbedingt langsam. Für meinen Wikiparser werden bei jedem Seitenaufruf mehr als 30 reguläre Ausdrücke ausgeführt. Trotzdem erhöht das die Ausführungszeit nur um 0.02 Sekunden um etwa zwei DIN A4 Seiten zu parsen - also ein relativ überschaubarer Geschwindigkeitsverlust für die Aufgabe.

Patrick Gotthardt 30.05.2004 15:38

Smarty ist für mich (trotz der Geschwindigkeit) das Negativ-Beispiel schlechthin.
Bevor man Smarty nutzt kann man gleich mit PHP arbeiten.

Ein Template-System braucht im Prinzip nur minimale Funktionen. Zum einen sind das Variablen, zum anderen If-Konstrukte - und die sind schon fast zuviel.
Wenn ich mir Smarty ansehe stelle ich fest, dass ich genausogut - wenn nicht besser - mit PHP arbeiten könnte.

Nehmen wir mal ein Beispiel aus dem Smarty-Manual (das für ne Template-Engine schon viel zu viel ist):
Code:

{* Ausgabe der drop-down Liste *}
 <SELECT name=firma>
{html_options values=$vals selected=$selected output=$output}
</SELECT>

Wieso ist das einfacher als direkt PHP zu verwenden:
PHP-Code:

<select name="firma">
<?php foreach($vals as $key => $value) { ?>
<option value="$value"<?php echo $value==$selected ' selected="selected"' ''?>>$key</option>
<?php ?>
</select>

Zumal man den Teil mit der Überprüfung noch in eine Funktion auslagern könnte:
PHP-Code:

<select name="firma">
<?php foreach($vals as $key => $value) { ?>
<option value="$value" <?php echo isSelected($value$selected); ?> >$key</option>
<?php ?>
</select>

Und natürlich gibts da noch die alternative Syntax (foreach([...]): [...] endforeach;).

Da finde ich die PHP-Syntax deutlich einfacher.

Gegen eine brauchbare (d.h. nicht dermaßen überladene) Template-Engine ist aber nichts einzuwenden.
Theoretisch könnte man sich das kompilieren der Templates sogar sparen, denn die 2 Regulären Ausdrücke pro Template machen den Braten auch nicht mehr fett (verglichen mit der Anzahl der Regulären Ausdrücke, die bei einem (nicht vor-kompilierten) Beitrag für BBCodes, Smilies, Badwords, usw. genutzt werden).

Allerdings würde ich persönlich eine HTML-Ähnliche Syntax für das TS verwenden, denn diese gliedert sich besser in das Template ein und ich denke mal, dass es auch einfacher für den Designer ist.

Was ich damit sagen will: TS ist gut, solange es simpel bleibt. Wenn eine Engine wie Smarty verwendet wird kann man besser mit PHP und include arbeiten.

codethief 30.05.2004 19:18

@TheDragonMaster: Da muss ich dir Recht geben.
Da mag selbige zwar noch schnell und beliebt sein, überladen ist sie trotzdem.

Und irgendwann endet es noch damit, dass man eine eigene Sprache mit PHP für Templates entwickelt hat aus der sich eine völlig neue Scriptsprache entwickelt... :D


Eine Frage zu
eval('$if_code = "'.$if_code.'";');

Wieso kommen da "s hin? PHP sieht es dann doch als String an und dürfte $if_code dann doch gar nicht ausführen?


codethief

Patrick Gotthardt 30.05.2004 19:45

Das ist ja das witzigste. PHP war anfangs ja auch eher als Template-Sprache gedacht, wurde dann jedoch zu dem, was wir heute verwenden.

Eine gewisse Analogie mit Smarty ist da schon erkennbar. Bin wirklich gespannt, wann die MySQL-Verbindungen, OOP usw. einbauen...

Zu deiner Frage: Du verwechselst $if_code mit $if_result.
$if_code ist das, was zwischen {if [...]} und {/if} steht.

codethief 30.05.2004 19:57

Ach nee, stimmt. Hab mir den Kontext nicht richtig durchgelesen. Danke dir, dass du mich drauf hingewiesen hast. :)

exe 30.05.2004 20:29

Zitat:

Zitat von TheDragonMaster
Smarty ist für mich (trotz der Geschwindigkeit) das Negativ-Beispiel schlechthin.
Bevor man Smarty nutzt kann man gleich mit PHP arbeiten.

Ich sagte ja: wegen mir kann man Templates auch direkt in PHP schreiben. Für die meisten Einsatzgebiete wäre das auch ausreichend.
Smarty ist natürlich ein recht kontroverses Beispiel, da diese Engine eine sehr umfangreiche Syntax hat und von Haus aus auch noch mit einer Menge Plugins daherkommt (der html_options-Tag gehört da ja meines Wissens dazu).
Trotzdem möchte ich die Möglichkeiten der if/else-Konstrukte, Schleifen, Includes und Plugins nicht mehr missen. Sie machen das Gestalten der Templates, unabhängig von der Programmlogik, sehr einfach und flexibel.

Ich wollte ja schon immer eine schlankere Version der Smarty Engine schreiben, die dann auch nicht mehr den Anspruch erhebt eine vollständige Scriptsprache anzubieten, allerdings ist das bislang an meiner Bequemlichkeit gescheitert ;)

TRS 30.05.2004 20:53

Eine gute Alternative zu Smarty stellt in meinen Augen die SmartTemplate-Engine von www.smartphp.net - Wer sich ein bischen damit befasst, wird begeistert sein. Auch dort gibt eine Reihe von nützlichen Zusatzfeatures. So kann beispielsweise Timestamps direkt im Template bearbeiten ohne den Umweg über PHP oder eine Truncut-Option, mit der man Texte kürzen kann. Ebenso ist Debugmode und eine Cachefunktion mitinbegriffen.

Homepage: http://smartphp.net/content/smarttem...out/about.html
Extensions: http://smartphp.net/content/smarttem...s.html?menu=47

Aber auch ich habe ein paar Probleme damit hatte und ich deswegen auf ebenso auf die eval-Variante umgestiegen bin.

codethief 30.05.2004 22:25

Ich benutze diese umfangreichen TemplateEngines aber auch deshalb nicht, weil ich lieber selber code. Ist auch eine super Übung. :)
Außerdem muss man bedenken: Wenn man sein Produkt (Forum, CMS, was weiß ich) später mal verkaufen will, braucht man höchst wahrscheinlich 'ne Erlaubnis von dem Entwickler der TemplateEngine.
Da code ich doch lieber selbst
-> geht schneller als sich einzuarbeiten
-> ist kostenlos ^^
-> wahrscheinlich bessere Performence, da man sicherlich so viele Funktionen wie bei Smarty oder so garantiert nicht einbauen wird
-> man braucht keine Erlaubnis oder sonst was (copyright by selbst ;))



Bis dann,

codethief

codethief 30.05.2004 22:53

Mir ist eben noch eine Frage gekommen:
Funktionieren diese REGEXP eigentlich auch noch, wenn man {if} verschachtelt, mehrere {if}'s hintereinander oder sogar beides im Template hat?
Ich glaube nicht, oder?


Alle Zeitangaben in WEZ +1. Es ist jetzt 23:48 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