txt nach csv Konverter

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
higginsa1
User
Beiträge: 11
Registriert: Freitag 16. Juni 2017, 07:24

Hallo, bin neu in Phyton habe aber Programmiererfahrung. Ich möchte eine Text Datei in eine CSV Datei wandeln.

In der Text Datei stehen Zeilen eines Kontoauszuges, ein Eintrag fängt mit einer Zeile an welche ein "+" oder "-" UND ein "," enthält.

Beispiel:
D Gut SEPA + 10,00

Danach kommen Zeilen mit dem Überweisungstext, das geht solange bis eine Zeile vier "." enthält.

Beispiel:
20.05. 20.05.

Beispiel eines Überweisungsteils:

Gutschr.SEPA + 10,00
Max Müller Referenz ryxsdfasfwef Auftraggeber
Identifikation sdfgsdfg Verwendungszweck
Klassenkasse5x Holger Schmidt
17.05. 17.05.

Daraus soll folgendes werden:

Gutschr.SEPA + 10,00; Max Müller Referenz ryxsdfasfwef Auftraggeber Identifikation sdfgsdfg Verwendungszweck Klassenkasse5x Holger Schmidt; 7.05. 17.05.

Zwischen den einzelnen Überweisungen stehen noch andere Zeilen welche ausgefiltert werden sollen

Beispiel:

Gutschr.SEPA + 10,00
Zeile 1
Zeile 2
Zeile 3
17.05. 17.05.

Zeile Müll 1
Zeile Müll 2
Zeile Müll 3

Gutschr.SEPA + 20,00
Zeile 1
Zeile 2
Zeile 3
17.05. 17.05.

Daraus soll folgendes werden

Gutschr.SEPA + 10,00; Zeile 1 Zeile 2 Zeile 3; 17.05. 17.05.
Gutschr.SEPA + 20,00; Zeile 1 Zeile 2 Zeile 3; 17.05. 17.05.

Im letzten Schritt sollen noch Zeilen manipuliert werden, sollen Strings ausgefiltert bzw. ergänzt werden

Gutschr.SEPA + 20,00
Zeile 1
Zeile 2
Zeile 3
17.05. 17.05.

Das Gutschr.SEPA soll weg, Zeile soll weg und hinter dem Datum soll das Jahr ergänzt werden

+ 20,00; 1 2 3; 17.05.2017

Jemand zufällig ein Beispiel zur Hand?
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Liest sich für mich so, als könntest Du einfach die *.txt zeilenweise einlesen und mit für CSV typischen Trennzeichen in eine *.csv schreiben. Wie man eine Datei zeilenweise liest und schreibt, dazu sollten sich genügend Beispiele im Web finden lassen, selbst hier im Forum bei einer schnellen Suche:

viewtopic.php?t=38918
higginsa1
User
Beiträge: 11
Registriert: Freitag 16. Juni 2017, 07:24

das 1:1 rüber kopieren ist ein dreizeile, das habe ich, aber die manipulationen und das umkopieren 3Zeilen in eine, etc...
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Das sollte sich mit join() erledigen lassen, wenn Du zuerst die Zeilen in einen Tupel oder in eine Liste schreibst, denke ich mir, habe es aber noch nicht probiert.

https://www.tutorialspoint.com/python/string_join.htm
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Reguläre Ausdrücke sind dein Freund. Schreib dir welche, die zB in einem Unit Test belegen, dass sie die Verschiedenen Zeilen erkennen, also die Sepa Zeile genauso wie die Doppel-Datums-Zeile.

Daraus lässt sich dann zB mit einem Generator gut ein parser schreiben, der Blöcke von Zeilen von Sepa bis Doppeldatum zusammenfasst und zurück gibt.
BlackJack

Nur weil es noch nicht erwähnt wurde: Die Standardbibliothek hat ein Modul für CSV-Dateien. Wir wissen ja nicht was in den Zeilen wirklich vorkommen kann, darum würde ich da nicht einfach ein ``';'.join(…)`` machen.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@higginsa1: das Format der Text-Datei ist sehr fragil. Kommen wirklich beliebig viele Überweisungstext-Zeilen oder sind es immer 3? Im Überweisungstext kann ja auch eine Zeile der Art »21.04. 05.05.« vorkommen. Da wäre es vielleicht hilfreicher auch den Müll zu parsen, wenn der eine feste Struktur hat. Im einen Beispiel schreibst Du »D Gut SEPA« im anderen »Gutschr.SEPA«. Die meisten Banken bieten es an, Kontodaten schon per CSV herunterzuladen. Woher kommt die Textdatei und warum verwendest Du sie, statt schon CSV?
BlackJack

@Sirius3: Eine Zeile '21.04. 05.05.' kann IMHO nicht vorkommen weil zumindest laut Beispielen die Zeilen zwischen Start und Endzeile alle mit einem Präfix anfangen, der ja auch entfernt werden soll. Ist halt die Frage wie die tatsächlichen Daten aussehen.
higginsa1
User
Beiträge: 11
Registriert: Freitag 16. Juni 2017, 07:24

Die Anzahl der Zeilen ist leider unterschiedlich, kein festes Muster, den Anfang mit + oder - UND , habe ich mir als Startmarker überlegt, als Ende habe ich mir 4x . überlegt, es ist auch nicht ausgeschlossen, dass in dem Text mal + oder - vorkommt, dass muss ich dann später selber parsen.

Aber wie scheibe ich das in Phyten, dies soll, nach Hello World und Files kopieren mein erstes Python Programm werden. Könnte es auch als Excel Macro schreiben, aber Python hat mich neugierig gemacht. Als IDE nehme ich PhyCharm
BlackJack

@higginsa1: Was heisst „kein festes Muster“ und Du hast Dir Sachen ausgedacht? Wenn sich keine formalen Kriterien mindestens für Start und Endzeile eines Datensatzes formulieren lassen, dann kann man das nicht per Programm lösen. Und die müssen auch eindeutig sein, also man muss eine Funktion schreiben können die eine Startzeile von einer ”Müllzeile” zwischen den Datensätzen eindeutig unterscheiden kann, und man muss eine Funktion schreiben können, die eine Endzeile eindeutig von den Zeilen innerhalb eines Datensatzes unterscheiden kann.

Ein Komma und + oder - in einer Zeile und vier Punkte in einer Zeile sind Kriterien die *sehr* fragil klingen.

Wie sehen denn die tatsächlichen Daten aus? Wo kommen die her? Warum sehen die so aus?
higginsa1
User
Beiträge: 11
Registriert: Freitag 16. Juni 2017, 07:24

das ist ja die Logik! Start bei + und , ende bei 4x . dieses LOGIK habe ich mir ausgedacht.

kann mir keiner sagen wie ich mit Python in einer Zeile nach einem Textmuster suchen kann und 3...x Zeilen in einer Zeile in einer neuen Datei speichern kann???

Ich werde wohl googeln müssen
nezzcarth
User
Beiträge: 1635
Registriert: Samstag 16. April 2011, 12:47

higginsa1 hat geschrieben: Ich werde wohl googeln müssen
Das ist eine Möglichkeit. Eine andere wäre, den Thread noch mal zu lesen, denn du hast eigentlich bereits die passenden Antworten bekommen. Das wäre einmal, dass du nach Möglichkeit ein anderes Format wählen (und im besten Fall, wenn das geht, nicht selbst ausdenken) solltest, wenn du das wirklich beeinflussen kannst, da das, was du da hast nicht besonders stabil ist. Und andererseits wäre das die Verwendung von regulären Ausdrücken, wenn du wirklich daran festhalten möchtest oder musst. Wenn du wirklich Unterstützung haben möchtest, solltest du außerdem die Gegenfragen beantworten, wenn es geht, insbesondere: Wo kommen die Daten her?
Zuletzt geändert von nezzcarth am Samstag 17. Juni 2017, 08:15, insgesamt 2-mal geändert.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Die Frage ist eher, wer soll sich für Dich einen halben oder ganzen Tag vertrödeln, um den oder die passenden regulären Ausdrücke zu finden?
Mit einem Punkt kommst Du nicht weit, wenn, so solltest Du nach kompletten Ausdrücken suchen. Für das Ende könnte es etwas in dieser Art sein, jedoch nur fürs Ende und die Betonung liegt auf "könnte".

Code: Alles auswählen

muster = "(.*)\d{2}\.\d{2}\.\s{1,2}\d{2}\.\d{2}\."
higginsa1
User
Beiträge: 11
Registriert: Freitag 16. Juni 2017, 07:24

ich glaube wer öfters etwas in python schreibt der benötigt hier keinen halben Tag

Ich habe dies in 30 Minuten ohne irgendwelche python Kenntnisse erstellt, allerdings habe ich noch nicht das passende Mittel gefunden die ANZAHL der Zeichen zu finden, lediglich die Position, dass passt natürlich nicht, gibt es da nichts passendes? oder muss ich mir eine eigene Funktion basteln?

Anscheinend kopiert er auch das Line Feed mit? Das muss natürlich weg...

Code: Alles auswählen

def convertFile():
    txt_file = open("PB_KAZ_KtoNr_0790130119_04-06-2016_0709.txt", "r")
    csv_file = open("PB_KAZ_KtoNr_0790130119_04-06-2016_0709.csv", "w")

    LineCnt = 1
    OutLine = ""

    for InLine in txt_file:
        numberPlus = InLine.rfind("+")
        numberMinus = InLine.rfind("-")
        numberComma = InLine.rfind(",")
        numberFullStop = InLine.rfind(".")

        if (((numberPlus > 0) or (numberMinus > 0)) and (numberComma > 0)) or (numberFullStop > 4):
            OutLine += InLine

        if (numberFullStop > 4):
            csv_file.write(OutLine)
            #print OutLine.rstrip()
            OutLine = ""

        LineCnt = LineCnt + 1

    txt_file.close()
    csv_file.close()
Zuletzt geändert von Anonymous am Samstag 17. Juni 2017, 08:33, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@higginsa1: Nicht googeln sondern in der Python-Dokumentation nachsehen was für Methoden Zeichenketten bieten und reguläre Ausdrücke wurden ja auch schon genannt. Das wäre das `re`-Modul. Was letztlich wohl nötig sein wird um die gewünschten Daten aus den Start- und Endzeilen zu holen wenn wirklich alles andere was da noch vorkommen kann als unsicher gilt.

Auf jeden Fall findest Du bei den Zeichenkettenmethoden auch etwas zum zählen.

Es geht nicht darum das Dir keiner helfen *kann*, sondern eher das es anscheinend keiner *möchte* solange hier so ziemlich jeder noch davon überzeugt ist, dass das so nicht funktionieren wird, weil das beides keine robusten Entscheidungskriterien sind. Solange Du nicht mehr über die Eingabedaten sagst, ist nicht klar ob es sich überhaupt lohnt zum nächsten Schritt überzugehen.

Gib mal bitte eine stichhaltige Begründung warum '+'/'-' und ',' nicht in den ”Müllzeilen” vorkommen kann, und warum vier Punkte nicht in den Zeilen zwischen Start- und Endzeile einer Überweisung vorkommen können.

Ansonsten ist das grobe Vorgehen in Python nicht anders als in anderen, ähnlichen Programmiersprachen.

Zum gezeigten Quelltext: Halte Dich bei der Schreibweise der Namen am besten von Anfang an an den Style Guide for Python Code.

Keine Abkürzungen in Namen. Also beispielsweise `count` statt `cnt`.

Die Dateinamen würde ich als Argumente der Funktion übergeben. Und den Namen der Ausgabedatei eventuell auch per Code erstellen, wenn der sich nur durch die Dateinamenserweiterung unterscheiden soll. Da gibt's im `os.path`-Modul hilfreiche Funktionen.

Dateien sollte man mit ``with`` öffnen um das schliessen auf jeden Fall zu garantieren.

Die ``if``-Bedingunge enthalten zu viele Klammern. Die um die Vergleichsoperationen können jeweils wegfallen. Dann sieht man die *wichtigen* Klammern auch besser. Ich würde die Bedingung auch nicht so lang werden lassen sondern Teilausdrücke vorher an Namen binden, so dass man besser versteht was da eigentlich geprüft wird. Das ist lesbarer und sprengt auch nicht die 80 Zeichen Grenze. Zum Vergleich:

Code: Alles auswählen

            if is_start_line or is_end_line:

            # vs.

            if (((numberPlus > 0) or (numberMinus > 0)) and (numberComma > 0)) or (numberFullStop > 4):
`OutLine` ist kein passender Name, denn der nimmt ja offensichtlich mehr auf als *eine* Zeile. Zudem sollte man Zeichenketten nicht durch wiederholtes ``+=`` erweitern — da Zeichenketten unveränderbar sind, muss dabei im ungünstigen Fall immmer der alte plus dem neuen Inhalt zu einer neuen Zeichenkette zusammenkopiert werden. Da werden dann im Laufe der Zeit viele Daten unnötig im Speicher herumgeschoben. Üblicherweise sammelt man Teilzeichenketten in einer Liste und fügt die am Ende mit der `join()`-Methode auf einer Zeichenkette mit dem Trenner zusammen. Wobei die Trennzeichenkette auch die leere Zeichenkette sein darf.

Letztendlich willst Du ja aber sowieso keine Zeilen schreiben, sondern Listen mit Werten für die einzelnen Spalten pro Zeile. Wie gesagt gibt es für CSV-Dateien ein Modul in der Standardbibliothek das sich um all die Randfälle kümmert, die in CSV vorgesehen sind.

`LineCnt` wird nicht verwendet. Wenn man diesen Wert bräuchte, würde man den aber auch nicht von Hand mitführen, sondern die `enumerate()`-Funktion verwenden.

Ich würde mehr Funktionen empfehlen. Mindestens mal das einlesen und schreiben der Daten trennen. Aber auch das Testen auf Start- und Endzeile, beziehungsweise das extrahieren der relevanten Daten würde ich in eigene Funktionen auslagern. Sonst wird diese Konvertierungsfunktion ziemlich unübersichtlich werden. Da fehlt ja noch das sammeln der Zeilen zwischen Start- und Endzeile und das Aufbereiten der ganzen Zeilen als Datensatz für die CSV-Datei.
higginsa1
User
Beiträge: 11
Registriert: Freitag 16. Juni 2017, 07:24

gebe dir ja recht das die metriken nicht serientauglich sind, darauf kommt es mir aber jetzt nicht an. Übrigens umfasst OutLine nur eine Zeile und zwar die neue die ausgegeben werden soll, ist aber auch egal

Gibt es in Pythen einen einfachen Befehl, welcher mir die Anzahl meines gesuchten Zeichens in einer Zeichenkette ausgibt?
BlackJack

@higginsa1: Nein, einen Befehl gibt's nicht, aber Zeichenketten haben eine Methode die das macht.

Da Du laut den Beispielen aber letztendlich auch an den Werten interessiert bist, würde ich das `re`-Modul empfehlen, damit kannst Du das erkennen einer Start-/Endzeile und heraus holen der Teilzeichenketten die von Interesse sind, in einem Schritt machen. Zudem wird der Test auch ein klein wenig genauer wenn Du nach Plus oder Minus, gefolgt von einem oder mehr Leerzeichen, gefolgt von einer oder mehr Ziffern, gefolgt von einem Komma, gefolgt von genau zwei Ziffern suchst, als wenn nur ein Plus oder Minus und ein Komma *irgendwo* in der Zeile vorkommen.
higginsa1
User
Beiträge: 11
Registriert: Freitag 16. Juni 2017, 07:24

das re Modul werde ich mir auch noch anschauen, vorerst habe ich folgenden Code, macht auch schon annähernd was es soll.

Folgende Probleme sind noch da...
... er packt das Line Feed mit in die OutLine, dieses würde ich gerne mit ";" ersetzen
... danach werde ich noch etwas Text ersetzen, mit re.replace

dann sollte es schon passen

In der nächsten Ausbaustufe werde ich eine exe erstellen mit 2 Übergabeparametern Filename und Jahr

Sorry dass ich nur ab und zu schreibe, bin nebenbei im Garten am schippen.

Code: Alles auswählen

def numberElements(InString, SearchString):
    RetVal = 0

    for x in range(0, (len(InString)-1)):
       if InString[x] == SearchString:
           RetVal += 1

    return RetVal


def convertFile():
    txt_file = open("PB_KAZ_KtoNr_0790130119_04-06-2016_0709.txt", "r")
    csv_file = open("PB_KAZ_KtoNr_0790130119_04-06-2016_0709.csv", "w")

    LineCnt = 1
    OutLine = ""
    FoundEntry = False

    for InLine in txt_file:
        # analyse line
        numberPlus = numberElements(InLine, "+")
        numberMinus = numberElements(InLine, "-")
        numberComma = numberElements(InLine, ",")
        numberFullStop = numberElements(InLine, ".")

        # start of entry
        if (numberPlus > 0 or numberMinus > 0) and numberComma > 0:
            FoundEntry = True

        # assemble outline
        if FoundEntry == True:
            OutLine += InLine

        #end of entry
        if (numberFullStop > 3):
            csv_file.write(OutLine)
            print(OutLine)
            OutLine = ""
            FoundEntry = False

        LineCnt = LineCnt + 1

    txt_file.close()
    csv_file.close()
Zuletzt geändert von Anonymous am Samstag 17. Juni 2017, 12:01, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
higginsa1
User
Beiträge: 11
Registriert: Freitag 16. Juni 2017, 07:24

OutLine += InLine.replace("\n", ";")
BlackJack

@higginsa1: Die `numberElements()`-Funktion ist überflüssig, so etwas gibt es wie schon gesagt als Methode auf Zeichenketten.

Zudem ist die extremst umständlich geschrieben und allgemein sogar falsch, weil das letzte Zeichen nicht berücksichtigt wird.

``for i in range(len(sequenze)):`` ist in Python ein „anti pattern“. Man kann *direkt* über die Elemente von Sequenzen wie Zeichenketten, Listen, … iterieren, da braucht man keinen Umweg über einen Index.

`RetVal` ist eine scheussliche Komposition aus zwei Abkürzungen. Wenn man es schon supergenerisch haben möchte, dann bietet sich `result` an.

Was soll `InString` bedeuten? Das `In` macht da keinen Sinn.

`numberElements` ist auch kein guter Name. Das würde ich mit „nummeriere Elemente“ übersetzen, hier wird aber etwas gezählt, also `count_elements()`.

Zählen brauchst Du doch auch nur bei Punkten. Bei den anderen Zeichen reicht ein einfacher Test mit ``in`` ob sie vorhanden sind oder nicht.

`LineCnt` ist immer noch mit dem Namen da, und immer noch manuell hochgezählt. (Und immer noch nicht verwendet.)

Bei den `numberThing`-Werten habe ich auch ein wenig Bauchschmerzen bei den Namen, denn das müsste eigentlich `number_of_things` heissen, oder etwas weniger umständlich `thing_count`. `number_of_plusses` vs. `plus_count`.

Und wie ebenfalls schon gesagt: Bastel da nicht selbst schon eine CSV-Zeile zusammen, sondern trenne das einlesen und das schreiben in die Zieldatei. CSV-Dateien möchte man nicht selber schreiben, denn ganz so einfach wie man denkt sind die nicht. Nichts hindert jemanden beim Überweisungstext ein Semikolon oder Anführungszeichen zu schreiben. Das `csv`-Modul kümmert sich um solche Sachen.

Die Funktion wird noch komplexer werden, da sollte man jetzt schon Sachen rausnehmen die thematisch getrennt sind. Du willst zudem nicht mit `replace()` auf der Ausgabezeile rumpfuschen, sondern auf den einzelnen Teilen wenn sie verarbeitet werden und *bevor* sie in der Ausgabezeile beziehungsweise in der oder den Datenstrukturen landen. Die Zeilen zwischen Start- und Endzeile muss man ja erst einmal sammeln und dann zusammenfügen bevor man sie als *einen* Wert zum Ausgabedatensatz hinzufügt.

Wobei sich das mit dem `replace()` sowieso komisch anhört und mit regulären Ausdrücken auch hinfällig wird. Denn eigentlich will man ja nichts entfernen, sondern gezielt genau das aus der Zeile holen was einen interessiert.

`FoundEntry` ist im Grunde redundant wenn man `OutLine` den Wert `None` zuweist wenn man sich ausserhalb einer Kontobewegung befindet und darauf dann testet.
Antworten