_String_Balanced --> Text zwischen korrespondierenden Klammern

    • Offizieller Beitrag

    Ich finde es schade, dass AutoIt keine Möglichkeit bietet Strings zwischen öffnendem und schließendem Zeichen ausbalanciert auszugeben.
    Meine Funktion "_String_Balanced" ermöglicht das.

    Bsp. "Das ist ein Mustertext (hier ist was ich will (plus gekapselten Inhalt) das gehört auch dazu) das hier nicht".
    Hier gibt es zwei Ausdrücke in runden Klammern
    • (hier ist was ich will (plus gekapselten Inhalt) das gehört auch dazu)
    • (plus gekapselten Inhalt)

    Mit meiner Funktion wird der Inhalt von öffnender Klammer bis zugehöriger schließender Klammer ermittelt.
    Alle Klammertypen können verwendet werden '(', '{', '[', '<'. Bei den spitzen Klammern kann zusätzlich ein Tag verwendet werden, praktisch um z.B. Websiten zu parsen.
    Es braucht nur der öffnende Begrenzer (plus optionalem Tag bei spitzen Klammern) angegeben werden. Das Pendant wird automatisch gebildet.
    Weitere Parameter sind
    [optional] Occurence - Welches Vorkommen im String soll gefunden werden. Wie bei StringInStr kann auch von hinten gesucht werden durch negative Zahl.
    [optional] Offset - An welcher Position im String soll begonnen werden.
    Standardmäßig sind Occurence und Offset so gesetzt, dass ein Array mit allen Vorkommen im String zurückgegeben wird. Dabei wird der String von Anfang bis Ende abgegrast.

    Aber mal als Verleich, wie simpel das z.B. mit Lua geht, Variable: text, gesucht ausbalancierter Inhalt in runden Klammern:

    Code
    text = "...."
    ret = text:find('(%b())')

    :whistling:

    _String_Balanced
    [autoit]

    ;===============================================================================
    ; Function Name...: _String_Balanced
    ; Description.....: Ermittelt einen ausbalancierten String innerhalb folgender
    ; ................: Begrenzer: "()", "{}", "[]", "<abc abc>"
    ; ................: Tags bei spitzen Klammern sind optional,
    ; ................: müssen aber für öffnend und schließend identisch sein.
    ; Parameter(s)....: $_sStr String mit begrenztem Substring
    ; ................: $_sOpen Das öffnende Zeichen des Begrenzerpaares und optional der Tag '<bla'
    ; ................: Daraus wird automatisch der schließende Tag gebildet: 'bla>'
    ; .....optional...: $_iOccurence 0 (Standard) Rückgabe aller Funde als Array mit Zähler an [0].
    ; ................: n Beginnt am n-ten Vorkommen von $_sOpen im String.
    ; ................: Negative Werte zählen Vorkommen von rechts.
    ; ................: Occurence greift nur, wenn $_iOffset < 1.
    ; .....optional...: $_iOffset Position im String zum Start der Selektion.
    ; ................: 0 (Standard) Occurence wird verwendet.
    ; Return Value(s).: Erfolg: Der ermittelte String inkl. der Begrenzer.
    ; ................: Fehler: Leerstring @error = 1 $_sOpen beginnt nicht mit '(', '{', '[' oder '<'
    ; ................: @error = 2 $_sOpen nicht in $_sStr enthalten
    ; ................: @error = 3 $_iOccurence größer als Anzahl Begrenzer
    ; Author(s).......: BugFix ( [email='bugfix@autoit.de'][/email] )
    ;===============================================================================
    Func _String_Balanced($_sStr, $_sOpen, $_iOccurence=0, $_iOffset=0)
    If Not StringInStr($_sStr, $_sOpen) Then Return SetError(2, 0, '')
    Local $sTag = ''
    If StringLeft($_sOpen, 1) = '<' Then
    $sTag = StringTrimLeft($_sOpen, 1)
    $_sOpen = '<'
    EndIf
    If Not StringInStr('({[<', $_sOpen) Then Return SetError(1, 0, '')
    StringReplace($_sStr, $_sOpen & $sTag, $_sOpen & $sTag)
    Local $iMax = @extended
    If Abs($_iOccurence) > $iMax Then Return SetError(3, 0, '')
    If $_iOffset < 1 Or $_iOffset = '' Then $_iOffset = 0
    Local $sClose, $sPattern, $fFirst = False, $iLast, $vOut = ''
    Local $countOpen = 0
    If $_iOffset = 0 And $_iOccurence <> 0 Then
    $_iOffset = StringInStr($_sStr, $_sOpen & $sTag, 1, $_iOccurence)
    EndIf
    If $_sOpen = '(' Then
    $sClose = ')'
    Else
    $sClose = Chr(Asc($_sOpen) +2)
    EndIf
    Local $sEscape = ''
    If StringInStr('({[', $_sOpen) Or $sClose = ')' Then $sEscape = '\'
    $sPattern = $sEscape & $_sOpen & $sTag & '|' & $sTag & $sEscape & $sClose
    While 1
    $aMatch = StringRegExp($_sStr, $sPattern, 1, $_iOffset)
    If Not @error Then
    $_iOffset = @extended
    If StringInStr('({[', $aMatch[0]) Or _
    StringInStr('<' & $sTag, $aMatch[0]) Then
    $countOpen += 1
    Else
    $countOpen -= 1
    EndIf
    If Not $fFirst Then
    $iLast = $_iOffset
    $fFirst = True
    ContinueLoop
    EndIf
    $vOut &= StringMid($_sStr, $iLast, $_iOffset-$iLast-1)
    If $countOpen = 0 Then ExitLoop
    $iLast = $_iOffset -1
    Else
    ExitLoop
    EndIf
    Wend
    If $_iOccurence = 0 Then
    Local $n = 2, $match = $vOut, $aOut[2] = [1, $_sOpen & $sTag & $vOut & $sClose]
    While True
    $match = _String_Balanced($_sStr, $_sOpen & $sTag, $n, 0)
    If Not @error Then
    $aOut[0] += 1
    ReDim $aOut[$aOut[0] +1]
    $aOut[$aOut[0]] = $match
    $n += 1
    Else
    Return $aOut
    EndIf
    WEnd
    Else
    Return $_sOpen & $sTag & $vOut & $sClose
    EndIf
    EndFunc ;==>_String_Balanced

    [/autoit]
    Testskript
    [autoit]

    #include '_String_Balanced.au3'
    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    $sHtml = _
    '<div class="content">' & @CRLF & _
    '<ul class="menu">' & @CRLF & _
    '<li class="collapsed first no-dhtml "><li class="BLA"><a href="BLA.html"</a></li><a href="file-management.html" id="dhtml_menu-504">File management</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="session-management.html" id="dhtml_menu-505">Session management</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="editing.html" id="dhtml_menu-508">Editing</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="document-properties.html" id="dhtml_menu-509">Document Properties</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="searching.html" id="dhtml_menu-511">Searching</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="display.html" id="dhtml_menu-510">Display</a></li>' & @CRLF & _
    '<li class="leaf no-dhtml "><a href="macros.html" id="dhtml_menu-512">Macros</a></li>' & @CRLF & _
    '<li class="leaf no-dhtml "><a href="commands.html" id="dhtml_menu-513">Commands</a></li>' & @CRLF & _
    '<li class="leaf no-dhtml "><a href="shortcuts-run-menu.html" id="dhtml_menu-514">Shortcuts for the Run menu</a></li>' & @CRLF & _
    '<li class="leaf no-dhtml active-trail"><a href="windows-dialog.html" id="dhtml_menu-515" class="active">Windows Dialog</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="gui-elements.html" id="dhtml_menu-744">GUI elements</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="switching-between-documents.html" id="dhtml_menu-745">Switching between Documents</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="languages.html" id="dhtml_menu-746">Languages</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="settings.html" id="dhtml_menu-747">Settings</a></li>' & @CRLF & _
    '<li class="leaf no-dhtml "><a href="plugins.html" id="dhtml_menu-749">Plugins</a></li>' & @CRLF & _
    '<li class="leaf no-dhtml "><a href="command-line.html" id="dhtml_menu-750">Command Line</a></li>' & @CRLF & _
    '<li class="leaf no-dhtml "><a href="control-files.html" id="dhtml_menu-751">Control files</a></li>' & @CRLF & _
    '<li class="leaf no-dhtml "><a href="shell-extension.html" id="dhtml_menu-752">Shell Extension</a></li>' & @CRLF & _
    '<li class="collapsed no-dhtml "><a href="further-help.html" id="dhtml_menu-748">Further help</a></li>' & @CRLF & _
    '<li class="leaf no-dhtml "><a href="upgrading.html" id="dhtml_menu-753">Upgrading</a></li>' & @CRLF & _
    '<li class="leaf last no-dhtml "><a href="credits.html" id="dhtml_menu-754">Credits</a></li>' & @CRLF & _
    '</ul>' & @CRLF & _
    '</div>' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, 'Test-html', 'Alle Treffer als Array')
    $ret = _String_Balanced($sHtml, '<li')
    _ArrayDisplay($ret)

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, 'Test-html', '5.tes Vorkommen -> Konsole')
    $ret = _String_Balanced($sHtml, '<li', 5)
    ConsoleWrite($ret & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, 'Test-String mit "{"', 'Alle Treffer als Array')
    $string = "TEST{'Klammer_1': {'Klammer_2': {'Inside_2': 'value'}},'key': {'Klammer_3': 'value'}}ENDE"
    $ret = _String_Balanced($string, '{')
    _ArrayDisplay($ret)

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, 'Test-String mit "("', 'Alle Treffer als Array')
    $string = "Das ist ein Mustertext (hier ist was ich will (plus gekapselten Inhalt) das gehört auch dazu) das hier nicht"
    $ret = _String_Balanced($string, '(')
    _ArrayDisplay($ret)

    [/autoit]
  • Aber mal als Verleich, wie simpel das z.B. mit Lua geht, Variable: text, gesucht ausbalancierter Inhalt in runden Klammern:

    Code
    text = "...."
    ret = text:find('(%b())')

    :whistling:

    Zugegeben, mit AutoIt ist das nicht ganz so leicht, aber wenigstens funktioniert es überhaupt :D (hätte ich nicht erwartet).

    Hier mal eine Funktion die den Inhalt von allen übergeordneten Klammerpaaren zurückgibt. Wenn man sie dann auf die Elemente ihres eigenen Arrays anwendet kann man den nächsten Level auslesen.

    [autoit]

    Func StringBalancedParentheses($text)
    Local $array = StringRegExp($text, "\(((?:(?R)|[^()]*)+)\)", 3)
    SetError(@error, @extended)
    Return $array
    EndFunc

    [/autoit]
    Code
    Text: Das ist ein Mustertext (hier ist was ich will (plus gekapselten Inhalt) das gehört auch dazu) das hier nicht.
    1. Durchgang: hier ist was ich will (plus gekapselten Inhalt) das gehört auch dazu
    2. Durchgang: plus gekapselten Inhalt
    • Offizieller Beitrag

    James
    Die Verwendung von Regulären Ausdrücken unter Nutzung der Rekursion ist sicher ein interessanter Ansatz. Aber es ermöglicht immer nur Teillösungen.
    Dass dem so ist ergibt sich auch einfach aus der Tatsache, dass Klammerausdrücke keiner regulären Struktur unterliegen und somit RegExp hier auch schwer anzupassen ist.
    Mal 2 Bsp., was mit Rekursion geht:

    [autoit]


    ;== alles von erster öffnender bis zugehöriger schließender Klammer
    $string = "TEST('Klammer_1': ('Klammer_2': ('Inside_2': ('In_Inside_2': 'value'))),'key_1': ('Klammer_3': ('In_Klammer_3': 'value')),'key_2': ('Klammer_4': 'value')))ENDE"
    $pattern = '(\(([^()]++|(?-2))*\))'
    $a = StringRegExp($string, $pattern, 3)
    ConsoleWrite($a[0] & @CRLF)

    [/autoit][autoit][/autoit][autoit]

    ;== die jeweils am tiefsten verschachtelte Klammer..
    ;== ..für alle Klammerausdrücke einer Ebene
    ;== im Muster sind in einer Ebene: 'Klammer_1', 'key_1', 'key_2'
    $pattern = '\((?:(?(R)\d++|[^()]*+)|(?R))*\)'
    $a= StringRegExp($string, $pattern, 3)
    _ArrayDisplay($a)

    [/autoit]


    Um aber z.B. alle korrespondierenden Klammerausdrücke zu erfassen, muss man dann vom Match die Klammern abschneiden und das Ergebnis wieder parsen usw.
    Ich denke vom internen Zeitaufwand geht das nicht schneller als meine Form der Iteration in der Funktion.

  • Ich denke vom internen Zeitaufwand geht das nicht schneller als meine Form der Iteration in der Funktion.


    Denke ich auch. Ich mache das sonst eigentlich auch anders, mich hat es nur gewundert, dass man in AutoIt sowas überhaupt machen kann.