Wörter aus XML dokument suchen und in ein Text neues Text dokument oder in Excel übermitteln.

kratos

Grünschnabel
Guten Tag,

Mein Ziel ist es, ein Programm zu schreiben, das alle xml-Dateien im Ordner durchsucht und speziell nach Wörtern sucht, die mit "Txt" || "txt"|| "TX_"|| "Tx_"|| "Tx" beginnen und die gefundenen Wörter in ein neues Dokument (oder Excel) schreibt. Da ich im Internet kein TOOL gefunden habe, das das kann (die TOOLs, die ich gefunden habe, können nur suchen & ersetzen), habe ich beschlossen, das in C++ zu schreiben, es sei denn, jemand kennt ein solches TOOL. Meine Frage wäre nun, welche String-Funktion ich verwenden sollte und ob ihr mir noch ein paar Tipps geben könntet, wie ich mein Ziel am besten erreichen kann.
 
Zuletzt bearbeitet:
Als Einstieg dürfte dies geeignet sein:
How to Parse XML in C++
Also zunächst parsen und dann die Funktionen der Bibliothek benutzen um die Texte auszuwerten.
Es stellt sich dann die Frage worin nach den Wörtern gesucht werden soll? In Tagnamen, Attributen, Textinhalten ...? Vermutlich letzteres.
 
Könnte ich mir das Leben leichter machen, indem ich den Text aus der xml-Datei in eine .txt-Datei kopiere, oder müsste ich auch dort parsen?
 
Das hängt davon ab, wie die Antwort auf diese Frage aussieht:
Es stellt sich dann die Frage worin nach den Wörtern gesucht werden soll? In Tagnamen, Attributen, Textinhalten ...?
Du kannst das XML-Dokument natürlich einfach als Textdatei nehmen und darin suchen. Dann findest Du die Suchstrings jedoch irgendwo, es kann auch in Tag- oder Attributnamen sein oder in den Werten der Attribute. Vermutlich nicht was Du vor hast.
 
Könnte ich mir das Leben leichter machen, indem ich den Text aus der xml-Datei in eine .txt-Datei kopiere, oder müsste ich auch dort parsen?
Am einfachsten für uns wäre es, wenn du einfach einen Ausschnitt deiner XML-Datei posten würdest.

Außerdem:
Da ich im Internet kein TOOL gefunden habe, das das kann (die TOOLs, die ich gefunden habe, können nur suchen & ersetzen)
Gibt es einen Grund, warum du nicht sed oder awk benutzt?

Und:
habe ich beschlossen, das in C++ zu schreiben
Muss es C++ sein? Das ist nämlich ziemlich low-level und es wird eine Menge Code und einiges an Debugging erfordern, bis du ein zufriedenstellendes Programm da raus bekommst.
Für derart simple Aufgaben wäre z.B. ein simples Python-Skript viel besser geeignet, da ja die allermeiste Arbeit hier im Prototyping des Programms besteht.

Gruß Technipion
 
Am einfachsten für uns wäre es, wenn du einfach einen Ausschnitt deiner XML-Datei posten würdest.

Außerdem:

Gibt es einen Grund, warum du nicht sed oder awk benutzt?

Und:

Muss es C++ sein? Das ist nämlich ziemlich low-level und es wird eine Menge Code und einiges an Debugging erfordern, bis du ein zufriedenstellendes Programm da raus bekommst.
Für derart simple Aufgaben wäre z.B. ein simples Python-Skript viel besser geeignet, da ja die allermeiste Arbeit hier im Prototyping des Programms besteht.

Gruß Technipion
So sehen zum Beispiel meine Xml-Dateien aus. Es muss nicht C++ sein, aber mit Python habe ich noch nichts zu tun gehabt. Kann ich mein Ziel mit Sed und Awk erreichen, das kenne ich leider auch nicht ?
 

Anhänge

  • txt.PNG
    txt.PNG
    296,6 KB · Aufrufe: 7
So sehen zum Beispiel meine Xml-Dateien aus. Es muss nicht C++ sein, aber mit Python habe ich noch nichts zu tun gehabt. Kann ich mein Ziel mit Sed und Awk erreichen, das kenne ich leider auch nicht ?
Sed und awk gibts nur auf Unix-Platformen (FreeBSD, Linux etc.).
Meines Wissens gibts das für Windows nicht.

Dein Bild zeigt eine stinknormale Baumstruktur (Wie auch zu erwarten bei einem XML)
Und dafür C++ zu nehmen, ist wie mit Atomraketen auf Ameisen zu schiessen.

Würds wahrscheinlich in Excel-VBA machen. Sollte es genug XML-Parser-Bibliotheken geben
Working with XML files in VBA (VBA XML) - Analyst Cave
 
Würds wahrscheinlich in Excel-VBA machen. Sollte es genug XML-Parser-Bibliotheken geben
Wenn ich ihn richtig verstanden habe will er aber gar kein XML parsen, sondern bloß ganz bestimmte Zeichenketten rausfiltern.

Und dafür C++ zu nehmen, ist wie mit Atomraketen auf Ameisen zu schiessen.
Es muss nicht C++ sein, aber mit Python habe ich noch nichts zu tun gehabt. Kann ich mein Ziel mit Sed und Awk erreichen, das kenne ich leider auch nicht ?
Ja, aber was machste halt wenn in deinem Arsenal bloß Atomraketen sind? Wenn ich irgendwo Windows neu aufsetze (was ich zugegebenermaßen sehr ungern mache; ich schlitze ja auch Autos nicht die Reifen auf) sind Python und Git die ersten Dinge, die ich gleich mal nachinstalliere. Wenn man sich überlegt wie viel "Bordsoftware" im Vanilla-Windows sowieso mit Python läuft, finde ich es völlig unverständlich es nicht gleich von Hause aus mitzuliefern. Stattdessen liegen halt irgendwo im system32 5 bis 8 veraltete Python-Versionen rum, und ich muss mir die neueste nachinstallieren. Ist halt so wenn man keinen Paketmanager hat.

Aber zurück zum Thema: C++ it is.
Um auf den Punkt zu kommen, so ungefähr kann man das machen:

C++:
#include <iostream>     // std::cout
#include <fstream>      // std::ifstream, std::ofstream
#include <string>       // std::string
#include <sstream>      // std::ostringstream
#include <regex>        // std::regex


void extract_from(const char* filename, std::ofstream& target);


int main(int argc, char* argv[])
{
    std::ofstream output_fstream(argv[argc-1], std::ofstream::out);

    for (int i = 1; i < (argc-1); i++) {
        extract_from(argv[i], output_fstream);
    }
    output_fstream.close();
}


void extract_from(const char* filename, std::ofstream& target)
{
    std::ifstream input_fstream(filename, std::ifstream::in);

    std::string content;
    if (input_fstream) {
        std::ostringstream oss;
        oss << input_fstream.rdbuf();
        content = oss.str();
    }
    input_fstream.close();

    // Verschiedene Varianten zum Durchtesten:
    std::regex words_regex(R"([Tt][Xx][t_]?\w*\W)");
    //std::regex words_regex(R"((Txt|txt|TX_|Tx_|Tx)\w*\W)");
    //std::regex words_regex(R"(([Tt]xt|T(X|x_?))[_[:alnum:]]+[^_[:alnum:]])");

    auto words_begin = std::sregex_iterator(content.begin(), content.end(), words_regex);
    auto words_end = std::sregex_iterator();

    std::cout << "Found "
              << std::distance(words_begin, words_end)
              << " words in "
              << filename << std::endl;

    for (auto i = words_begin; i != words_end; i++) {
        std::smatch match = *i;
        std::string match_str = match.str();
        target << match_str << std::endl;
    }
}
(Der Code könnte schöner sein; ich hab's halt in 5 min schnell zusammengebastelt und versucht es möglichst anfängerfreundlich zu halten)

Im Prinzip ist die Sprache beinahe egal. Wichtig ist: Reguläre Ausdrücke! Dein Anwendungsfall schreit beinahe danach. Und Regular Expressions sind in praktisch jeder Programmiersprache vorhanden.

Wir könnten hier jetzt eine lange Diskussion über REs und ihre Performance anfangen. Aber seien wir mal ehrlich: Das Programm läuft so wie es ist auf jedem Mittelklasselaptop mit deutlich über 100 MB/s. Da brauchste nix optimieren. Außer du hast halt wirklich gigabyteweise Daten...

Kurz zur Benutzung, falls du noch Neuling bist:
  • Du compilierst dir das Programm. Gerne auch mit -O2 oder -O3.
  • Die compilierte Programmdatei kommt dorthin, wo sie für dich praktisch liegt. Z.B. direkt in dem Ordner mit den XML-Dateien.
  • Shift+Rechtsklick in den Ordner und auf "Command hier öffnen" oder "Powershell hier öffnen" klicken.
  • Der Befehl lautet dann mein_programm erste_xml.xml zweite_xml.xml dritte_xml.xml ausgabedatei.txt
  • Das Programm filtert aus den XML-Dateien alle Treffer heraus und schreibt sie in die Ausgabedatei.
So wie ich den Code geschrieben habe, wird jeweils eine ganze XML-Datei in den Hauptspeicher gelesen. Bei typischerweise mehreren freien GB RAM heutzutage und Dateigrößen von wahrscheinlich deutlich unter einem Gigabyte sollte das aber kein Problem sein.
Falls du Fragen zum Code hast natürlich immer raus damit.


Kurze Erläuterung zu Regulären Ausdrücken

Ich habe dir einfach mal 3 mögliche Patterns in das Programm (als Raw-String) gepackt. Die Syntax für C++ REs kann man hier nachlesen. Eine gute Einführung in Regular Expressions gibt es hier.

Variante 1 - "[Tt][Xx][t_]?\w*\W": Erwarte ein t oder T. Danach ein X oder x. Optional (?) folgt hiernach ein t oder ein _ (Unterstrich). Wiederhole mind. 0 mal (*) ein alphanumerisches Zeichen (\w). Am Ende steht ein nicht-alphanumerisches Zeichen (\W). Vorteil: Sehr kurz. Nachteil: Matcht auch "falsche" Zeichenketten, z.B. "tX_".

Variante 2 - "((Txt|txt|TX_|Tx_|Tx)\w*\W)": Erwarte am Anfang "Txt" oder "txt" oder "TX_" oder "Tx_" oder "Tx". Hier habe ich also wörtlich die Suche aus deinem ersten Beitrag übernommen. Danach kommen wieder 0 oder mehr alphanumerische Zeichen und am Schluss ein nicht-alphanumerisches Zeichen. Vorteil: Gut lesbar/nachvollziehbar, da nur 5 Varianten. Nachteil: Sehr unoptimiert (in jeglicher Hinsicht). Wenn es mehr als 5 mögliche Varianten gibt wird es außerdem sehr unübersichtlich.

Variante 3 - "([Tt]xt|T(X|x_?))[_[:alnum:]]+[^_[:alnum:]])": Der erste Teil des Patterns kann auf zwei Weisen gebildet werden. "[Tt]xt": Entweder beginnt das Wort mit T oder mit t, danach folgt "xt". "T(X|x_?)": Oder das Wort beginnt mit einem großen T, gefolgt von einem großen X (dann aber kein Unterstrich), oder von einem kleinen x, wobei hier dann noch ein Unterstrich folgen kann. Danach kommt mindestens ein (+) alphanumerisches Zeichen, und das Pattern stoppt beim ersten nicht-alphanumerischen Zeichen. Die Gruppe "[:alphanum:]" habe ich hier extra mal verwendet, damit du siehst wo das "\w" und "\W" oben herkommt. Vorteil: Elegant und matcht alles richtig. Außerdem wahrscheinlich ganz performant. Nachteil: Braucht mehr Hirnschmalz.


Achtung: Wie du an den Regulären Ausdrücken siehst, hängt am Schluss immer noch das "Stoppzeichen", also ein nicht-alphanumerisches Zeichen dran. Falls du das nicht mit übertragen willst (bei Newlines nicht so schlimm, bei ">" aber vielleicht schon), kannst du es über match_str.pop_back(); leicht entfernen.


Ich hoffe ich habe dich jetzt nicht mit allem totgeschlagen. Bei Unklarheiten frag einfach nach.

Gruß Technipion
 
Rausfiltern, parsen….. wurscht.
wenn er es einmal in der Baumstruktur hat braucht er nur durchlaufen, und vergleichen.
mein link oben hat daf ein Beispiel
 
Wenn ich ihn richtig verstanden habe will er aber gar kein XML parsen, sondern bloß ganz bestimmte Zeichenketten rausfiltern.



Ja, aber was machste halt wenn in deinem Arsenal bloß Atomraketen sind? Wenn ich irgendwo Windows neu aufsetze (was ich zugegebenermaßen sehr ungern mache; ich schlitze ja auch Autos nicht die Reifen auf) sind Python und Git die ersten Dinge, die ich gleich mal nachinstalliere. Wenn man sich überlegt wie viel "Bordsoftware" im Vanilla-Windows sowieso mit Python läuft, finde ich es völlig unverständlich es nicht gleich von Hause aus mitzuliefern. Stattdessen liegen halt irgendwo im system32 5 bis 8 veraltete Python-Versionen rum, und ich muss mir die neueste nachinstallieren. Ist halt so wenn man keinen Paketmanager hat.

Aber zurück zum Thema: C++ it is.
Um auf den Punkt zu kommen, so ungefähr kann man das machen:

C++:
#include <iostream>     // std::cout
#include <fstream>      // std::ifstream, std::ofstream
#include <string>       // std::string
#include <sstream>      // std::ostringstream
#include <regex>        // std::regex


void extract_from(const char* filename, std::ofstream& target);


int main(int argc, char* argv[])
{
    std::ofstream output_fstream(argv[argc-1], std::ofstream::out);

    for (int i = 1; i < (argc-1); i++) {
        extract_from(argv[i], output_fstream);
    }
    output_fstream.close();
}


void extract_from(const char* filename, std::ofstream& target)
{
    std::ifstream input_fstream(filename, std::ifstream::in);

    std::string content;
    if (input_fstream) {
        std::ostringstream oss;
        oss << input_fstream.rdbuf();
        content = oss.str();
    }
    input_fstream.close();

    // Verschiedene Varianten zum Durchtesten:
    std::regex words_regex(R"([Tt][Xx][t_]?\w*\W)");
    //std::regex words_regex(R"((Txt|txt|TX_|Tx_|Tx)\w*\W)");
    //std::regex words_regex(R"(([Tt]xt|T(X|x_?))[_[:alnum:]]+[^_[:alnum:]])");

    auto words_begin = std::sregex_iterator(content.begin(), content.end(), words_regex);
    auto words_end = std::sregex_iterator();

    std::cout << "Found "
              << std::distance(words_begin, words_end)
              << " words in "
              << filename << std::endl;

    for (auto i = words_begin; i != words_end; i++) {
        std::smatch match = *i;
        std::string match_str = match.str();
        target << match_str << std::endl;
    }
}
(Der Code könnte schöner sein; ich hab's halt in 5 min schnell zusammengebastelt und versucht es möglichst anfängerfreundlich zu halten)

Im Prinzip ist die Sprache beinahe egal. Wichtig ist: Reguläre Ausdrücke! Dein Anwendungsfall schreit beinahe danach. Und Regular Expressions sind in praktisch jeder Programmiersprache vorhanden.

Wir könnten hier jetzt eine lange Diskussion über REs und ihre Performance anfangen. Aber seien wir mal ehrlich: Das Programm läuft so wie es ist auf jedem Mittelklasselaptop mit deutlich über 100 MB/s. Da brauchste nix optimieren. Außer du hast halt wirklich gigabyteweise Daten...

Kurz zur Benutzung, falls du noch Neuling bist:
Was genau meinst du mit -O2 oder -O3 ? Ich compiliere mit " Lokaler Windows-Debugger" ist es das ?
  • Du compilierst dir das Programm. Gerne auch mit -O2 oder -O3.
Meinst du die Datei, welche sich nach dem Compilieren im Debug befinden ( .exe) ?
  • Die compilierte Programmdatei kommt dorthin, wo sie für dich praktisch liegt. Z.B. direkt in dem Ordner mit den XML-Dateien.
  • Shift+Rechtsklick in den Ordner und auf "Command hier öffnen" oder "Powershell hier öffnen" klicken.
Habe mein Programm jetzt z.B. testen gennant und drei xml Dateien in den Ordner gepackt (siehe Bild) d.h. ich muss in der Befehlszeile \test.exe eine.xml zwei.xml drei.xml liste.txt eingeben. Leider kriege ich das auch nicht ganz hin (siehe Bild)
  • Der Befehl lautet dann mein_programm erste_xml.xml zweite_xml.xml dritte_xml.xml ausgabedatei.txt
  • Das Programm filtert aus den XML-Dateien alle Treffer heraus und schreibt sie in die Ausgabedatei.
So wie ich den Code geschrieben habe, wird jeweils eine ganze XML-Datei in den Hauptspeicher gelesen. Bei typischerweise mehreren freien GB RAM heutzutage und Dateigrößen von wahrscheinlich deutlich unter einem Gigabyte sollte das aber kein Problem sein.
Falls du Fragen zum Code hast natürlich immer raus damit.


Kurze Erläuterung zu Regulären Ausdrücken

Ich habe dir einfach mal 3 mögliche Patterns in das Programm (als Raw-String) gepackt. Die Syntax für C++ REs kann man hier nachlesen. Eine gute Einführung in Regular Expressions gibt es hier.

Variante 1 - "[Tt][Xx][t_]?\w*\W": Erwarte ein t oder T. Danach ein X oder x. Optional (?) folgt hiernach ein t oder ein _ (Unterstrich). Wiederhole mind. 0 mal (*) ein alphanumerisches Zeichen (\w). Am Ende steht ein nicht-alphanumerisches Zeichen (\W). Vorteil: Sehr kurz. Nachteil: Matcht auch "falsche" Zeichenketten, z.B. "tX_".

Variante 2 - "((Txt|txt|TX_|Tx_|Tx)\w*\W)": Erwarte am Anfang "Txt" oder "txt" oder "TX_" oder "Tx_" oder "Tx". Hier habe ich also wörtlich die Suche aus deinem ersten Beitrag übernommen. Danach kommen wieder 0 oder mehr alphanumerische Zeichen und am Schluss ein nicht-alphanumerisches Zeichen. Vorteil: Gut lesbar/nachvollziehbar, da nur 5 Varianten. Nachteil: Sehr unoptimiert (in jeglicher Hinsicht). Wenn es mehr als 5 mögliche Varianten gibt wird es außerdem sehr unübersichtlich.

Variante 3 - "([Tt]xt|T(X|x_?))[_[:alnum:]]+[^_[:alnum:]])": Der erste Teil des Patterns kann auf zwei Weisen gebildet werden. "[Tt]xt": Entweder beginnt das Wort mit T oder mit t, danach folgt "xt". "T(X|x_?)": Oder das Wort beginnt mit einem großen T, gefolgt von einem großen X (dann aber kein Unterstrich), oder von einem kleinen x, wobei hier dann noch ein Unterstrich folgen kann. Danach kommt mindestens ein (+) alphanumerisches Zeichen, und das Pattern stoppt beim ersten nicht-alphanumerischen Zeichen. Die Gruppe "[:alphanum:]" habe ich hier extra mal verwendet, damit du siehst wo das "\w" und "\W" oben herkommt. Vorteil: Elegant und matcht alles richtig. Außerdem wahrscheinlich ganz performant. Nachteil: Braucht mehr Hirnschmalz.


Achtung: Wie du an den Regulären Ausdrücken siehst, hängt am Schluss immer noch das "Stoppzeichen", also ein nicht-alphanumerisches Zeichen dran. Falls du das nicht mit übertragen willst (bei Newlines nicht so schlimm, bei ">" aber vielleicht schon), kannst du es über match_str.pop_back(); leicht entfernen.


Ich hoffe ich habe dich jetzt nicht mit allem totgeschlagen. Bei Unklarheiten frag einfach nach.

Gruß Technipion
Zunächst einmal danke ich dir für deine ausführliche Antwort. Deine Beschreibung, wie man dein Programm benutzt, ist wirklich gut von dir durchdacht. Leider habe ich einige Fragen, weil ich das nicht ganz hinbekomme.
 

Anhänge

  • test_ordner.PNG
    test_ordner.PNG
    16,5 KB · Aufrufe: 5
  • ergbniss.PNG
    ergbniss.PNG
    29,1 KB · Aufrufe: 5
Zurück