Template-Engines für C++

Standard

Ich habe in den letzten Tage nach einer Alternative für NL::Template gesucht, weil der Support für C++98 in NL::Template nicht optimal zu sein scheint. Es gibt zwar ein speziellen Branch für C++98, wenn ich diesen verwende, bekomme ich jedoch einen Speicherzugriffsfehler.

Im englischsprachigen Wikipedia gibt es eine tabellarische Auflistung von Template-Engines, ebenso wie für C++: http://en.wikipedia.org/wiki/Comparison_of_web_template_engines

Google ctemplate scheint von Google fallen gelassen worden zu sein und wird noch von einer Person auf GitHub weiter gepflegt. Es gib noch zwei Frameworks, die zum Großteil nur auf Russisch dokumentiert sind (auch in den Sourcen). Aber irgendwie habe ich nichts wirklich Überzeugendes gefunden.

Ich habe auch ein Wenig zu den (relativ) neuen Programmiersprachen Go und Rust quer gelesen. Beide möchten eine Alternative zu C++ sein und wollen es besser machen, als C/C++. Ich war sehr erstaunt zu sehen, dass es für beide Programmiersprachen schon mehrere ausgereift wirkende Web Frameworks gibt.

Mein Eindruck ist: C++ kann sich im Web-Umfeld warm anziehen. Der einzige Vorteil, den ich noch bei C++ sehe, ist dass es mehr Compiler zur Auswahl gibt und dass diese für mehr Plattformen zur Verfügung stehen, als für Rust und Go ist. Im Gegensatz zu node.js, gibt es in Rust und Go echte Nebenläufigkeit (zu dem noch auch einen interessanten Ansatz). Ich denke, das wird die nächsten Jahren noch recht spannend werden.

MVC-Architektur in Tntnet (Variante III.)

Standard

In der Vergangenheit habe ich bereits zwei Varianten (hier und hier), wie man mit Tntnet ein MVC-Konzept umsetzen kann, vorgestellt. Heute zeige ich eine dritte Möglichkeit.

Diese nutzt eine Template-Engine namens „NLTemplate“. Der Projekt-Code ist kompakt und überschaubar, herunterzuladen bei GitHub. Installation und  Benutzung sind ausreichend dokumentiert, daher gehe ich hier nicht weiter darauf ein, sondern beschreibe lediglich die Benutzung in Tntnet.

Auf Eines sei aber noch vorher hingewiesen : das Template-Engine läuft, wenn es das angegebene Template-File nicht laden kann, in einem segment foult. Das bringt den gesamten Application-Server von Tntnet zum Absturz. Deshalb habe ich den Code für mich geforkt und angepasst. Jetzt wirft bei mir die Template-Engine Exception, wenn sich das Template-File nicht öffnen lässt. Der Tntnet-Application-Server kann damit sauber umgehen.

Durch die Verwendung von NLTemplate ist die Trennung zwischen View- und Controller-Code noch stärker, als es mit Bordmitteln von Tntnet möglich ist. Die Template-Files enthalten keinerlei interpretierten Code oder irgend welche „Magic“ mehr. Der zweite Vorteil, den man aber auch als Nachteil sehen kann, ist dass die Template-Files zur Laufzeit geladen werden. Das bedeutet, dass man die Template-Files ändern kann ohne den kompletten Code neu übersetzen zu müssen. Der dritte Vorteil ist, dass man den Precompiler ecppc nicht mehr braucht. Das vereinfacht den Build-Prozess.

Hier zu meinem Beispiel : es gibt zwei Template-Files. „site_skeletal.nlt“ ist das Template, dass die Seiten-Struktur abbildet, die auf jeder Seite gleich bleibt.

<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>{{ site_title }}</title>
    </head>
    <body>
        {{ main_content}}
    </body>
</html>

Der Bereich „{{ main_content}}“ wird ersetzt durch den individuellen Teil der Seite. Die zweite Template-Datei sieht so aus:

<h1>Use the NLTemplate system</h1>
You can generate items with loops:
<ul>
{% block town_items %}
    <li>Title: {{ town_name }}</li>
{% endblock %}
</ul>
You can use this template system without a pre compiler.

Dieser Inhalt wird in das obere Tamplate eingefügt. Der Teil, der mit dem Schlüsselwort „block“ gekennzeichnet ist, kann in einer Schleife beliebig generisch wiederholt werden. Mit den doppelt geschweiften Klammern werden Schlüsselwörter gekennzeichnet, die durch einen beliebigen Inhalt ersetzt werden können.

Jetzt zum Controller-Code:

#include <tnt/ecpp.h>
#include <tnt/httprequest.h>
#include <tnt/httpreply.h>
#include <tnt/httpheader.h>
#include <tnt/http.h>
#include <tnt/data.h>
#include <tnt/componentfactory.h>
#include <stdexcept>
#include <ostream>
#include <vector>

#include <NLTemplate/NLTemplate.h>

log_define("component.alternativ_hello")

namespace
{
class _component_ : public tnt::EcppComponent
{
    _component_& main()  {
        return *this;
    }

  protected:
    ~_component_();

  public:
    _component_(const tnt::Compident& ci, const tnt::Urlmapper& um, tnt::Comploader& cl);
    unsigned operator() (tnt::HttpRequest& request, tnt::HttpReply& reply, tnt::QueryParams& qparam);
};

static tnt::EcppComponentFactoryImpl<_component_> Factory("alternativ_hello");

_component_::_component_(const tnt::Compident& ci, const tnt::Urlmapper& um, tnt::Comploader& cl)
  : EcppComponent(ci, um, cl){
}

_component_::~_component_(){}

unsigned _component_::operator() (tnt::HttpRequest& request, tnt::HttpReply& reply, tnt::QueryParams& qparam)
{
    std::vector<std::string> towns;
    towns = {"Hamburg", "Frankfurt", "München"};

    NL::Template::LoaderFile loader;
    NL::Template::Template nl_main_content( loader );
    nl_main_content.load( "src/hello/view/main_content.nlt" );
    nl_main_content.block( "town_items" ).repeat( towns.size() );
    for ( unsigned int i=0; i < towns.size() ; i++ ) {
        nl_main_content.block( "town_items" )[ i ].set( "town_name", towns[ i ] );
    }
    std::stringstream ss_main_content;
    nl_main_content.render( ss_main_content );

    NL::Template::LoaderFile skeletal_loader;
    NL::Template::Template nlTemplate( skeletal_loader );
    nlTemplate.load( "src/hello/view/site_skeletal.nlt" );
    nlTemplate.set( "site_title", "About NLTemplate" );
    nlTemplate.set( "main_content", ss_main_content.str() );

    nlTemplate.render( reply.out() );

    return HTTP_OK;
}

} // namespace

Ich lade hier die zwei Templates-Files:  erst das Template mit dem Haupt-Inhalt, das ich mit generischen Inhalt befülle. Dann lade ich das Template für die Seiten-Struktur und füge den Variablen Teil des Haupt-Inhaltes ein.

NLTemplate bietet auch die Möglichkeit, ein Template in ein anderes zu includieren. Ich mag den Ansatz jedoch nicht, weil dann das Seiten-Skelett-Tamplate in einen Kopf- und Fuß-Teil aufgetrennt werden muss. Wenn das Seiten-Skelett-Tamplate an Komplexität zunimmt, wird es recht schnell unübersichtlich.

Dieses Beispiel funktioniert nur mit dem Master-Branch vom Tntnet-Projekt. In der nächsten Zeit werde ich versuchen das Beispiel auch mit dem 2.Xer-Zweig (dem offiziellen Release-Zweig) zum Laufen zu bekommen.