StringRegEx mit Rückgabe der Position der Expression

  • Hi Community,

    vorhin musste ich für ein Skript herausfinden, an welcher Position sich in einem String eine Expression aus einem regulären Ausdruck befindet. So habe ich mir die Funktion _StringRegExPos() geschrieben.

    Die Funktion gibt ein 2D-Array zurück, indem Index-0 die RegExp. ist und Index-1 die Position. Das Errorhandling ist das Gleiche wie bei StringRegEx.

    [autoit]


    Func _StringRegExPos($string, $pattern, $flag = 0, $offset = 1)
    Local $ar_regEx = StringRegExp($string, $pattern, $flag, $offset)
    If @error Then
    SetError(@error, @extended)
    Else
    Local $s = $string, $hp = 0, $ar_pos[1][2]

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

    For $i = 0 To UBound($ar_regEx) - 1
    ReDim $ar_pos[$i + 1][2]
    $p = StringInStr($s, $ar_regEx[$i], 1, 1)
    $l = StringLen($ar_regEx[$i])
    $s = StringTrimLeft($s, ($p - 1) + $l)
    $ar_pos[$i][1] = $hp + $p
    If $l < 1 Then $ar_pos[$i][1] += $l - 1
    $ar_pos[$i][0] = $ar_regEx[$i]
    $hp += ($p - 1) + $l
    Next

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

    Return $ar_pos
    EndIf
    EndFunc ;==>_StringRegExPos

    [/autoit]

    Beispiel:

    Spoiler anzeigen
    [autoit]


    #include <Array.au3>

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

    $regEx = _StringRegExPos('A1B2C3D4E5F6G7H8I9J0', '\d', 3)
    If @error Then MsgBox(16,"Caution","_StringRegExPos failed:" & @CRLF & "@error: " & @error)

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

    _ArrayDisplay($regEx)

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

    Func _StringRegExPos($string, $pattern, $flag = 0, $offset = 1)
    Local $ar_regEx = StringRegExp($string, $pattern, $flag, $offset)
    If @error Then
    SetError(@error, @extended)
    Else
    Local $s = $string, $hp = 0, $ar_pos[1][2]

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

    For $i = 0 To UBound($ar_regEx) - 1
    ReDim $ar_pos[$i + 1][2]
    $p = StringInStr($s, $ar_regEx[$i], 1, 1)
    $l = StringLen($ar_regEx[$i])
    $s = StringTrimLeft($s, ($p - 1) + $l)
    $ar_pos[$i][1] = $hp + $p
    If $l < 1 Then $ar_pos[$i][1] += $l - 1
    $ar_pos[$i][0] = $ar_regEx[$i]
    $hp += ($p - 1) + $l
    Next

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

    Return $ar_pos
    EndIf
    EndFunc ;==>_StringRegExPos

    [/autoit]

    Die Funktion ist "recht" schnell. Der Durchschnittswert von 100.000 Durchläufen mit obigem Skript beträgt 0.00033 Sekunden.

    Einmal editiert, zuletzt von Jautois (8. März 2011 um 07:45)

  • Vielleicht sollte man hinweisen das man auf diese Art und Weise leider nicht allgemeingültig arbeiten kann.
    Mal als Beispiel um das zu verdeutlichen:

    Spoiler anzeigen

    $regEx = _StringRegExPos('aadsadsadasMeinTestasdasdsad|MeinTest|sdasdasd', '\|(MeinTest)\|', 3)


    Laut dem Pattern darf nur das "MeinTest" gefunden werden welches zwischen den beiden "|"-Zeichen steht.
    StringRegExp gibt das auch korrekt so zurück.
    Deine Funktion sucht dann allerdings nach dem 1. Auftreten der Zeichenfolge des Matches und gibt hier fälschlicherweise als Position 12 zurück - richtig wäre aber 30.
    Leider wird man das mit diesem Lösungsansatz schlecht umgehen können.
    Daher sollte man explizit auf diesen Umstand hinweisen damit die Nutzer das beachten können.

  • Mit dem Modus 2 kann man die Position genau bstimmen. Man bekommt ein Offset und den kompletten Treffer. Jetzt kann man vom Offset die Stringlänge abziehen und man hat die Position ;)

    • Offizieller Beitrag

    Und mit Modus 1 bekommt man den Offset um beim nächsten Match weiterzumachen. Ich habe mal beides gemixt.

    Edit:
    Als Position wird die Position des Matches ausgegeben. Hat man, wie in AspirinJunkies Bsp. Teile des Matches ausgeschlossen ( | ), so muß man deren Länge zur Matchposition dazurechnen.
    Und nochwas: Das bedeutet aber auch, dass bei sehr flexiblen Pattern (maskierte Zeichen optional) bei einem Match die Länge(n) addiert werden müssen und beim nächsten gar nicht. Insofern bleibt die Positionsermittlung hiermit eine hochgenaue Schätzung. :D

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>

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

    $s = 'aadsadsadasMeinTestasdasdsad|MeinTest|sdasdasd'
    $aOut = _StringRegExpPos($s, '\|(MeinTest)\|')
    _ArrayDisplay($aOut)

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

    Func _StringRegExpPos($sString, $sPattern)
    Local $pos = 0, $sOut = '', $aRet, $offset, $aSplit, $tmp
    While True
    $aRet = StringRegExp($sString, $sPattern, 2, $pos)
    $offset = @extended
    If Not IsArray($aRet) Then ExitLoop
    $pos = $offset - StringLen($aRet[0])
    $aRet = StringRegExp($sString, $sPattern, 1, $pos)
    $offset = @extended
    $sOut &= $aRet[0] & Chr(0) & $pos & Chr(29)
    $pos = $offset
    WEnd
    If $pos = 0 Then Return SetError(1,0,0) ; kein Match
    $aSplit = StringSplit(StringTrimRight($sOut, 1), Chr(29) , 1)
    Local $aOut[$aSplit[0]][2]
    For $i = 1 To $aSplit[0]
    $tmp = StringSplit($aSplit[$i], Chr(0))
    $aOut[$i-1][0] = $tmp[1]
    $aOut[$i-1][1] = $tmp[2]
    Next
    Return $aOut
    EndFunc

    [/autoit]
    • Offizieller Beitrag

    Das geht doch auch ohne Aufruf von 2 StringRegExp.

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>

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

    $s = 'aadsadsadasMeinTestasdasdsad|MeinTest|sdasdasd|MeinTest|'
    $aOut = _StringRegExpPos($s, '\|(MeinTest)\|')
    _ArrayDisplay($aOut)

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

    Func _StringRegExpPos($sString, $sPattern)
    Local $pos = 0, $sOut = '', $aRet, $offset, $aSplit, $tmp
    While True
    $aRet = StringRegExp($sString, $sPattern, 2, $pos)
    $offset = @extended
    If Not IsArray($aRet) Then ExitLoop
    $pos = $offset - StringLen($aRet[0])
    $sOut &= $aRet[1] & Chr(0) & $pos & Chr(29)
    $pos = $offset
    WEnd
    If $pos = 0 Then Return SetError(1, 0, 0) ; kein Match
    $aSplit = StringSplit(StringTrimRight($sOut, 1), Chr(29), 1)
    Local $aOut[$aSplit[0]][2]
    For $i = 1 To $aSplit[0]
    $tmp = StringSplit($aSplit[$i], Chr(0))
    $aOut[$i - 1][0] = $tmp[1]
    $aOut[$i - 1][1] = $tmp[2]
    Next
    Return $aOut
    EndFunc ;==>_StringRegExpPos

    [/autoit]

    Oder habe ich da einen Denkfehler?

    Edit: Ups, falschen Code kopiert. Hab ihn noch mal ausgetauscht. :D

    • Offizieller Beitrag

    Oder habe ich da einen Denkfehler?


    Ja ;)

    Steht direkt in meinem vorigen Post. Das Ergebnis ist maskiert. Es wird "MeinTest" innerhalb zweier Pipes gesucht. Die Pipes sind aber nicht Bestandteil des gewünschten Matches. Bei Modus 2 bekommst du aber immer den kpl. Treffer, inkl. der ausgeschlossenen Matchbestandteile.

    Edit:
    Äh.. jetzt bin ich verwirrt :rofl:
    Ach, Denkfehler - hatte nicht bedacht, dass im Mode 2 mehr als ein Element im Array steht. Man muß dann Index 1 nehmen. :Kopf-auf-Tisch:

    • Offizieller Beitrag

    Schau mal mein Edit, hatte den falschen Quellcode aus Scite kopiert.
    Zu viele Fenster offen :D

    Zitat von BugFix

    Bei Modus 2 bekommst du aber immer den kpl. Treffer, inkl. der ausgeschlossenen Matchbestandteile.


    $aRet[0] liefert den Match inkl. der ausgeschlossenen Matchbestandteile und $aRet[1] liefert den gesuchten Match :D

    Zitat von BugFix

    Edit:
    Äh.. jetzt bin ich verwirrt :rofl:
    Ach, Denkfehler - hatte nicht bedacht, dass im Mode 2 mehr als ein Element im Array steht. Man muß dann Index 1 nehmen. :Kopf-auf-Tisch:

    Manchmal sieht man den Wald vor lauter Bäumen nicht :rofl:

  • Super funktion. Funktioniert klasse.
    Meiner Meinung nach sollte mal diese Funktion als Standardfunktion in AutoIt einbauen.

    Meine Projekte:
    ClipBoard Manager (beendet)
    Gutes ClipBoard Verwaltungs Programm mit nützlichen Funktionen.

    HTML Creator (beendet)
    Nützliches Tool um schnell ein eigenes HTML Dokument zu erstellen.

    • Offizieller Beitrag

    Raupi :
    Mit der Variante funktioniert dann aber die Ausgabe von Matches mit einfachen Treffern ("\d") nicht, da es kein $aRet[1] gibt, sondern nur den Fullmatch.
    Habe das jetzt korrigiert und auch eine Änderung der Positionsermittlung, wenn maskierte Zeichen dabei sind. Ich such einfach nach dem gefundenen Begriff ab der gemeldeten Matchposition mit StringInStr. Das klappt dann. :thumbup:

    Vielleicht hat ja mal jemand Muße, das mit etwas komplexeren Ausdrücken zu testen.

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>

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

    $s = 'aads|Mein01Test|adsadasMeinTestasdasdsadsdasdasd|Dein002Test|'
    $aOut = _StringRegExpPos($s, '\|((M|D)ein(?:\d{1,3})Test)\|')
    _ArrayDisplay($aOut)

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

    $s = 'A12C23D45E56F67G7H8I9J0'
    $aOut = _StringRegExpPos($s, '\d')
    _ArrayDisplay($aOut)

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

    Func _StringRegExpPos($sString, $sPattern)
    Local $pos = 0, $sOut = '', $aRet, $offset, $aSplit, $tmp, $tmpStr, $NonVisibleChar = False
    While True
    $aRet = StringRegExp($sString, $sPattern, 2, $pos)
    $offset = @extended
    If Not IsArray($aRet) Then ExitLoop
    $pos = $offset - StringLen($aRet[0])
    If UBound($aRet) > 1 Then
    $aRet[0] = $aRet[1]
    $NonVisibleChar = True
    Endif
    $sOut &= $aRet[0] & Chr(0) & $pos & Chr(29)
    $pos = $offset
    WEnd
    If $pos = 0 Then Return SetError(1, 0, 0) ; kein Match
    $aSplit = StringSplit(StringTrimRight($sOut, 1), Chr(29), 1)
    Local $aOut[$aSplit[0]][2]
    For $i = 1 To $aSplit[0]
    $tmp = StringSplit($aSplit[$i], Chr(0))
    $aOut[$i - 1][0] = $tmp[1] ; Inhalt
    If $NonVisibleChar Then ; korrekte Position ermitteln:
    $tmpStr = StringTrimLeft($sString, $tmp[2])
    $tmp[2] = StringInStr($tmpStr, $tmp[1]) + $tmp[2]
    EndIf
    $aOut[$i - 1][1] = $tmp[2] ; Position
    Next
    Return $aOut
    EndFunc ;==>_StringRegExpPos

    [/autoit]
    • Offizieller Beitrag

    Ich hab da noch was:

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    $s = 'aads|Mein01Test|adsadasMeinTestasdasdsadsdasdasd|Dein002Test|'
    $aOut1 = _StringRegExpPos($s, '\|((M|D)ein(?:\d{1,3})Test)\|')
    $s = 'A12C23D45E56F67G7H8I9J0'
    $aOut2 = _StringRegExpPos($s, '\d')
    _ArrayDisplay($aOut1)
    _ArrayDisplay($aOut2)

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

    Func _StringRegExpPos($sString, $sPattern)
    Local $pos = 0, $sOut = '', $aRet, $offset, $aSplit, $tmp
    While True
    $aRet = StringRegExp($sString, $sPattern, 2, $pos)
    $offset = @extended
    If Not IsArray($aRet) Then ExitLoop
    If UBound($aRet) > 1 Then
    $pos = $offset - StringLen($aRet[0]) + StringInStr($aRet[0], $aRet[1])- 1
    $sOut &= $aRet[1] & Chr(0) & $pos & Chr(29)
    Else
    $pos = $offset - StringLen($aRet[0])
    $sOut &= $aRet[0] & Chr(0) & $pos & Chr(29)
    EndIf
    $pos = $offset
    WEnd
    If $pos = 0 Then Return SetError(1, 0, 0) ; kein Match
    $aSplit = StringSplit(StringTrimRight($sOut, 1), Chr(29), 1)
    Local $aOut[$aSplit[0]][2]
    For $i = 1 To $aSplit[0]
    $tmp = StringSplit($aSplit[$i], Chr(0))
    $aOut[$i - 1][0] = $tmp[1]
    $aOut[$i - 1][1] = $tmp[2]
    Next
    Return $aOut
    EndFunc ;==>_StringRegExpPos

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

    Funzt genauso wie deins BugFix .

    Edit: Noch ein Bug beseitigt

    • Offizieller Beitrag

    Genau, außerdem lernt man noch was draus.
    Ich benutze RegExp und die String Befehle zu selten, war eine gute Übung :D
    Wieder mal ein bischen um die Ecke denken ist auch was feines. :thumbup:

  • Zitat

    @Jautois: Sorry, dass wir deinen Thread gekapert haben :D - aber war halt ein interessantes Thema. ;)

    Macht nichts, ich hätte wohl vorher lieber mal die StringRegEx Funktion studiert, dann wäre ich wohl auf die Geschichte mit dem Offset gekommen :D
    Aber dennoch wars eine gute Übung.

    Zitat

    Raupi : "Viele Wege führen nach Rom.." w.z.b.w. :D


    Meiner führt in einen Vorort und Blasen am Fuß gibts gratis ;)