regex_replace $1



  • std::regex_replace gibt den geänderten String zurück, deine Schleife operiert aber immer nur auf dem Originalstring s. Du mußt also s neu zuweisen.



  • Ich glaube, du kannst immer nur ein Vorkommen gleichzeitig ersetzen, dazu musst du doch alle Einträge per Schleife durchlaufen und separat ersetzen:

    std::string const templ = "Name `name` Vname `vname`";
    std::map<std::string, std::string> const stash { { "name", "Hansel" }, { "vname", "Ulrich" } };
    
    std::string output = templ;	
    for( auto const& [key, value] : stash )
    {
       std::regex r( "`" + key + "`" );
       output = std::regex_replace( output, r, value );
    }
    std::cout << output << "\n";
    


  • @Th69 sagte in regex_replace $1:

    std::regex_replace gibt den geänderten String zurück, deine Schleife operiert aber immer nur auf dem Originalstring s. Du mußt also s neu zuweisen.

    Ja is schon klar. Die Frage ist nur wie 😉

    MFG



  • Moin!

    1. Regex-Strings kann man in C++ am besten mit einem R-String schreiben, damit man nicht überall die Backslashes quoten muss. Zum Beispiel so:
      Dein
    "`(\\w+)`"
    // ist 
    R"(`(\w+)`)"
    // oder auch:
    R"***(`(\w+)`)***"
    

    Du kannst den String zwischen " und ( so wählen, dass das Ende-Zeichen sicher nicht im String vorkommt.

    1. Regexes in C++ benutzen sich so "mäh", wenn man das aus Perl gewöhnt ist. Man kann mit einem regex_token_iterator arbeiten, ungefähr so:
    string text { "Name `name` Vname `vname` " };
    regex re { R"*(`(\w+)`)*" };
    for_each(
        sregex_token_iterator(begin(text), end(text), re, {0, -1}),
        sregex_token_iterator{},
        [](const string &s){cout << s << "\n";}
    );
    

    Ist aber blöd, weil du abwechselnd die Matches und Zwischenteile bekommst (oder wahlweise nur die Matches, wenn du das -1 weglässt). Ich glaube, dass man sich da also was selbst basteln muss (vielleicht auch nur mit dem regex_iterator?). Ich habe std::regex immer versucht zu vermeiden.

    1. Weil man oft gar keine regexes braucht, die man zur Laufzeit zusammenstellen kann (ist eh recht teuer), gibt es auch eine spannende Compilezeit-Regex-Bibliothek von Hanka Dusikova: https://github.com/hanickadot/compile-time-regular-expressions


  • @wob

    Danke @wob das wird ne wüste Frickelei 😉

    Also mir geht es darum, meine Templating Engine performanter zu machen. Die geht Zeichen für Zeichen durch das Template um die String-Begrenzer (da habe ich mich für Backticks entschieden) und damit die Platzhalter zu finden. Das Ganze läuft stabil und ermöglicht eine Fehlerbehandlung wenn die Syntax des Template nicht stimmt.

    Wahrscheinlich ists das Beste, es so zu belassen. Performance ist nicht immer das Kriterium.

    Viele Grüße.



  • @_ro_ro In Boost geht das, was du willst: https://www.boost.org/doc/libs/1_84_0/libs/regex/doc/html/boost_regex/ref/regex_replace.html (das Formatter kann dort eine Funktion sein)



  • @wob sagte in regex_replace $1:

    @_ro_ro In Boost geht das, was du willst: https://www.boost.org/doc/libs/1_84_0/libs/regex/doc/html/boost_regex/ref/regex_replace.html (das Formatter kann dort eine Funktion sein)

    Ja danke Dir. Boost kenn' ich auch schon 😉

    Und wie nochmal verspeist man einen Elefanten? Stück für Stück 😉

    Viele Grüße1!!



  • @_ro_ro

    Das ist doch kein Hexenwerk, sich sowas schnell selbst zu schreiben.



  • @DocShoe sagte in regex_replace $1:

    Das ist doch kein Hexenwerk, sich sowas schnell selbst zu schreiben.

    Naja, ich finde es nicht so einfach. Diese Funktionalität fehlt einfach in std::regex. Klar, für diesen speziellen Fall hat man selbst schnell was geklöppelt.

    Oder eben mit boost:

    #include <boost/regex.hpp>
    #include <map>
    #include <string>
    #include <iostream>
    
    int main() {
        using namespace std;
    
        map<string, string> repl = {{"name", "Ulrich"}, {"vname", "Hansel"}};
    
        string text { "Name `name` Vname `vname` " };
        boost::regex re { R"*(`(\w+)`)*" };
    
        string result = boost::regex_replace(text, re, [&](const boost::smatch &m) { return repl.at(m[1]); } );
        cout << result << '\n';
    }
    


  • @wob

    Ja, sehr schön. Wie sieht die Fehlerbehandlung aus wenn im Template die Syntax nicht stimmt? Wenn z.B. an einem Platzhalter ein Backtick vergessen wurde?

    MFG



  • Du meinst, wenn kein Text ersetzt werden konnte?
    Entweder (bezogen auf den Code von @wob) den String vergleichen:

    if (result == text)
        // ...
    

    oder aber ein Flag setzen:

    bool changed = false;
    string result = boost::regex_replace(text, re, [&](const boost::smatch &m) { changed = true; return repl.at(m[1]); } );
    if (!changed)
        // ...
    


  • @_ro_ro
    Hab mal was gebastelt, kannst ja mal schauen, ob's funktioniert. Und wie's performancetechnisch im Vergleich zu regex aussieht:

    #include <string>
    #include <map>
    #include <algorithm>
    #include <stdexcept>
    
    std::string replace_copy( std::string const& templ,
                              std::string::value_type delimiter,
                              std::map<std::string, std::string> const substitutions )
    {
       std::string retval;
       for( auto it = templ.begin(); it != templ.end(); )
       {
          // Delimiter finden und alles bis zum Delimiter in Ergebnis übernehmen
          auto pos = std::find( it, templ.end(), delimiter );
          retval.insert( retval.end(), it, pos );
    
          // existieren weitere Zeichen?
          if( pos != templ.end() )
          {
             // ja, pos steht auf dem öffnenden Trennzeichen, Platzhalter beginnt mit dem folgendem Zeichen
             ++pos;
    
             // ab erstem Platzhalterzeichen das schließende Trennzeichen suchen
             auto end = std::find( pos, templ.end(), delimiter );
             if( end == templ.end() )
             {
                throw std::invalid_argument( "Missing terminal delimiter." );
             }
             // end steht auf dem schließenden Trennzeichen
             std::string const name( pos, end );
             auto sub = substitutions.find( name );
             if( sub == substitutions.end() )
             {
                throw std::invalid_argument( "Unknown placeholder '" + name + "'." );
             }			
             // Wert übernehmen
             retval += sub->second;
    	
             // end steht auf Delimiter, ab folgendem Zeichen weitersuchen		
             it = ++end;
          }
       }
       return retval;
    }
    

    Grundsätzlich sollte man aber schon fertige Lösungen bervorzugen. Hab leider keine Möglichkeit gefunden, wie man string_viewals Lookup benutzt, dann könnte man sich den std::string in Zeile 30 sparen.



  • @DocShoe

    Funktioniert einwandfrei, vielen Dank!!!
    ++ für die Prüfung ob ein Platzhalter bekannt ist!

    MFG

    PS:: Die Konsistenzprüfung können wir vereinfachen, einfach die Delimiter (Stringbegrenzer) zählen. Die Summe muß eine gerade Zahl ergeben 😉
    Ansonsten einer erstklassige Alternative für sprintf! Schluss mit der Stringverkettungsfummelei 😉



  • @DocShoe

    der Vollstkt. halber. Meine Lösung:

    namespace Templating{
        // Einfache Platzhalter in Backticks
        class Simple{
            private:
            string TEMPL;
            map <string, string> STASH;
            size_t cur_pos;  // Current pos in Template           
            string result;
            size_t pcount;   // Zähler Backticks
            string cur_name; // Current Name Platzhalter
    
            public:
            string encode_entities(const string &str){
                string res;
                for(int i = 0; i < str.length(); i++){
                    string s = str.substr(i,1);
                    if( s.compare("<") == 0 ) res += "&lt;";
                    else if( s.compare("'") == 0  ) res += "&x#27;";
                    else if( s.compare("\"") == 0  ) res += "&x#22;";
                    else if( s.compare("&") == 0  ) res += "&amp;";
                    else res += s; 
                }
                return res;
            };
            // extrahiere Name des Platzhalters
            void exname(){
                string s;
                cur_name = "";
                for(; cur_pos < TEMPL.length(); cur_pos++){
                    s = TEMPL.substr(cur_pos,1);
                    if( s.compare("`") == 0 ) {
                        pcount++;
                        break;
                    }
                    cur_name += s;
                }
                result += encode_entities( STASH[cur_name] );
            };
    
            string render(){
                for(cur_pos = 0 ; cur_pos < TEMPL.length(); cur_pos++){
                    string s = TEMPL.substr(cur_pos, 1);
                    if( s.compare("`") == 0 && pcount % 2 == 0){
                        pcount++;
                        cur_pos++;
                        exname();
                    }
                    else result += s;
                }
                if ( pcount % 2 ) throw string("Template Syntaxfehler. Die Anzahl der Backticks geht nicht auf, bitte prüfen!");
                return result;
            };
    
            // Konstruktor
            Simple(const string &templ, const map<string,string> &names){
                TEMPL = templ;
                STASH = names;   
                cur_pos = 0;
                pcount = 0;
            };
        };// class TE Templating Engine
    


  • @_ro_ro sagte in regex_replace $1:

    Schluss mit der Stringverkettungsfummelei

    Vielleicht willst du dir auch mal https://github.com/fmtlib/fmt anschauen. Ab C++20 bzw. C++23 auch im Standard vorhanden. Das ist dann mehr im Python-Stil und meiner Meinung nach viel besser lesbar als stream << ver << "kett" << ungen, vor allem weil man bei den Streams nie weiß, was passiert, wenn man z.B. eine Zahl schreibt (das hängt dann davon ab, ob jemand vorher irgendein Format gesetzt hat)

    PS:
    In deinem Code

    string s = str.substr(i,1);
    if( s.compare("<") == 0 ) ...
    

    Was soll das werden?!
    Du hast einen 1-Zeichen langen String. Das ist ein char. Und warum compare statt == - oder, da das ja eigentlich ein char ist - warum nicht ein switch? Und das encode_entities sollte irgedwie eine allgemeine Funktion sein, man braucht ja keine Templating-Engine dafür.

    Achtung: Strings wissen nichts von der Kodierung! Also immer Vorsicht bei der Verarbeitung über je 1 char.



    • Öffnest du den namespace std schon im Header?
    • Warum sind deine Variablen mal groß und mal klein geschrieben? Und mal mit Unterstrich und mal ohne?
    • Laufvariablen als Klassenmember schreien nach Ärger
    • statt substr( pos, 1 ) kannste besser das Zeichen direkt vergleichen, statt einen temporären string mit nur einem Zeichen zu erzeugen.


  • @DocShoe , @wob

    danke für Eure Hinweise!!! Verbessern geht immer. Die Klasse ist ein Erstlingswerk, geschrieben nach einer Woche c++.

    MFG

    Und noch etwas: Die Templateklasse ist Bestandteil meines Webframework. Das heißt daß sämtliche Ausgaben in HTML-Templates gerendert werden sofern der Content-Type text/html; Charset=UTF-8 ist. Von daher gehört die Funktion encode_entities in die Templateklasse. Andere Content-Types (binaries) gibt das Framework direkt auf stdout, also ohne Templating-Prozess.

    Des Weiteren compiliere ich auf einem Shared Host, bin also auf das angewiesen was der Provider installiert hat. Das heißt auch, daß der Provider allein entscheidet welche Libraries in C++ verfügbar sind, da kann ich gar nichts machen.
    Infolgedessen macht es keinen Sinn, mit einer höheren C++Version zu entwickeln.

    Was das das Einbinden von eigenen Header-Dateien betrifft, also ein Auslagern von Code, das ist sicher dann zweckmäßig wenn mit wiederkehrendem Code verschiedene ausführbare Dateien zu erstellen sind. Das ist jedoch bei meinem FW nicht der Fall, denn da wird nicht etwa für jede Webseite eine eigene ausführbare Datei erstellt sondern genau eine Einzige für Alle Webseiten die für das Framework konfiguriert sind. Insofern steht da auch nicht die Frage nach Coderedundanzen. Alles in einer Datei vereinfacht meinen Deployment-Prozess, die fw.cpp schicke ich aus dem Editor heraus per Knopfdruck (Taste F8) an einen Webservice (REST-Schnittstelle) der auch den Compiler als Remote-Prozedur aufruft. Das Ergebnis sehe ich im Editor genauso wie nach dem Aufruf des lokalen Compilers mit Taste F7.

    Btw., mit allen Klassen und Namespaces zusammen hat meine Source deutlich weniger als 600 Zeilen, selbst wenn da irgendwann einmal eine MySQL-Anbindung hinzu kommt wird das unter 1000 Zeilen bleiben. Und wenn das wirklich mal mehr werden sollte, kann ich über eine Auslagerung immer noch nachdenken, nur wegen der Übersichtlichkeit. Bei mehreren Sourcedateien jedoch käme zu meinem bisherigen Deployment-Prozess noch das Hochladen dazu und natürlich auch das Einchecken in die Revision Control.

    Ihr seht also, meine Entwicklungsumgebung ist schon ein bischen proprietär. Aber praktisch 😉

    Viele Grüße, schönen Abend.



  • @_ro_ro sagte in regex_replace $1:

    Des Weiteren compiliere ich auf einem Shared Host, bin also auf das angewiesen was der Provider installiert hat. Das heißt auch, daß der Provider allein entscheidet welche Libraries in C++ verfügbar sind, da kann ich gar nichts machen.
    Infolgedessen macht es keinen Sinn, mit einer höheren C++Version zu entwickeln.

    Die Frage dann ist doch, was dein Shared Host installiert hat, und ob höhere Versionen nicht nur an einem Compiler Flag scheitern.
    Man kann auch dritt Libs mit einchecken, die man dann dort halt mit kompiliert, auch das ist kein Problem. Je nachdem kann man auch den Host anweisen, entsprechende Libraries für den Buildvorgang zu installieren.
    Es ist keine Seltenheit, dass mans seine Sourcen irgendwo auf einem Server kompilieren lässt und in Zeiten von Infrastructure as a Service ist es auch keine Seltenheit, dass man sich den irgendwo anmietet.



  • @_ro_ro sagte in regex_replace $1:

    Des Weiteren compiliere ich auf einem Shared Host, bin also auf das angewiesen was der Provider installiert hat. Das heißt auch, daß der Provider allein entscheidet welche Libraries in C++ verfügbar sind, da kann ich gar nichts machen.

    Vielleicht für den Anfang zu umständlich, aber Alternativen könnten hier sein:

    Finde heraus, welches OS auf dem Server installiert ist und schau, ob du das unter Windows mit WSL installieren kannst. Besonders mit WSL2 hast du damit ein vollwertiges Linux, das du über eine Kommandozeile in Windows nutzen kannst. Da könntest du dann selbst entscheiden, welche Compiler-Version und welche Bibliotheken du installierst. Auch den Webserver zum Testen könntest du unter diesem WSL-Linux laufen lassen. Auf dem Server nicht vorhandene Bibliotheken kann man alternativ auch statisch in die ausführbare Datei einkompilieren. Da müssen die Bibliotheken dann nicht mehr auf dem Shared Host installiert sein, allerdings mit dem Nachteil, dass man diese Bibliotheken selbst updaten muss, indem man das Programm mit den neuen Bibliotheken neu kompiliert. Der Update-Mechanismus des OS deckt sowas nicht automatisch ab. In Anbetracht des steinalten Compilers auf deinem Shared Host ist allerdings fraglich, ob dieser vermeintliche Nachteil nicht eher ein Vorteil ist. Schau besser auch mal nach, ob das OS auf dem Server überhaupt regelmässige Updates bekommt 😉

    Du kannst auch mit einem anderen Linux für dein Zielsystem kompilieren, da könnte es aber eventuell Probleme mit Diskrepanzen zwischen den verwendeten Bibliotheken geben - auch hier wieder nicht bei statisch eingebundenen Bibliotheken. Nichts, das man nicht in den Griff bekommt, aber das würde es umständlicher machen.

    Etwas komplizierter wird es, wenn der Shared Host eine andere CPU-Architektur hat als dein Windows-System (also ARM, PowerPC oder sowas), dann müsste man für dieses cross-kompilieren. Das ist aber bei den Shared Hosts die ich bisher gesehen habe eher unwahrscheinlich.

    Alternativ kannst du schauen, ob auf dem Server auch so etwas wie Docker oder eine andere Container-Software läuft. Dann könntest du in dem Container die Compiler und Bibliotheken installieren, die du brauchst und das Programm damit bauen. Auch die Ausführung könnte man in einen Container verlagern, damit wärst du dann nicht darauf angewiesen, was dein Anbieter auf dem System installiert hat, sondern hättest freie Auswahl. Allerdings gilt auch für die Container, dass man die dann selbst auf auf dem neuesten Stand halten muss. Das macht das Host-OS dann nicht automatsich. Auch (und gerade) mit Container gilt: das kannst du auch auf deinem Entwicklungs-System (Windows) machen. Das ist damit auch wahrscheinlich wesentlich flotter als so ein handelsüblicher Shared Host - besonders wenn dein Programm irgendwann umfangreicher wird. Testen kann man unter Windows mit WSL. Auf den Shared Host muss man es erst packen wenn alles läuft.

    Und nochwas am Rande: Debuggen ist natürlich auch angenehmer, wenn du erstmal alles auf dem Entwicklungsrechner baust und testest. Im laufenden CGI Breakpoints setzen und Variablen auswerten hat schon seine Vorteile. Das ginge theoretsich auch auf dem Shared Host mit Remote Debugging, ist aber auch wieder ein paar Level komplizierter ... leider kann ich dir nicht auf Anhieb sagen, wie man das alles einrichtet, ich müsste mir das erst noch selbst genauer ansehen. Das sind aber alles technisch machbare Sachen - bitte erstmal nur als Anregung verstehen 😁



  • @Schlangenmensch

    in meinem Webframework (FW) kommt dem Auslagern von Code eine andere Bedeutung zu. Ich werde die Architektur bei Gelegenheit mal unter Projekte vorstellen, nur soviel: Alle für das FW konfigurierten Webseiten werden von einer einzigen ausführbaren Datei ausgeliefert, das können beliebig viele URLs sein. Dieser Anforderung genügt eine Responseklasse von der bei jedem Request eine Instanz erstellt wird welche die Response liefert. Neu hinzukonfigurierte URLs erfordern also nicht das FW neu zu kompilieren. Einzig die konfigurierten Eigenschaften dieser URL-Objekte bestimmen den Ablauf innerhalb der ausführbaren Datei welche Antwortseiten beliebiger Content-Types ausliefert.

    Extra Code wird nur geladen, wenn Eingaben zu verarbeiten sind (Webservices, Ajax usw.). In erster Linie ist das eine Instanz der CGI-Klasse. Zum Anderen ist dafür tatsächlich ein extra Code erforderlich welcher z.B. eine DB-Verbindung herstellt und die Daten für die Response bereitstellt. Ausgeliefert werden die Daten jedoch von der bereits vorhandenen Response-Basisklasse, denn der ganze Ablauf Request/Response ist immer derselbe.

    Viele Grüße!


Anmelden zum Antworten