[geloest] XML Parsen innerhalb einer Section mit shell

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Benutzeravatar
safran
Beiträge: 94
Registriert: 02.01.2008 04:46:07
Lizenz eigener Beiträge: Artistic Lizenz

[geloest] XML Parsen innerhalb einer Section mit shell

Beitrag von safran » 17.04.2018 08:27:36

Hallo,

ich nutze folgende Funktionen, um eine XML Datei einzulesen und ihren Inhalt spezifischen Variablen zuzuordnen

Code: Alles auswählen

read_dom () {
    local IFS=\>
    read -d \< ENTITY CONTENT
    local ret=$?
    TAG_NAME=${ENTITY%% *}
    ATTRIBUTES=${ENTITY#* }
    return $ret
}
und

Code: Alles auswählen

while read_dom; do
                if [[ $ENTITY = "Distributor" ]] ; then
                    echo "Distributor:" $CONTENT
                elif [[ $ENTITY = "Konditionen" ]] ; then
                    echo "Konditionen starten hier"                    
                fi
                if [[ $TAG_NAME = "Auszahlung" ]] ; then
                    eval $ATTRIBUTES
                    echo "Auszahlung Quote name is: $quote"

                elif [[ $TAG_NAME = "bar" ]] ; then
                    eval local $ATTRIBUTES
                    echo "bar type is: $type"
                fi
                if [[ $ENTITY = "Produktname" ]] ; then
                    echo $CONTENT
                fi
                echo "zeilenweise?"
            done < ./xml-datei
hier auch nochmal der vollständigkeithalber ein Teil der XML-Datei

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<Daten>
    <Distributor>Distributor</Distributor>
    <Konditionen>
        <Abschlussfrist>24</Abschlussfrist>
        <Abruffrist>26</Abruffrist>
        <Uebertritt>1</Uebertritt>
        <Staffelung>
            <Auszahlung quote="0.80"></Auszahlung>
            <Auszahlung quote="0.20">0628</Auszahlung>   
        </Staffelung>
        <Zielvereinbarung>
            <Provision abziel="005" abdatum="180101" wdhab="25" wdbis="24">000415.00</Provision>
        </Zielvereinbarung> 
    </Konditionen>
    <Portfolio> 
        <Produktgruppe>
        </Produktgruppe>
    </Portfolio>    
</Daten>
Nun möchte ich während dem Parsen, also dem Einlesen der Datei und dem Zuordnen von Variablen und Arrays innerhalb einer Section, in diesem Falle hier z.B. der Block zwischen <Konditionen> und </Konditionen> den Einlesefluss dorthin umleiten, z.B. zu einer Funktion.
Es ist ja so dass auch hier die Datei Zeile für Zeile eingelesen wird und dann die Verarbeitung stattfindet.

In meinem derzeitig angewandtem Modell gehe ich da so vor:

Code: Alles auswählen

Konditionenposition=`awk -v a="$distributor_zeile" -v b="<Konditionen>" 'BEGIN{print index(a,b)}'`
                if [ 0 -lt "$Konditionenposition" ]; then
                    Konditionenpositionende=0
                    echo "KONDITIONENSCHLEIFE START" $distributor_zeile
                    while [ $Konditionenpositionende == 0 ]
                    do
                        read distributor_zeile
                        Konditionenpositionende=`awk -v a="$distributor_zeile" -v b="/Konditionen>" 'BEGIN{print index(a,b)}'`
                        if [ 0 != "$Konditionenpositionende" ]; then
                            
                            
                            if [ ${abschlussdatum:4} -le $Abschlussfrist ] && [ ${abrufdatum:4} -le $Abruffrist ] ; then
                                echo "in der frist"
                                aufschub=0
                            else
                                echo "ein monat später"
                                aufschub=1
                            fi
                            echo "KONDITIONENSCHLEIFE END" ${abschlussdatum:4} $Abschlussfrist ${abrufdatum:4} $Abruffrist
                            break
                        fi
                        
                        Distributorposition=`awk -v a="$distributor_zeile" -v b="<Distributor>" 'BEGIN{print index(a,b)}'`
                        if [ 0 -lt "$Distributorposition" ]; then
                            insg=${#distributor_zeile}
                            Distributor=${distributor_zeile:13:$insg-27}
                            echo  ":" $Distributor 
                            continue
                        fi
                
                        Abschlussfristposition=`awk -v a="$distributor_zeile" -v b="<Abschlussfrist>" 'BEGIN{print index(a,b)}'`
                        if [ 0 -lt "$Abschlussfristposition" ]; then
                            insg=${#distributor_zeile}
                            Abschlussfrist=${distributor_zeile:16:$insg-33}
                            echo  ":" $Abschlussfrist 
                            continue
                        fi
                
                        Abruffristposition=`awk -v a="$distributor_zeile" -v b="<Abruffrist>" 'BEGIN{print index(a,b)}'`
                        if [ 0 -lt "$Abruffristposition" ]; then
                            insg=${#distributor_zeile}
                            Abruffrist=${distributor_zeile:12:$insg-25}
                            echo  ":" $Abruffrist
                            continue
                        fi               
                
                        Uebertrittposition=`awk -v a="$distributor_zeile" -v b="<Uebertritt>" 'BEGIN{print index(a,b)}'`
                        if [ 0 -lt "$Uebertrittposition" ]; then
                            insg=${#distributor_zeile}
                            Uebertritt=${distributor_zeile:12:$insg-25}
                            echo  ":" $Uebertritt
                            continue
                        fi   
doch das ist nicht die fortschittliche Art. Hier habe ich die Möglichkeit sobald man in der Section ist dort weiter Zeile für Zeile einzulesen, und dann wenn es fertig ist, dann macht er mit dem Einlesevorgang in der normalen Schleife weiter, das ging hier mit read distributor_zeile innerhalb der neuen Verschachtelung, doch wie geht das in der besseren Parsingvariante?

Wie bekomme ich das hin?

Danke schonmal...
Zuletzt geändert von safran am 17.04.2018 20:12:51, insgesamt 1-mal geändert.

Benutzeravatar
TRex
Moderator
Beiträge: 8318
Registriert: 23.11.2006 12:23:54
Wohnort: KA

Re: XML Parsen innerhalb einer Section mit shell

Beitrag von TRex » 17.04.2018 08:40:12

Im Prinzip suchst/schreibst du einen SAX-Parser [1] für die Shell. Ich will dir auch gar nicht ausreden, das komplett in einer Shell zu lösen, aber die Lösung wäre mit xmlstarlet/xmllint und xpath zum Suchen der Werte deutlich einfacher erreichbar.

[1] https://de.wikipedia.org/wiki/Simple_AP ... se_von_SAX
Jesus saves. Buddha does incremental backups.
Windows ist doof, Linux funktioniert nichtDon't break debian!Wie man widerspricht

Benutzeravatar
safran
Beiträge: 94
Registriert: 02.01.2008 04:46:07
Lizenz eigener Beiträge: Artistic Lizenz

Re: XML Parsen innerhalb einer Section mit shell

Beitrag von safran » 17.04.2018 12:04:49

Hallo,

ja das hatte ich gesehen, mich allerdings noch nicht da herangewagt bis dato. Da ich allerdings wohl öfter etwas mit XML zutun hab ist es ratsam dass ich mich da reinarbeite.
Danke für den Tipp.

<Portfolio>
<Produktgruppe>
<Gruppenname>345</Gruppenname>
<Zielvereinbarung>
<Provision abziel="006" abdatum="180501" bisdatum="300101" wdhab="01" wdbis="31">000020.00</Provision>
</Zielvereinbarung>
<Produkt>
<Abkuerzung>VFNP24</Abkuerzung>
<Produktname>234 Natur Plus 24</Produktname>
<Zielvereinbarung>
<Provision abziel="001" abdatum="180501" bisdatum="300101" wdhab="01" wdbis="31">000220.00</Provision>
</Zielvereinbarung>
<Provider>eer</Provider>
<Laufzeit>24</Laufzeit>
</Produkt>
<Produkt>
<Abkuerzung>VFNP12</Abkuerzung>
<Produktname>tas Plus 12</Produktname>
<Zielvereinbarung>
<Provision abziel="001" abdatum="180501" bisdatum="300101" wdhab="01" wdbis="31">000230.00</Provision>
</Zielvereinbarung>
<Provider>wwr</Provider>
<Laufzeit>12</Laufzeit>
</Produkt>
</Produktgruppe>
<Produktgruppe>
<Gruppenname>epr</Gruppenname>
<Zielvereinbarung>
<Provision abziel="016" abdatum="180501" bisdatum="300101" wdhab="01" wdbis="31">000120.00</Provision>
</Zielvereinbarung>
<Produkt>
<Abkuerzung>PR1</Abkuerzung>
<Produktname>Primo Pro</Produktname>
<Zielvereinbarung>
<Provision abziel="001" abdatum="180501" bisdatum="300101" quote1="0.80, 0128" quote2="0.20, 0628">000330.00</Provision>
<Provision abziel="005" abdatum="180501" bisdatum="300101" wdhab="01" wdbis="31">000030.00</Provision>
</Zielvereinbarung>
<Provider>23strom</Provider>
<Laufzeit>12</Laufzeit>
</Produkt>
</Produktgruppe>
</Portfolio>
Wie könnte ich denn das realisieren:

zB ich suche nach der Abkuerzung PR1.
Dann möchte ich alles was in den Section mit dem Produkt und Produktgruppe wo PR1 gefunden wurde, in dem Fall

<Produktgruppe>
<Gruppenname>epr</Gruppenname>
<Zielvereinbarung>
<Provision abziel="016" abdatum="180501" bisdatum="300101" wdhab="01" wdbis="31">000120.00</Provision>
<Provision abziel="020" abdatum="180501" bisdatum="300101" wdhab="01" wdbis="31">000060.00</Provision>
</Zielvereinbarung>
<Produkt>
<Abkuerzung>PR1</Abkuerzung>
<Produktname>Primo Pro</Produktname>
<Zielvereinbarung>
<Provision abziel="001" abdatum="180501" bisdatum="300101" quote1="0.80, 0128" quote2="0.20, 0628">000330.00</Provision>
<Provision abziel="005" abdatum="180501" bisdatum="300101" wdhab="01" wdbis="31">000030.00</Provision>
</Zielvereinbarung>
<Provider>23strom</Provider>
<Laufzeit>12</Laufzeit>
</Produkt>
</Produktgruppe>
</Portfolio>
entsprechenden Variablen in der Shell zuweisen. Wie würde das denn aussehen?

zB suche den Block wo PR1 steht bei Abkuerzung, dann bestimme entsprechend die Variable Gruppenname, die Arrays Zielvereinbarung, Produktname, Zielvereinbarungen Produkt, Provider usw?

Ich bin schon gewillt selber das Manual zu lesen, was ich auch gerade mache, aber vll weiss jemand wie das in ganz wenigen Zeilen geht.
Derzeit ist mein Skript über 500 Zeilen lang, ich denke man kann es auf unter 200 schrumpfen damit.

Benutzeravatar
heisenberg
Beiträge: 4123
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: XML Parsen innerhalb einer Section mit shell

Beitrag von heisenberg » 17.04.2018 12:21:59

Mal ein kleines Beispiel, damit Du siehst wie einfach XPATH sein kann:

Ich habe in Deine Testdaten mal ein zweites Produkt angehängt, dass man wirklich einen Filtereffekt sieht:

NoPaste-Eintrag40267

Das wäre der Befehl um den Namen des Produktes mit der Abkürzung PR1 zu bekommen:

Code: Alles auswählen

xmllint --xpath "//Produkt[Abkuerzung='PR2']/Produktname" datei.xml
xmllint macht selbst aber aus dem Ergebnis noch ein XML, was man durch einen sed los wird:

Code: Alles auswählen

xmllint --xpath "//Produkt[Abkuerzung='PR2']/Produktname"  datei.xml  | sed -r -e 's/.*>(.*)<.*/\1/'
Update: Gemäss Empfehlung von Safran(weiter unten), direkt als xpath-Funktion eingebunden:

Code: Alles auswählen

xmllint --xpath "//Produkt[Abkuerzung='PR2']/Produktname/text()"  datei.xml
Und im Übrigen würde ich für so etwas überhaupt kein Shellscript verwenden, sondern eine Scriptsprache(Python, Ruby, PHP,...). Dann musst du da nix in irgendwelche Shell-Variablen umbiegen, sondern hast den XML-Baum direkt voll zugreifbar als Datenstruktur in Deinem Programm, bzw. hast umfangreiche Funktionen, mit denen Du viel effektiver als mit reiner Textmanipulation Daten suchen und zugreifen kannst.
Zuletzt geändert von heisenberg am 18.04.2018 10:18:40, insgesamt 3-mal geändert.

Benutzeravatar
heisenberg
Beiträge: 4123
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: XML Parsen innerhalb einer Section mit shell

Beitrag von heisenberg » 17.04.2018 13:51:30

Kleines Ruby-Beispiel(braucht Debianruby und Debianruby-nokogiri):

Code: Alles auswählen

#!/usr/bin/env ruby

require 'nokogiri'

text = File.open("data.xml").read.gsub("\n","")

# Mit Slop-Modus ist die XML-Nutzung wirklich seeehr entspannt
# wenn auch nicht unbedingt guter, robuster Programmierstil
@data = Nokogiri::Slop(text)

# XPath-Abfrage der gesuchten Produkte
@data.xpath("//Produkt[Abkuerzung='PR1']").each do |produkt|

        # Zugriff auf Kind-Elemente und Extraktion des Wertes via XPath-Text-Funktion
        puts "Produktname   : #{produkt.Produktname.text}"
        puts "Abkuerzung    : #{produkt.Abkuerzung.text}"
        
        # Ermittle das Eltern-Element via XPath und hole daraus die Produktgruppe
        parent = @data.xpath("//*[Produkt/Produktname='"+produkt.Produktname.text+"']")[0]
        puts "Produktgruppe : #{parent.Gruppenname.text}"
        
        x=0
        # Iteriere über Kind-Element-Array        
        produkt.Zielvereinbarung.Provision.each do |provision|
                x+=1
                puts "Provision-#{x}"
                # Zugriff auf Attribute eines XML-Elementes
                puts "  Ab Ziel   : #{provision["abziel"]}"
                puts "  Ab Datum  : #{provision["abdatum"]}"
                puts "  Bis Datum : #{provision["bisdatum"]}"
        end
end
Ausgabe:

Code: Alles auswählen

Produktname   : Primo Pro
Abkuerzung    : PR1
Produktgruppe : epr
Provision-1
  Ab Ziel   : 001
  Ab Datum  : 180501
  Bis Datum : 300101
Provision-2
  Ab Ziel   : 005
  Ab Datum  : 180501
  Bis Datum : 300101
Zuletzt geändert von heisenberg am 17.04.2018 15:09:42, insgesamt 2-mal geändert.

Benutzeravatar
Meillo
Moderator
Beiträge: 9227
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: XML Parsen innerhalb einer Section mit shell

Beitrag von Meillo » 17.04.2018 15:06:28

heisenberg hat geschrieben: ↑ zum Beitrag ↑
17.04.2018 12:21:59
Und im Übrigen würde ich für so etwas überhaupt kein Shellscript verwenden, sondern eine Scriptsprache(Python, Ruby, PHP,...). Dann musst du da nix in irgendwelche Shell-Variablen umbiegen, sondern hast den XML-Baum direkt voll zugreifbar als Datenstruktur in Deinem Programm, bzw. hast umfangreiche Funktionen, mit denen Du viel effektiver als mit reiner Textmanipulation Daten suchen und zugreifen kannst.
Das will ich unterstreichen: Verwende eine moderne Scriptsprache dafuer und arbeite mit einer XML-Datenstruktur (anstatt mit Textoperationen). Das ist dann nicht nur viel lesbarer und korrekter, sondern auch einfacher verstaendlich (nachdem man sich ein bisschen eingearbeitet hat). heisenberg war ja schon so freundlich, dir ein Beispielprogramm zu erstellen. :THX:
Use ed once in a while!

Benutzeravatar
heisenberg
Beiträge: 4123
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: XML Parsen innerhalb einer Section mit shell

Beitrag von heisenberg » 17.04.2018 16:30:25

Ich habe auch mal einen Versuch unternommen ein ausgewachsenes Programm via Bash zu implementieren.

Das Ergebnis:

Bild

Der Klügere gibt nach.

Die Probleme sind da:
  • Der Umgang mit globalen und lokalen Variablen erfordert sehr viel Disziplin(Weil global der Standard-Gültigkeitsbereich ist)
  • Sichere strukturierte Programmierung geht auf Kosten der Performance(Subshells)
  • Man bekommt viele externe Abhängigkeiten, weil die Bash an sich in Ihrer Funktionalität beschränkt ist(D. h. bei einer Portierung/Migration auf ein anderes System kann viel Arbeit auf einen zukommen).
  • Performance sinkt prinzipbedingt stark ab bis zur Unbenutzbarkeit ab einer gewissen Projektgrösse.
  • Sehr geringe Programmiereffizienz im Vegleich zu vollumfänglichen Programmiersprachen.

Benutzeravatar
safran
Beiträge: 94
Registriert: 02.01.2008 04:46:07
Lizenz eigener Beiträge: Artistic Lizenz

Re: XML Parsen innerhalb einer Section mit shell

Beitrag von safran » 17.04.2018 18:11:11

Hallo, vielen Dank an alle.
Mal ein kleines Beispiel, damit Du siehst wie einfach XPATH sein kann
super lieber Heisenberg, damit komm ich weiter.

Ja warscheinlich ist das Anliegen zu heftig für die Shell.

Ganz kurz zum Hintergrund des Projekts: Wie man vll erkannt hat geht es um den Vertrieb, Verträge, Abrufe, Provisionen, Partner usw.

Anstelle jetzt alles immer nur in die genutzte Software einzupfegen die auch der Buchhalter nutzt, hatte ich vor die Art und Weise wie ich die Dokumente in meiner Dateistruktur speicher so informativ zu gestalten, dass man nur aus dieser, zzgl. einigen XML Dateien mit Informationen, Zahlungsfälligkeiten und -forderungen ermitteln kann.

Dabei wollte ich die Shell bzw. Bash nutzen um diese auch einmal für etwas Anspruchsvolleres zu nutzen, man sagt ihr ja nach sie sei das Schweizer Messer von Linux.

Das klappt soweit auch gut.

Dann bin ich auf XML gekommen und joa dachte mir warum nicht in XML machen, ist ja besser als wenn ich da irgendwelche Textdateien parse die keine genormte Syntax haben.

Und joa, jetzt führt mich der Weg wohl weiter zu Python. Ich meine ich bring einige Programmiererfahrung mit (Assembler / SPS / C / C++ / QT) dann ist es jetzt wohl an der Zeit dafür sich das zu geben. :)

Für C bzw C++ ist das Projekt eher nichts oder?

Danke nochmal, ich experimentier derzeit an xmllint und xpath.

Eine kleine Frage die etwas offtopic ist: Angenommen jeden Tag kommen neue Datensätze dazu, wie würde man sowas angehen, dass nicht immer alle Ordner komplett durchforstet werden, sondern man genau sieht was sich geändert hat am Dateisystem und nur die Neuerungen implementiert (Neuerungen wären neue hinzukommende Dateien in der Ordnerstruktur)

Das ganze wird jetzt doch noch mehr Zeit in Anspruch nehmen als angenommen :-D

kleiner Hinweis:

Code: Alles auswählen

xmllint --xpath "//Produkt[Abkuerzung='PR2']/Produktname"  datei.xml  | sed -r -e 's/.*>(.*)<.*/\1/' 
bekommt man auch ohne pipe hin mit

Code: Alles auswählen

xmllint --xpath "//Produkt[Abkuerzung='PR2']/Produktname/text()"  datei.xml
Zuletzt geändert von safran am 18.04.2018 03:36:38, insgesamt 1-mal geändert.

Benutzeravatar
Meillo
Moderator
Beiträge: 9227
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: XML Parsen innerhalb einer Section mit shell

Beitrag von Meillo » 17.04.2018 18:15:18

Unterschiedliche Sprachen und Programmierumgebungen sind fuer unterschiedliche Anwendungsfaelle geeignet.
Use ed once in a while!

Benutzeravatar
safran
Beiträge: 94
Registriert: 02.01.2008 04:46:07
Lizenz eigener Beiträge: Artistic Lizenz

Re: XML Parsen innerhalb einer Section mit shell

Beitrag von safran » 17.04.2018 19:13:15

das klingt plausibel, nen Industrieroboter würde ich auch nicht mit PHP laufen lassen :-)

Benutzeravatar
safran
Beiträge: 94
Registriert: 02.01.2008 04:46:07
Lizenz eigener Beiträge: Artistic Lizenz

Re: [geloest] XML Parsen innerhalb einer Section mit shell

Beitrag von safran » 18.04.2018 01:41:19

http://zvon.org/xxl/XPathTutorial/Output/examples.html das tutorial kann ich sehr empfehlen, da kommt viel drin vor was nützlich ist...

Antworten