Textdatei auslesen und dabei Informationen Zeilenweise und getrennt speichern

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von KingLM97.

    Textdatei auslesen und dabei Informationen Zeilenweise und getrennt speichern

    Heyho :)

    Ich habe hier eine Textdatei, welche ich Zeilenweise auslese. Hierbei handelt es sich um einen exportierten Chat aus WhatsApp.
    In fast jeder Zeile stehen immer die selben Informationen in immer gleicher Reihenfolge.
    Hier ein Beispiel:
    Spoiler anzeigen

    Quellcode

    1. 23.04.16, 00:03 - Peter: Beispieltext
    2. 23.04.16, 00:04 - Peter: Beispieltext
    3. 23.04.16, 00:05 - Hans: Beispieltext
    4. 23.04.16, 01:37 - Dieter: Beispieltext
    5. Beispieltext in neuer Zeile
    6. Beispieltext in neuer Zeile
    7. 23.04.16, 13:15 - Ulrich‬: Beispieltext
    8. 23.04.16, 13:25 - Arno: Beispieltext
    9. Beispieltext in neuer Zeile
    10. 23.04.16, 14:25 - Arno‬: Beispieltext
    11. 23.04.16, 14:26 - Max‬: Beispieltext
    12. 23.04.16, 14:26 - Max‬: Beispieltext
    13. 23.04.16, 14:29 - Reinhard: Beispieltext


    Es sollte eigentlich ersichtlich sein, aber am Anfang steht das Datum, dann die Uhrzeit gefolgt vom Absender und danach die Nachricht. Wie man sieht kann es vorkommen, dass die Nachricht in der nächsten Zeile weitergeht. Das kann vorkommen, wenn in einer Nachricht manuell eine neue Zeile angefangen wurde (wie man es hier mit der Entertaste macht).

    Zuerst habe ich es mit RegEx probiert (wobei ich gestehen muss, dass ich null Erfahrung mit RegEx habe und mir im Internet das Pattern rausgesucht habe und es anschließend nach dem Motto "Trial and Error" angepasst habe) und mir folgende Funktion gebaut:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Shared Function RegExMatch(ByVal Line As String, ByVal Pattern As String) As String
    2. Dim m As Match = Regex.Match(Line, Pattern, RegexOptions.IgnoreCase)
    3. If m.Groups.Count = 0 Then
    4. Return String.Empty
    5. End If
    6. Return m.Groups(0).Value
    7. End Function

    Und dazu meine Patterns:

    Datum:
    \d{2}(\.)\d{2}(\.)\d{2}
    Uhrzeit:
    (0.|[1][0-9]|[2][0-3])(\:)[0-5][0-9]((\:)[0-5][0-9]|)(\,[0-5][0-9]|)
    Absender:
    (-([^-])*:)

    Das Funktioniert auch sehr gut muss ich sagen... :thumbsup:
    ...bis auf die Ausnahme, dass wenn die Nachricht in einer neuen Zeile weitergeht, aber mit einem Datum bzw. einer Uhrzeit beginnt, er das natürlich erkennt und als neue Nachricht interpretiert, was natürlich die Daten verfälscht (im Programm). ;(

    Dazu habe ich folgende Logik um den Chat auszulesen:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub bgwAuslesen_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgwAuslesen.DoWork
    2. Dim row = DirectCast(e.Argument, WhatsApp.WhatsAppChatRow)
    3. Dim Inhalt = IO.File.ReadAllLines(row.Pfad).ToList
    4. _IstWertFürProgressbar = True
    5. bgwAuslesen.ReportProgress(Inhalt.Count)
    6. For i As Integer = 0 To Inhalt.Count - 1
    7. Dim line = Inhalt(i)
    8. bgwAuslesen.ReportProgress(i)
    9. If line.Contains("Nachrichten in diesem Chat sowie Anrufe sind jetzt mit Ende-zu-Ende-Verschlüsselung geschützt. Tippe für mehr Infos.") = False Then
    10. If line.Contains("<Medien weggelassen>") = False Then
    11. Dim Datum = ChatHelfer.RegExMatch(line, "\d{2}(\.)\d{2}(\.)\d{2}")
    12. Dim Uhrzeit = ChatHelfer.RegExMatch(line, "(0.|[1][0-9]|[2][0-3])(\:)[0-5][0-9]((\:)[0-5][0-9]|)(\,[0-5][0-9]|)")
    13. Dim Absender = ChatHelfer.RegExMatch(line, "(-([^-])*:)").Replace("-", "").Replace(":", "")
    14. Dim NachrichtKopf = $"{Datum}, {Uhrzeit} - {Absender}:"
    15. If String.IsNullOrWhiteSpace(Datum) OrElse String.IsNullOrWhiteSpace(Uhrzeit) OrElse String.IsNullOrWhiteSpace(Absender) Then
    16. WhatsAppDataset.Nachricht.Last.Text &= $"{Environment.NewLine}{line}"
    17. Else
    18. WhatsAppDataset.Nachricht.AddNachrichtRow(Date.Parse(Datum), TimeSpan.Parse(Uhrzeit), Absender, line.Substring(NachrichtKopf.Length), row)
    19. End If
    20. End If
    21. End If
    22. Next
    23. row.IstAusgelesen = True
    24. End Sub


    In der If-Abfrage ab Zeile #17 überprüfe ich, ob irgendwas von den Werten "leer" ist (und interpretiere es demnach nicht als neue Nachricht!) und füge es im Dataset der letzten Nachricht bei.

    Da ich eben nun das Problem habe, dass er die Nachricht als neue Interpretiert, habe ich versucht das ganze mittels SubString auszulesen. Folgend die Funktion dafür:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Shared Function SubStringMatch(ByVal Line As String, ByVal ss As SubString) As String
    2. Dim result As String = String.Empty
    3. Select Case ss
    4. Case SubString.Datum
    5. result = Line.Substring(0, 8)
    6. Case SubString.Uhrzeit
    7. result = Line.Substring(10, 5)
    8. Case SubString.Absender
    9. Dim absenderAnfang = Line.Substring(18)
    10. For i As Integer = 0 To absenderAnfang.Length
    11. If absenderAnfang(i).Equals(":"c) Then
    12. result = Line.Substring(18, i)
    13. Exit For
    14. End If
    15. Next
    16. Case SubString.Nachricht
    17. Dim NachrichtenKopf = $"{Line.Substring(0, 8)}, {Line.Substring(10, 5)} - {New Func(Of String)(Function() As String
    18. Dim absenderAnfang = Line.Substring(18)
    19. Dim r As String = String.Empty
    20. For i As Integer = 0 To absenderAnfang.Length
    21. If absenderAnfang(i).Equals(":"c) Then
    22. r = Line.Substring(18, i)
    23. Exit For
    24. End If
    25. Next
    26. Return r
    27. End Function).Invoke}: "
    28. result = Line.Substring(NachrichtenKopf.Length)
    29. End Select
    30. Return result
    31. End Function


    Das bringt mir aber noch mehr Probleme, denn wenn die Nachricht der neuen Zeile weniger Zeichen hat als ich im SubString abfrage, stürzt es natürlich ab. Ich könnte es mit Try-Catch Abfangen, String.Empty zurückgeben und dann daran herausfinden, ob es eine neue Nachricht ist oder nicht. Aber das würde wieder in vielen If-Abfragen Enden, so meine Befürchtung.


    Das ist viel Text und rumgefasel von mir auf einmal, während ich den Text geschrieben habe kamen und gingen neue Ideen, deswegen könnte er ein wenig schwer zu verstehen sein. Ich bitte um Entschuldigung.

    Wenn jemand eine Idee hat, wie ich das besser angehen könnte, so möge er hervortreten.
    Einen angenehmen Rest-Abend wünsche ich.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „KingLM97“ () aus folgendem Grund: falschen Code kopiert

    als erstes ist .ReadAllLines() wohl nicht angebracht, da dadurch Datensätze zerschnitten werden, wenn sie einen ZeilVorschub ine Nachricht enthalten.
    Da musst .ReadAllText() nehmen, und wirst wohl einen listigen Regex entwickeln müssen, der die Datensätze korrekt und jeweils komplett matcht.
    Denkbar wäre was, was ZeilVorschub, Datum, Minus-Zeichen, Name, Doppelpunkt, beliebigenText erwartet.

    Und 100%ig sicher kann das prinzipiell nicht werden, aber immerhin höchst unwahrscheinlich, dass der beliebigeText-Abschnitt Text enthält, welcher ebenfalls dem soeben skizzierten Muster entspricht.

    Son Regex schüttelt man natürlich nicht aussm Ärmel, sondern muss man mit einem RegexTester entwickeln.
    Du kannst dir Regextester - OpenSource runterladen und selbst bischen probieren.

    ps: deine RegexMatch-Methode ist unnötig - was die leistet, leistet Regex auch ohne die, und deutlich besser.

    Sry - ich fürchte, das hilft dir alles nicht viel weiter, aber sind noch weitere Problempunkte:
    Etwa dass das im NebenThread erfolgen soll, wird so wahrscheinlich nicht funzzen. Weil Manipulationen an gebundenen Datasets sind immer auch indirekte Zugriffe auf die angebundenen Controls, und somit eiglich unzulässig.
    Solch stürzt zwar nicht ab, zeigt aber auch nicht richtig an.

    Besser wäre, das Parsen überhaupt erstmal zu lösen, bevor man da mit Nebenläufigkeit was anfängt.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    @KingLM97 Ich würde da mit ReadAllLines() arbeiten.
    Beginnt eine Zeile mit Datum und Uhrzeit, ist sie ein neuer Datensatz.
    Wenn nicht, ist sie Appendix des letzten Datensatzes.
    Feddich.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    Beginnt eine Zeile mit Datum und Uhrzeit, ist sie ein neuer Datensatz.Wenn nicht, ist sie Appendix des letzten Datensatzes
    Was aber, wenn in der Nachricht selber ein Zeilenumbruch steht und diese neue Zeile mit Datum und Uhrzeit anfängt? Das ist ja der Grund, warum ich das Problem oben habe, weil in meinen "Veruchchats" eben dies passiert.

    ErfinderDesRades schrieb:

    ps: deine RegexMatch-Methode ist unnötig - was die leistet, leistet Regex auch ohne die, und deutlich besser.
    Nunja, mag sein. Ich arbeite das erste mal mit RegEx und dachte mir eine zentrale Funktion die ich immer wieder aufrufe würde meine Wartbarkeit erhöhen -ich muss so nur an einer Stelle was ändern. Wie wäre es denn besser/sinnvoller?

    ErfinderDesRades schrieb:

    Etwa dass das im NebenThread erfolgen soll, wird so wahrscheinlich nicht funzzen. Weil Manipulationen an gebundenen Datasets sind immer auch indirekte Zugriffe auf die angebundenen Controls, und somit eiglich unzulässig.Solch stürzt zwar nicht ab, zeigt aber auch nicht richtig an.
    Anfangs hatte ich es im Hauptthread laufen. Bei Chats, dessen Textdatei etwa 30.000 Zeilen hatte, ging das einigermaßen schnell und ich konnte die paar Sekunden einfrieren der Hauptform verkraften. Jetzt habe ich hier aber auch einen Gruppenchat, dessen Nachrichten noch von Anfang 2016 sind. Das sind zwar nur 10.000 Zeilen mehr, also knapp 40.000, dennoch dauert das auslesen bestimmt 10x so lange. Dachte erst das liegt daran, dass ich die Daten in ein bereits befülltes DataSet geschrieben habe (was als XML etwa 15MB ausmachte) geschrieben habe. Dem war aber nicht so, selbst bei einem komplett leeren DataSet dauert es 10x so lange. Daraufhin habe ich den BGW eingebaut. Gibts einen Grund dafür, dass es so extrem lange dauern kann?

    ErfinderDesRades schrieb:

    als erstes ist .ReadAllLines() wohl nicht angebracht, da dadurch Datensätze zerschnitten werden, wenn sie einen ZeilVorschub ine Nachricht enthalten.
    Ich habs jetzt (anscheinend & vorerst) dennoch mit ReadAllLines zum laufen gebracht. Habe dafür die Patterns angepasst. So sieht meine Logik jetzt aus:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub bgwAuslesen_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgwAuslesen.DoWork
    2. Dim row = DirectCast(e.Argument, WhatsApp.WhatsAppChatRow)
    3. Dim Inhalt = IO.File.ReadAllLines(row.Pfad).ToList
    4. _IstWertFürProgressbar = True
    5. bgwAuslesen.ReportProgress(Inhalt.Count)
    6. For i As Integer = 0 To Inhalt.Count - 1
    7. Dim line = Inhalt(i)
    8. bgwAuslesen.ReportProgress(i)
    9. If line.Contains("Nachrichten in diesem Chat sowie Anrufe sind jetzt mit Ende-zu-Ende-Verschlüsselung geschützt. Tippe für mehr Infos.") = False Then
    10. If line.Contains("<Medien weggelassen>") = False Then
    11. Dim Datum = ChatHelfer.RegExMatch(line, "((?:(?:[0-2]?\d{1})|(?:[3][01]{1}))[-:\/.](?:[0]?[1-9]|[1][012])[-:\/.](?:(?:\d{1}\d{1})))(?![\d])")
    12. Dim Uhrzeit = ChatHelfer.RegExMatch(line, "((?:(?:[0-1][0-9])|(?:[2][0-3])|(?:[0-9])):(?:[0-5][0-9])(?::[0-5][0-9])?(?:\s?(?:am|AM|pm|PM))?)")
    13. Dim Absender = ChatHelfer.RegExMatch(line, "(-([^-])*:)").Replace("- ", "").Replace(":", "")
    14. If String.IsNullOrWhiteSpace(Datum) OrElse String.IsNullOrWhiteSpace(Uhrzeit) OrElse String.IsNullOrWhiteSpace(Absender) Then
    15. Dim a = WhatsAppDataset.Nachricht.Last
    16. WhatsAppDataset.Nachricht.Last.Text &= $"{Environment.NewLine}{line}"
    17. Else
    18. Dim NachrichtKopf = $"{Datum}, {Uhrzeit} - {Absender}: "
    19. Dim Nachricht = line.Substring(NachrichtKopf.Length)
    20. WhatsAppDataset.Nachricht.AddNachrichtRow(Date.Parse(Datum), TimeSpan.Parse(Uhrzeit), Absender, Nachricht, row)
    21. End If
    22. End If
    23. End If
    24. Next
    25. row.IstAusgelesen = True
    26. End Sub​

    KingLM97 schrieb:

    Was aber, wenn in der Nachricht selber ein Zeilenumbruch steht und diese neue Zeile mit Datum und Uhrzeit anfängt?
    Woher willst Du dann wissen, dass es ein Teil des Textes ist - und nicht eine neue Nachricht? Was ist, wenn im Chat steht:

    Quellcode

    1. 22.05.18 - Peter: Was hattest Du mir gleich gesagt?
    2. 22.05.18 - Achim: Meine Güte, bist Du vergesslich. Ich hab's Dir doch in Deinen Terminkalender geschrieben. Da stand dick und fett:
    3. 20.05.18 - Peter: Mäh den Rasen! Wichtig!
    4. Kannst Du noch nicht mal in Deinen Terminkalender schauen?
    5. 22.05.18 - Peter: Ach so, der Rasen. Naja. War das wirklich wichtig?

    Da wär nur aus dem Kontext heraus erkennbar, dass Zeile#3 Teil des Textes ist. Für ein Programm aber unmöglich. RegEx hin oder her.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    @VaporiZed

    Wenn ich eine ellenlange Nachricht schreibe mit 30 Sätzen, 2000 Zeichen...dann steht genau diese Nachricht in der Textdatei in genau einer Zeile. Nur wenn ich explizit in der Nachricht einen Zeilenumbruch einfüge, also manuell, dann steht dieser Teil in der Textdatei eben auch in einer neuen Zeile, aber ohne Datum, Uhrzeit und Absender, sondern einfach nur Text.
    Der generelle Aufbau ist mir schon klar, aber Du sagtest, dass Du ggf. mehrzeilige Texte mit nem Datum und Uhrzeit zum Zeilenanfang haben könntest:

    KingLM97 schrieb:

    Was aber, wenn in der Nachricht selber ein Zeilenumbruch steht und diese neue Zeile mit Datum und Uhrzeit anfängt?
    Es ist ein umwahrscheinliches, aber nicht auszuschließendes Szenario. Es klingt zumindest, als ob Du es nicht ausschließen kannst. Daher das sehr spezielle Chatverlaufsbeispiel von mir, um zu zeigen, wo das komplette Vorhaben im krassesten Fall scheitern könnte. Meines Erachtens.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Bei deinem Beispielchat sehe ich aktuell kein Problem. Die Nachricht, die in der neuen Zeile weitergeht, hat weder ein Datum, eine Uhrzeit noch einen Absender. Somit interpretiere ich das nicht als neue Nachricht sondern hänge es der letzten Nachricht einfach hinten dran.

    Oder ich verstehe aktuell dein Problem nicht... Es ist spät, ich sollte den Rechner für heute ausmachen...
    Dann geht's morgen weiter.
    Aber Dein Kommentar ist genau das, was ich meine: Es ist tw. selbst für Menschen nicht zu erkennen, was man da vor sich hat. Post#5, Codezeile#3 beginnt mit Datum, Uhrzeit und Benutzername, also der Beginn einer neuen Nachricht. Aber nur scheinbar! Das gehört nämlich zu dem, was Achim in Peters Terminkalender schrieb: "20.05.18 - Peter: Mäh den Rasen! Wichtig!", denn Achims Nachricht geht von Zeile#2 bis#4! Zeile#3 ist eine Aufforderung mit Anrede, dass Peter am 20.05.18 den Rasen mähen soll. Das ist also kein Teil der automatisierten WhatsApp-Chatnachricht mit TimeStamp, sondern Achims Text. Zu sehen an dem "falschen Datum".

    ##########

    Nochmal zur Klarheit die einzelnen Nachrichten (es sind nämlich nur 3!), die Benutzer (Peter und Achim) in unterschiedlichen Farben und die Textfalle dazu, die das Problem darstellt.

    Nachricht#1
    22.05.18 - Peter: Was hattest Du mir gleich gesagt?

    Nachricht#2
    22.05.18 - Achim: Meine Güte, bist Du vergesslich. Ich hab's Dir doch in Deinen Terminkalender geschrieben. Da stand dick und fett:
    20.05.18 - Peter: Mäh den Rasen! Wichtig!
    Kannst Du noch nicht mal in Deinen Terminkalender schauen?

    Nachricht#3
    22.05.18 - Peter: Ach so, der Rasen. Naja. War das wirklich wichtig?

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „VaporiZed“ ()

    KingLM97 schrieb:

    und diese neue Zeile mit Datum und Uhrzeit anfängt?
    Mit welcher Wahrscheinlichkeit stimmt das "falsche" Datum mit dem "richtigen" überein?
    Wie sieht ein "richtiges" und ein "falsches" Zeilenende aus? (Cr, Lf, CrLf)?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany
    Die Wahrscheinlichkeit sollte relativ gering sein. Vorkommen kann es dennoch. Mein RegEx-Pattern schaut aber explizit nach dd.mm.yy während dd.mm.yyyy nicht erkannt wird.Das mit den Zeilenenden war ein guter Einfall, wäre ich nicht drauf gekommen.Sind aber leider immer CrLf.

    @VaporiZed
    Ahh, jetzt habe ich es verstanden. Gut, das ist natürlich ein Problem, welches wahrscheinlich unter 1% Wahrscheinlichkeit liegt, also vernachlässigbar...denke ich.Sowas müsste ich dennoch theoretisch abfangen können. Ich schaue ja nach ob Datum, Uhrzeit und Absender vorhanden sind. Sollte eines davon fehlen interpretiere ich es als neue Nachricht.Eine Möglichkeit wäre es, irgendwie sowas zu erkennen und den Benutzer zu Fragen...wüsste jetzt nur nicht wie ich mich da richtig an die Sache ran begebe.

    Gerade ein Problem mit dem Erkennen des Absenders gefunden...
    Wenn in der Nachricht ebenfalls ein ":" steht, wird alles zwischen dem "-" und dem letzten ":" als Absender erkannt...ich habe um die Uhrzeit jetzt keine Motivation wieder mit RegEx zu spielen...
    Beispiel:
    27.05.17, 14:23 - Peter: Dämlicher Text mit Smiley (-:
    27.05.17, 14:23 - Peter: Dämlicher Text mit Smiley (-:
    Das violette wird erkannt, ich möchte aber nur das grüne. Kann mir jemand einen Tipp geben wie ich mein Pattern (-([^-])*:) dementsprechend anpassen kann?

    Die Farbe „Rot“ ist der Moderation vorbehalten → Farbe geändert. ~Thunderbolt

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Thunderbolt“ ()