Textblöcke zwischen zwei Strings extrahieren

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

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von Vortex1991.

    Textblöcke zwischen zwei Strings extrahieren

    Hi, im moment sitze ich an einem Projekt, welches ich vor einiger Zeit schon einmal begonnen hatte. Da ich jedoch eine lange Zeit nichts mehr mit VB o.ä. zu tun hatte, bin ich nun in einer "Haarraufsituation".

    Es geht darum, dass ich ein Programm habe, welches Textdateien einlesen soll und gewisse Textblöcke beginnend mit dem string "ctxt" und endend mit "done" oder "sdone" auslesen lassen möchte.

    In den Textdateien kommen done und sdone unterschiedlich zusammen.

    Hier ein kleiner Auszug der Textdatei:


    -------------------------------------------------------------------------
    Orphanage_MapScriptHeader;trigger count
    db 0
    ;callback count
    db 0

    adopt_mon: MACRO
    ; input: pokemon, level, orphan point cost
    ; output:
    ; 0. pokemon
    ; 1. level
    ; 2. orphan point cost
    ; 4. event flag
    db \1, \2
    dw \3, EVENT_ADOPTED_\1
    ENDM

    OrphanageDonationLady:
    faceplayer
    opentext
    checkevent EVENT_GET_ORPHAN_CARD
    sif false, then
    writetext .first_welcome_text
    verbosegiveitem ORPHAN_CARD, 1
    waitbutton
    writetext .received_orphan_card_text
    setevent EVENT_GET_ORPHAN_CARD
    endtext
    sendif
    checkcode VAR_PARTYCOUNT
    sif <, 2
    jumptext .only_mon_text
    writetext .which_mon_text
    special Special_SelectMonFromParty
    sif false
    jumptext .cancelled_text
    sif =, $ff
    jumptext .invalid_mon_text
    sif =, EGG
    jumptext .egg_text
    callasm IsThisPokemonPlayerLarvitar
    sif true
    jumptext .invalid_mon_text
    callasm OrphanageCalculatePoints
    writetext .confirmation_text
    yesorno
    sif true, then
    callasm .process_donation
    writetext .thank_you_text
    callasm DeletePartyPoke
    special HealParty
    playwaitsfx SFX_HEAL_
    sendif
    jumptext .parting_text

    .process_donation
    ld hl, wOrphanageDonation1
    push hl
    ld de, wOrphanageDonation2
    ld c, wOrphanageDonationEnd - wOrphanageDonation2
    .shift_loop
    ld a, [hli]
    ld [de], a
    inc de
    dec c
    jr nz, .shift_loop
    call UpdateTime
    ld a, [wCurPartySpecies]
    pop hl
    ld [hli], a
    ld de, wCurYear
    rept 2
    ld a, [de]
    inc de
    ld [hli], a
    endr
    ld a, [de]
    ld [hl], a
    ld a, [wCurPartySpecies]
    call PlayCry
    ld hl, TempNumber
    ld a, [hli]
    ld [hMoneyTemp], a
    ld a, [hl]
    ld [hMoneyTemp + 1], a
    ld bc, hMoneyTemp
    push hl
    callba GiveOrphanPoints
    pop hl
    ld a, [hld]
    ld b, [hl]
    ld hl, wAccumulatedOrphanPoints + 3
    add [hl]
    ld [hld], a
    ld a, b
    adc [hl]
    ld [hld], a
    ret nc
    inc [hl]
    ret nz
    dec hl
    inc [hl]
    ret

    .only_mon_text
    ctxt "Du kannst uns dein"
    line "einziges Hemd"
    cont "nicht überlassen."
    done


    .which_mon_text
    ctxt "Willkommen beim"
    line "kleinen"
    cont "Weisenhaus."

    para "Wie können"
    line "wir dir behilflich"
    cont "sein?"
    sdone

    .cancelled_text
    ctxt "Hast du deine"
    line "Meinung geändert?"

    para "Wenn du es doch"
    line "übers Herz"
    cont "bringen kannst,"

    para "wir sind hier und"
    cont "erwarten dich."
    done

    -------------------------------------------------

    Grün markierte Stellen sind die einzig relevanten hierfür.
    Hier mein Lösungsansatz, der überhaupt nicht das macht wie ich es mir vorgestellt hatte...

    VB.NET-Quellcode

    1. Public LineCNT As Integer = 0, ASMSearcher As New FolderBrowserDialog
    2. Dim PARAANDLINE As Boolean = False, backuptext As New RichTextBox
    3. Sub textsplitter()
    4. Dim LineIndex As Integer = 0, CurLine As Integer = 0
    5. For Each line As String In backuptext.Lines
    6. LineIndex = LineIndex + 1
    7. If line.Contains("ctxt") Then
    8. ' Text
    9. 'Dim Z As Integer = backuptext.Text.IndexOf("done")
    10. 'Dim Y As Integer = backuptext.Text.IndexOf("sdone")
    11. 'Dim STR As String = ""
    12. 'CurLine = LineIndex
    13. 'Dim N As New TreeNode With {.Text = backuptext.Lines(LineIndex - 2), .ToolTipText = LineIndex, .StateImageIndex = 0}
    14. 'TreeView1.Nodes(0).Nodes.Add(N)
    15. MsgBox(TextTeiler(backuptext.Text, backuptext.Lines(LineIndex - 2), "done" or "sdone"))
    16. End If
    17. Next
    18. End Sub
    19. Public Function TextTeiler(ByVal GanzerText As String, ByVal ErsterString As String, ByVal LetzterString As String)
    20. Dim Anfang As Integer, Ende_ As Integer, Start_ As Integer
    21. Anfang = GanzerText.IndexOf(ErsterString, StringComparison.CurrentCultureIgnoreCase)
    22. If Anfang >= 0 Then
    23. Start_ = Start_ + ErsterString.Length
    24. Ende_ = GanzerText.IndexOf(LetzterString, Start_, StringComparison.CurrentCultureIgnoreCase)
    25. If Ende_ >= 0 Then
    26. Return GanzerText.Substring(Anfang, Ende_ - Anfang)
    27. End If
    28. End If
    29. Return "Nicht geklappt"
    30. End Function
    31. Private Sub OpenASMDIR_Click(sender As Object, e As EventArgs) Handles OpenASMDIR.Click
    32. If ASMSearcher.ShowDialog() = DialogResult.OK Then
    33. ASM_Dir.Text = ASMSearcher.SelectedPath
    34. End If
    35. reload_asm(ASM_Dir.Text)
    36. End Sub
    37. Private Sub ListView1_DoubleClick(sender As Object, e As EventArgs) Handles ListView1.DoubleClick
    38. If ListView1.SelectedItems.Count = 1 Then
    39. backuptext.Text = ListView1.SelectedItems.Item(0).SubItems(1).Text
    40. TreeView1.Nodes.Clear()
    41. TreeView1.Nodes.Add(ListView1.SelectedItems.Item(0).Text)
    42. textsplitter()
    43. End If
    44. End Sub
    45. Sub reload_asm(ByVal Direct As String)
    46. Try
    47. For Each listFiles As String In IO.Directory.GetFiles(Direct, "*.asm")
    48. Dim I As New ListViewItem With {.Text = IO.Path.GetFileName(listFiles), .ImageIndex = 0}
    49. 'ListView1.Items.Add(IO.Path.GetFileName(listFiles))
    50. I.SubItems.Add(IO.File.ReadAllText(listFiles, System.Text.Encoding.Default))
    51. ListView1.Items.Add(I)
    52. Next
    53. Catch ex As Exception
    54. End Try
    55. End Sub


    Denke es liegt daran, dass ich keine 2 möglichen end-stings angeben kann aber ich steh momentan wirklich auf dem Schlauch.

    Danke schon einmal im Voraus :)
    Das wichtigste für dich ist dieses: Visual Studio - Empfohlene Einstellungen

    Und das zweit-wichtigste wäre, alle TryCatchens rauszuwerfen.

    Weil derzeit codest du einen Stil, der konsequent Fehlermeldungen unterdrückt. Dadurch nimmst du dir selbst nahezu jede Möglichkeit, korrigiert zu werden, und in Folge hast du nahezu keine Chance, dich in deinen vb.net - Kenntnissen weiterzuentwickeln.

    ZB deine Funktion TextTeiler() hat überhaupt keinen Datentyp als Rückgabewert definiert.
    Dir ist aber schon klar, dass eine Function was zurückgibt, oder?

    Wenn du Visual Studio - Empfohlene Einstellungen umsetzst (den Strict On - Teil), dann zwingt dich der Compiler von vornherein dazu, diesen Datentyp auch zu deklarieren (mit As)- damit er weiss, was er eiglich zurückgeben soll.

    Das wäre nämlich auch meine Frage: Was willst du eiglich, was diese Methode zurückgeben soll - was, und in welcher Form, also in welchem Datentyp?
    @Vortex1991 Gehen wir das Problem mal gang formal an:

    VB.NET-Quellcode

    1. ' List(Of String) anlegen
    2. For Each line in File.ReadAllLine
    3. If line.StartsWith("ctxt") Then
    4. ' Startmarker setzen, List(Of String) löschen
    5. Else If line.StartsWith("done") OrElse line.StartsWith("sdone") Then
    6. ' Block zu Ende List(Of String) weiterreichen
    7. End If
    8. If Startmarker Then
    9. ' line in eine List(Of String) packen
    10. End If
    11. Next
    Testen, korrigieren, feddich. :D
    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!
    @ErfinderDesRades
    Das wichtigste für dich ist dieses: Visual Studio - Empfohlene Einstellungen

    Danke werde ich mir ansehen. :)

    Und das zweit-wichtigste wäre, alle TryCatchens rauszuwerfen.

    Bin ich ehrlich gesagt schon dabei. Habe hier doch nur einen Try Block. Der ja mit der eigentlichen Funktion nichts zu tun hat ^^.

    Was willst du eiglich, was diese Methode zurückgeben soll


    VB.NET-Quellcode

    1. Return GanzerText.Substring(Anfang, Ende_ - Anfang)

    Naja einen String, zumindest war das die Idee dahinter. Bestenfalls so:

    ctxt "Willkommen beim"
    line "kleinen"
    cont "Weisenhaus."

    para "Wie können"
    line "wir dir behilflich"
    cont "sein?"
    sdone

    Er gibt ja auch einen string zurück, der aber leider fast die ganze Textdatei anzeigt.
    @Vortex1991 Hast Du Dir mal den Code-Rumpf in Post #3 angesehen :?:
    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

    Noch nicht, werde ich aber gleich und sage dir bescheid ob es geklappt hat :)

    Edit: Habe es jetzt versucht. Das Problem ist, dass "ctxt" genauso wie "done" bzw. "sdone" mehrfach in einer Textdatei vorkommen.
    Hab das mal leicht umgestellt, ich weis leider nicht ob man eine for lines Schleife auch an einem gewissen Index starten kann, dann wäre das Problem in ein paar Zeilen gelöst.

    VB.NET-Quellcode

    1. backuptext.Text.Substring(backuptext.Text.IndexOf("ctxt"), backuptext.Text.IndexOf("done") - backuptext.Text.IndexOf("ctxt"))


    mit diesem Code macht das tool jetzt genau das, was es machen soll. Leider kann ich kein zusätzliches "sdone" als IndexOf angeben, da ich sonst eine Converting to Long exception bekomme.

    VB.NET-Quellcode

    1. backuptext.Text.Substring(backuptext.Text.IndexOf("ctxt"), backuptext.Text.IndexOf("done" Or "sdone") - backuptext.Text.IndexOf("ctxt"))



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

    @Vortex1991 So was:

    RodFromGermany schrieb:

    VB.NET-Quellcode

    1. For Each line in File.ReadAllLine(...)
    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!
    Habe es nun etwas kreativer gelöst :)

    VB.NET-Quellcode

    1. backuptext.text = backuptext.Text.Replace("done", "doneENDOFBLOCK")


    und die Funktion zum splitten

    VB.NET-Quellcode

    1. backuptext.Text.Substring(backuptext.Text.IndexOf("ctxt"), backuptext.Text.IndexOf("ENDOFBLOCK") - backuptext.Text.IndexOf("ctxt"))


    Und dann bekomme ich immer nur die inneren Texte.
    Zusätzlich habe ich auch die sdone und done Bezeichner im Text.

    Trotzdem vielen Lieben Dank für deine Hilfe :)

    Gesammt sieht das dann so aus. Leider habe ich nun das Problem, dass er nur den ersten Block angibt...

    VB.NET-Quellcode

    1. Sub textsplitter()
    2. Dim LineIndex As Integer = 0, StartLine As Integer = 0
    3. Dim List As New RichTextBox
    4. 'backuptext.Text.
    5. ' MsgBox(backuptext.Text.Substring(backuptext.Text.IndexOf("ctxt"), backuptext.Text.IndexOf("ENDOFBLOCK") - backuptext.Text.IndexOf("ctxt")))
    6. backuptext.Text = backuptext.Text.Replace("done", "doneENDOFBLOCK")
    7. For Each line As String In backuptext.Lines
    8. If line.Contains("ctxt") Then
    9. MsgBox(backuptext.Text.Substring(backuptext.Text.IndexOf("ctxt"),
    10. backuptext.Text.IndexOf("ENDOFBLOCK") - backuptext.Text.IndexOf("ctxt")))
    11. End If
    12. Next
    13. 'For Each line As String In backuptext.Lines
    14. ' LineIndex = LineIndex + 1
    15. ' If line.Contains("ctxt") Then
    16. ' End If
    17. ' If line.Contains("done" Or "sdone") Then
    18. ' End If
    19. 'Next
    20. End Sub

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

    Vortex1991 schrieb:

    Was willst du eiglich, was diese Methode zurückgeben soll

    VB.NET-Quellcode

    1. Return GanzerText.Substring(Anfang, Ende_ - Anfang)

    Naja einen String, zumindest war das die Idee dahinter. Bestenfalls so:[...]
    Naja, einen String zurückzugeben ist ja zuwenig - hast ja inzwischen selbst gemerkt, dass der Text mehrere Treffer enthält, und logisch, dasses dann kompliziert wird, das mit einer Methode abzuhandeln, die nur einen Treffer zurückgeben kann.
    Die müsste sich dann ja immer den vorherigen Treffer merken und so Zeugs.

    Nein - tatsächlich sollte die Methode einfach eine List(Of String) zurückgeben, also viele Strings, nämlich alle Treffer, um genau zu sein.



    ups - sehe grade - schon erledigt?

    Zeig mal deine Lösung, dann zeig ich auch meine ;)
    @ErfinderDesRades

    HIer :P

    VB.NET-Quellcode

    1. Sub textsplitter()
    2. Dim LineIndex As Integer = 0
    3. backuptext.Text = backuptext.Text.Replace("done", "doneENDOFBLOCK")
    4. For Each line As String In backuptext.Lines
    5. LineIndex = LineIndex + 1
    6. If line.Contains("ctxt") Then
    7. MsgBox(backuptext.Text.Substring(backuptext.Text.IndexOf("ctxt", LineIndex - 1),
    8. backuptext.Text.IndexOf("ENDOFBLOCK", LineIndex - 1) - backuptext.Text.IndexOf("ctxt", LineIndex - 1)))
    9. End If
    10. Next
    11. End Sub


    Edit: Den code grade noch einmal überarbeitet :P Jetzt geht es komplett @ErfinderDesRades würde deine lösung auch gerne sehen. So lernt man am besten dazu :>

    VB.NET-Quellcode

    1. Sub textsplitter()
    2. Dim LineIndex As Integer = 0, cusPos As Integer = 0
    3. backuptext.Text = backuptext.Text.Replace("done", "doneENDOFBLOCK")
    4. For Each line As String In backuptext.Lines
    5. LineIndex = LineIndex + 1
    6. If line.Contains("ctxt") Then
    7. MsgBox(backuptext.Text.Substring(backuptext.Text.IndexOf("ctxt", cusPos),
    8. backuptext.Text.IndexOf("ENDOFBLOCK", cusPos) - backuptext.Text.IndexOf("ctxt", cusPos)))
    9. cusPos = backuptext.Text.IndexOf("ctxt", cusPos + 5)
    10. End If
    11. Next
    12. End Sub


    Kurze Erläuterung:
    Da done und sdone beide die Zeichenfolge done haben, habe ich ersteinmal alle 'done' mit 'doneENDOFBLOCK' replaced, damit ich zwischen 'ctxt' und 'ENDOFBLOCK' den substring rausholen kann.
    CusPos ist die momentane Position im Text, die den neuen Einstiegspunkt für die IndexOf 'ctxt' festlegt. Nach jedem Durchlauf wird CusPos mit der aktuellen Position im Text überschrieben und der nächste Einstiegspunkt gesetzt.

    Das einzige (kleine) Problem was jetzt noch auftritt, ist das der erste Block 2 mal angezeigt wird, bevor die nächsten kommen^^.

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

    VB.NET-Quellcode

    1. Private _GesamtText As String = ".only_mon_text" _
    2. & LF & "ctxt ""Du kannst uns dein""" _
    3. & LF & "line ""einziges Hemd""" _
    4. & LF & "cont ""nicht überlassen.""" _
    5. & LF & "done" _
    6. & LF & "" _
    7. & LF & ".which_mon_text" _
    8. & LF & "ctxt ""Willkommen beim""" _
    9. & LF & "line ""kleinen""" _
    10. & LF & "cont ""Weisenhaus.""" _
    11. & LF & "" _
    12. & LF & "para ""Wie können""" _
    13. & LF & "line ""wir dir behilflich""" _
    14. & LF & "cont ""sein?""" _
    15. & LF & "sdone" _
    16. & LF & "" _
    17. & LF & ".cancelled_text" _
    18. & LF & "ctxt ""Hast du deine""" _
    19. & LF & "line ""Meinung geändert?""" _
    20. & LF & "" _
    21. & LF & "para ""Wenn du es doch""" _
    22. & LF & "line ""übers Herz""" _
    23. & LF & "cont ""bringen kannst,""" _
    24. & LF & "" _
    25. & LF & "para ""wir sind hier und""" _
    26. & LF & "cont ""erwarten dich.""" _
    27. & LF & "done"
    28. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles TestToolStripMenuItem.Click
    29. For Each txt In GetDoneParts(_GesamtText, "ctxt", {"sdone", "done"})
    30. MessageBox.Show(txt)
    31. Next
    32. End Sub
    33. Private Function GetDoneParts(ganzerText As String, ersterString As String, letzteStrings As String()) As List(Of String)
    34. Dim lst = New List(Of String)
    35. Dim splits1 = ganzerText.Split({ersterString}, StringSplitOptions.None) 'nach ctxt splitten
    36. For Each splt In splits1.Skip(1) ' Skip(1): erstes Element überspringen
    37. Dim splts2 = splt.Split({letzteStrings(0)}, StringSplitOptions.None) 'nach sdone splitten
    38. Dim splts3 = splts2(0).Split({letzteStrings(1)}, StringSplitOptions.None) 'den ersten splt nach done splitten
    39. If splts3.Length > 1 Then 'war das den ersten splt nach done splitten erfolgreich, so lag zw. ctxt und sdone
    40. lst.Add(splts3(0)) ' offsichtlich noch ein done - nehmemer also das
    41. Else
    42. lst.Add(splts2(0))
    43. End If
    44. Next
    45. Return lst
    46. End Function
    Bei mir ist die Methode GetDoneParts ganz autark, also bekommt alles was sie braucht übergeben, und grabscht nicht in iwelche Textboxen.
    Übergeben wird ihr:
    1. der Text (String)
    2. der start-Pattern (String)
    3. beide end-Pattern, (String-Array)
      Hierbei ist wichtig, dass "sdone" zuerst übergeben wird, damit, wenn als zweites nach "done" gesplittet wird, "sdone" schon weg ist.
    Rückgabe ist eine List(Of String), eben die Trefferliste.



    Aber ich bin etwas enttäuscht, dass du dich einen Kehricht um Visual Studio - Empfohlene Einstellungen scherst.

    (Naja, enttäuscht ist etwas übertrieben - genauer wäre "es war kaum anners zu erwarten" - wohl 90% der Anfänger setzen ihre Prioritäten so, dass ihnen ihr partikulares Problemchen wichtiger ist, als überhaupt mal ihr Entwicklungswerkzeug in einen brauchbaren Zustand zu versetzen)

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

    Ja, ein richitger Programierer werde ich glaube ich eh' nicht. Mache das größtenteils Hobby und Privat- mäßig. Mir reicht es grundsätzlich wenn es funktioniert :P .
    Finde es aber trotzdem schön, dass man hier (auch mit Spagetti-Code) gute Hilfe bekommt. :)

    Habe mir deine Funktion angesehen und auch verstanden. Abgeändert hätte ich nun nur, dass das done und sdone mit im String übergeben wird.
    Dir wird die Lösund dafür wahrscheinlich misfallen, aber mein Wissen ist nunmal limitiert.

    VB.NET-Quellcode

    1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    2. For Each txt In GetDoneParts(ListView1.SelectedItems.Item(0).SubItems(1).Text.Replace("done", "doneENDOFBLOCK"), "ctxt", {"ENDOFBLOCK", "NOTANACTUALSTRD"})
    3. MessageBox.Show(txt)
    4. Next
    5. End Sub


    So bekomme ich done und sdone. -> Die muss ich später wieder genau so in die Textdatei zurückschreiben.

    Bin mich momentan am versuchen, ob auch die Zeile vor einem gefundenen "ctxt" mit returned werden kann (also in deinem zur Übung).
    In meinem habe ich das ja schon folgendermaßen gelöst:

    VB.NET-Quellcode

    1. backuptext.Lines(LineIndex - 2)

    So springt er ne Zeile im Dokument nach oben.
    Ist zwar schon etwas her, sitze jedoch wieder daran. Den Code zum Splitten hab ich zwischenzeitlich neu Geschrieben. Danke an der Stelle auch nochmal an @ErfinderDesRades, wenn ich mir das alt geschriebene von mir da anseh, schon recht Panne.

    Visual Basic-Quellcode

    1. Public StartDelimiters() As String = {vbTab & "ctxt ", vbTab & "text "}
    2. Public EndDelimiters() As String = {vbTab & "sdone", vbTab & "done", vbTab & "prompt"}
    3. Public Function GetTextBlocks(ASMFile As String) As List(Of String)
    4. Dim Blocks As New List(Of String)
    5. Dim a As String = GetText(ASMFile)
    6. Dim EndSplits() = a.Split(EndDelimiters, Nothing)
    7. For Each CtxtSplit In EndSplits
    8. Dim FinalBlock() As String = CtxtSplit.Split(StartDelimiters, Nothing)
    9. If FinalBlock.Length <> 1 Then
    10. Blocks.Add(FinalBlock(1))
    11. End If
    12. Next
    13. GetTextBlocks = Blocks
    14. End Function


    Das Funktioniert auch wirklich gut so. :)