Strings in Quellcode austauschen (mit RegEx)

  • Hall, :)

    ich versuche in Resource Quelldateien Textstrings die Hex kodierten Unicode enthalten in normale Unicode Zeichen zu konvertieren.

    Dazu lese ich eine *.rc Datei ein, suche zeilenweise nach Texten (erkennbar am L" und ", für Anfang/Ende) und suche dann im gefundenen String nach Hexwerten (Anfang mit \x und Ende mit \x oder ",).

    Das Problem ist: verwende ich zum Suchen der Hexwerte StringRegEx werden alle Fundstellen richtig ins Array geschrieben, verwende ich zum Suchen/Ersetzen StringRegExpReplace dann wird der falsche Teil ersetzt.

    Hier ein als Beispiel Auszug aus einer RC Datei:

    Code
    CTEXT       L"\x20\x20\x20\x41f\x43e\x43b\x43e\x436\x435\x43d\x438\x435\x20\x433\x43b\x430\x432\x43d\x43e\x433\x43e\x20\x432\x430\x43b\x430\x20\x20\x20\x20\x20\x20\x5b\x413\x420\x410\x414\x5d",IDST_TITEL,0,0,
                    800,26
    CONTROL     "",IDFR_HW_BREMSE_RECT,"Static",SS_BLACKFRAME | NOT

    Im folgenden (StringRegEx) Beispiel wird der Teststring richtig zerlegt und finden alle Hexwerte

    [autoit]

    #include <array.au3>
    $sString = ' CTEXT L"\x20\x20\x20\x41f\x43e\x43b\x43e\x436\x435\x43d\x438\x435\x20\x433\x43b\x430\x432\x43d\x43e\x433\x43e\x20\x432\x430\x43b\x430\x20\x20\x20\x20\x20\x20\x5b\x413\x420\x410\x414\x5d",IDST_TITEL,0,0,'
    $aArray = StringRegExp($sString, '\\x(.*?)\\x', 3)
    _ArrayDisplay($aArray, "Titel")

    [/autoit]

    Im (StringRegExReplace) Beispiel wird der Teil um den Wert herum ersetzt!!

    [autoit]

    #include <array.au3>

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

    $sString = ' CTEXT L"\x20\x20\x20\x41f\x43e\x43b\x43e\x436\x435\x43d\x438\x435\x20\x433\x43b\x430\x432\x43d\x43e\x433\x43e\x20\x432\x430\x43b\x430\x20\x20\x20\x20\x20\x20\x5b\x413\x420\x410\x414\x5d",IDST_TITEL,0,0,'
    $aArray = StringRegExpReplace($sString, '\\x(.*?)\\x', "kk")
    MsgBox(0, "tt", $aArray)

    [/autoit]

    Hier werden alle "\x" durch "kk" ersetzt!!!

    Das ganze sieht im Moment so aus:

    Spoiler anzeigen

    #include <String.au3>
    #include <file.au3>
    #include <array.au3>
    #include <Constants.au3>

    Dim $sDrive, $sDir, $sName, $sExt

    ; Select file to convert
    $file = FileOpenDialog("Select RC file", ".\", "RC files (*.RC)", 1 + 2)
    If @Error Then
    MsgBox(4096, "Error", "No file(s) chosen")
    Exit
    EndIf

    ; create new file name based on the previous selected
    $aArray = _PathSplit($file, $sDrive, $sDir, $sName, $sExt)
    $sNewFile = $aArray[1] & $aArray[2] & $aArray[3] & "_utf.rc"

    ; create new text file
    $newfile = FileOpen($sNewFile, 2+256)

    Local $aFile = StringSplit(FileRead($file),@CRLF,3)

    For $i = 0 to Ubound($aFile)-1
    $aLine = _StringBetween($aFile[$i], 'L"', '",')
    If @error Then
    FileWrite($newfile, $aFile[$i] & @CRLF)
    Else
    $aUTFChars = StringRegExpReplace($aFile[$i], '\\x(.*?)\\x', chrW('i'))
    FileWrite($newfile, "XXX" & $aUTFChars & @CRLF)
    EndIf
    Next

    Hmm.. ich versuche nun schon seit Stunden einen Weg zu finden, und vermute ich mache etwas bei den regEx falsch. Hat mir hier jemand einen Tipp? Bin ich mit StringRegEx/Replace auf dem Holzweg??

    Gruß aus Ludwigsburg
    Anton


    Zzzzzzzzz

  • Moin,

    ich hab zwar nicht ganz verstanden was genau du als Ausgabe benötigst aber evtl. hilft dir das hier.

    Spoiler anzeigen
    [autoit]

    #include <array.au3>
    $sString = ' CTEXT L"\x20\x20\x20\x41f\x43e\x43b\x43e\x436\x435\x43d\x438\x435\x20\x433\x43b\x430\x432\x43d\x43e\x433\x43e\x20\x432\x430\x43b\x430\x20\x20\x20\x20\x20\x20\x5b\x413\x420\x410\x414\x5d",IDST_TITEL,0,0,'
    $aArray = StringRegExp($sString, 'L\"\\x(.+)\"\,', 3)
    $nArray = StringSplit($aArray[0], '\x', 1)
    _ArrayDisplay($nArray)

    [/autoit]
  • n´morgen :)

    ja, sorry.. ich nehme an ich war um die Uhrzeit nicht klar beim Formulieren.

    Ich will in dem Text die Hex Zahlenwerte durch UTF tauschen, daher hatte ich die StringRegExReplace Funkion verwendet.

    Das finden der Zahlenwerte mit StringRegEx funktioniert mit dem von mir verwendeten Ausdruck, der selbe Ausdruck in StringRegExReplace liefert aber ein anderes Ergebnis.

    Oder anders: Bei "\x20" als String liefert mir StringRegEx die "20" in einem Array, soweit OK. Ich will die aber diese "20" ersetzen lassen durch z.B. ein chrW(20), also nehme ich dafür StringRegExReplace mit der selben RegEx., das ersetzt mir aber das "\x" und nicht die "20". Bin ich nun total auf dem Holzweg??

    Gruß aus Ludwigsburg :)
    Anton

    • Offizieller Beitrag

    also nehme ich dafür StringRegExReplace mit der selben RegEx., das ersetzt mir aber das "\x" und nicht die "20". Bin ich nun total auf dem Holzweg??


    AspirinJunkie hat dir schon die optimale Lösung gezeigt.
    Hier kurz die Erklärung, warum StringRegExReplace nicht so geht:
    Um Replace zu verwenden, mußt du den gesamten String ersetzen gemäß deiner Bedingung. Dazu muß dein Pattern aber auch den gesamten String erkennen, was bei deinem nicht der Fall ist, das erkennt nur die Hexwerte. Das Pattern zur Erkennung des gesamten Strings (mit Gruppierung der Hexwerte) könnte z.B. so aussehen: '(.+")((\\x([\da-fA-F]+)\\x)(?:[\da-fA-F]+))+(".+)'
    Aber dadurch, dass die gesuchte Gruppe mehrfach auftritt kannst du keine klare Ersetzung vornehmen.

  • Die "optimale" Lösung wär eigentlich wenn die Matches Unique wären.
    So muss er wie hier in dem Beispiel mehrmals versuchen "\x20" oder andere zu ersetzen obwohl diese ja bereits ersetzt wurden.
    Es gibt also zu viele unnötige Schleifendurchläufe.
    Könnte man mit ArrayUnique machen aber falls jemand da eine Lösung kennt wie man über die regexp-Patterns gleich anweisen kann Matches nur 1x zu finden wär das echt klasse. :)

    Edit: Teilerfolg: Mit dem Pattern kann ich schonmal die Nummern finden die doppelt vorkommen:

    [autoit]

    StringRegExp($Text, "\\x([\da-f]+).+?(?=)", 3)

    [/autoit]

    Edit2: SO ists "optimal" ;) :

    [autoit]

    For $i in StringRegExp($Text, "(?m)\\x([\da-f]+)(?!.*?\\x\1.*?)", 3)
    $Text = StringReplace($Text, "\x" & $i, Chr(Dec($i)))
    Next

    [/autoit]

    Edit3: Du hast Zahlen von über 400 - ASCII kennt aber nur 256 - sicher das du Chr() nehmen musst?

    8 Mal editiert, zuletzt von AspirinJunkie (14. Juni 2010 um 12:37)

  • Super, das letzte Codebeispiel hat funktioniert. Ich werde mich wohl oder übel doch intensiver mit RegEx auseinander setzen müsssen, auch wenn es das letzte Haupthaar kostet. Und ja, es müsste im Code natürlich ein chrW() sein da ich fremde Zeichensätze (hier z.B Kyrillisch) erwarte.

    Beim Lesen einer kompletten RC Datei (4900 Zeilen) treten aber dennoch zwei/drei Fehler auf: einmal wird ein einzelner (!) Wert "übersehen", einmal ganze Zeilen und an einer anderen Stelle bricht das Programm der folgenden Fehlermeldung ab.

    C:\Users\tape\Desktop\ZSK\rc2utf.au3 (29) : ==> Variable must be of type "Object".:
    For $j in StringRegExp($aFile[$i], "(?m)\\x([\da-f]+)(?!.*?\\x\1.*?)", 3)
    For $j in StringRegExp($aFile[$i], "(?m)\\x([\da-f]+)(?!.*?\\x\1.*?)", 3)^ ERROR

    Hier ist die "Musterdatei":

    In den Zeilen 1 und 2 wird der Wert "\x44" nicht gefunden/übersetzt, Zeile 9 wird nicht übersetzt und bei Zeile 13 bricht das Programm mit der obigen Fehlermeldung ab. Kommt da der Regex durcheinander??


    Bei den Zeilen 1 und 2 erhalte ich folgendes Ergebnis ("\x44" wird nicht übersetzt, in allen (5) Vorkommen in der Datei):

    Code
    CTEXT       	L"     	\x44IP переключатели      	",IDST_TITEL,0,0,
    	CTEXT       	L"     	\x44IP переключатели      	",IDST_TITEL,0,0,

    Zeile 9, ist unverändert

    Code
    IDS_MAX_DREHZAHL_PAILLETTEN L"\x41c\x430\x43a\x441\x438\x43c\x430\x43b\x44c\x43d\x430\x44f\x20\x441\x43a\x43e\x440\x43e\x441\x442\x44c\x20\x431\x43b\x435\x441\x442\x43e\x43a\x20\x20\x20\x20\x20"

    Das Problem aus Zeile 13 tritt in der großen Datei mehrfach auf, wenn ich dort jeweils das Komma am letzten Anführungszeichen entferne , dann geht es ohne Fehler weiter.

    Hier mal zur Ansicht, alle Vorkommen bei denen dieser Fehler auftritt:

    Code
    CONTROL     	"DEL",IDPB_BACKSPACE,"T8PicPushbutton",WS_TABSTOP | 
    	CONTROL     	"DEL",IDPB_BACKSPACE,"T8PicPushbutton",WS_TABSTOP | 
    	CONTROL     	"Cancel",IDPB_ESC,"T8Pushbutton",WS_TABSTOP,410,497,380,
    	CONTROL     	"Cancel",IDPB_ESC,"T8Pushbutton",0x0,470,497,320,47
    	CONTROL     	"Simuliere Kopfartwechsel",IDCB_KOPFART_WECHSEL,"Button",
    	CONTROL     	"Simuliere Stichartwechsel",IDCB_STICHART_WECHSEL,"Button",

    Und zu guter Letzt, einmal dat janze Programm.

    [autoit]

    #include <String.au3>
    #include <file.au3>
    #include <array.au3>
    #include <Constants.au3>

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

    Dim $sDrive, $sDir, $sName, $sExt

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

    ; Select file to convert
    $file = FileOpenDialog("Select RC file", ".\", "RC files (*.RC)", 1 + 2)
    If @Error Then
    MsgBox(4096, "Error", "No file(s) chosen")
    Exit
    EndIf

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

    ; create new file name based on the previous selected
    $aArray = _PathSplit($file, $sDrive, $sDir, $sName, $sExt)
    $sNewFile = $aArray[1] & $aArray[2] & $aArray[3] & "_utf.rc"

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

    ; create new text file
    $newfile = FileOpen($sNewFile, 2+256)

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

    Local $aFile = StringSplit(FileRead($file),@CRLF,3)

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

    For $i = 0 to Ubound($aFile)-1
    $aLine = _StringBetween($aFile[$i], 'L"', '",')
    If @error Then
    FileWrite($newfile, $aFile[$i] & @CRLF)
    Else
    For $j in StringRegExp($aFile[$i], "(?m)\\x([\da-f]+)(?!.*?\\x\1.*?)", 3)
    $aFile[$i] = StringReplace($aFile[$i], "\x" & $j, ChrW(Dec($j)))
    Next
    FileWrite($newfile, $aFile[$i] & @CRLF)
    EndIf
    Next

    [/autoit]

    Ich vermute ein kleines Durcheinander beim RegEx, zumal es ja "fast immer" funktioniert.

    Gruß aus Ludwigsburg
    Zzzzzzz
    Anton

  • Das Problem mit Zeile 9 liegt am

    Code
    $aLine = _StringBetween($aFile[$i], 'L"', '"')


    Da der Quellcode hier den String nicht mit einem ", beendet hatte. Einen hätte ich also schon.. sigh

  • Das Problem mit Zeile 9 liegt am

    Code
    $aLine = _StringBetween($aFile[$i], 'L"', '"')


    Da der Quellcode hier den String nicht mit einem ", beendet hatte.

    Ich hatte versucht die Anzahl der zu überprüfenmden Zeilen zu minimieren indem ich Strings suche die in [L"...",] eingeschlossen sind. Das scheint aber nicht optimal zu sein. Ändere ich den _StringBetween aber auf

    Code
    $aLine = _StringBetween($aFile[$i], '"', '",')


    Dann hagelt es nur noch die Fehlermeldungen. Das scheint für den RegEx zuviel zu sein..?!

    Den momentanen RegEx habe ich noch nicht ganz verstanden, ich sehe nicht ob/wo er den String durch " begrenzt. Müsste er nicht folgendes abfragen (auch um den vorherigen _StringBetween einzusparen): Suche String der mit einem " beginnt, ein oder mehrere "\x" mit folgenden 2-3 stelligen beinhaltet und der mit einem " beendet wird? Uh.. das war jetzt nicht"RegEx mäßig ausgedrückt.. :)

    Guten Morgen aus Lubu :)
    Anton

    Einmal editiert, zuletzt von tapester (15. Juni 2010 um 09:05)

  • Das mit der \x44 liegt tatsächlich am RegEx-Pattern.
    Hier war der Versuch einen Match nur einmal zurückzugeben.
    Deswegen gibt er nur die Funde zurück bei welchen kein entsprechender Fund nochmal danach folgt. (er gibt quasi von einer Zahl immer nur die letzte zurück).
    Hier folgt aber noch \x44e - und daher scheitert das ganze.
    Aber das war erstmal nur für die B-Note.
    Daher sollte der 1. und simplere Ansatz wohl weiterhelfen:

    Spoiler anzeigen
    [autoit]

    $Text = 'CTEXT L"\x20\x20\x20\x20\x20\x20\x20\x20\x20\x44\x49\x50\x20\x43f\x435\x440\x435\x43a\x43b\x44e\x447\x430\x442\x435\x43b\x438\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20",IDST_TITEL,0,0,' & @CRLF & _
    'CTEXT L"\x20\x20\x20\x20\x20\x20\x20\x20\x20\x44\x49\x50\x20\x43f\x435\x440\x435\x43a\x43b\x44e\x447\x430\x442\x435\x43b\x438\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20",IDST_TITEL,0,0,' & @CRLF & _
    'LTEXT L"\x412\x432\x435\x434\x438\x442\x435\x21\x20",IDST_EINGABEFEHLER,330,444,136,26,NOT' & @CRLF & _
    ' WS_VISIBLE' & @CRLF & _
    'CONTROL L"\x412\x44b\x445\x43e\x434\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20",IDPB_ESC,"T8Pushbutton",' & @CRLF & _
    ' WS_TABSTOP,410,497,380,47' & @CRLF & _
    'CONTROL L"\x423\x441\x442\x430\x43d\x43e\x432\x43a\x430\x20\x438\x437\x43c\x435\x43d\x435\x43d\x438\x439\x20\x20\x20",IDPB_RETURN,"T8Pushbutton",' & @CRLF & _
    ' WS_TABSTOP,10,497,380,47' & @CRLF & _
    ' IDS_MAX_DREHZAHL_PAILLETTEN L"\x41c\x430\x43a\x441\x438\x43c\x430\x43b\x44c\x43d\x430\x44f\x20\x441\x43a\x43e\x440\x43e\x441\x442\x44c\x20\x431\x43b\x435\x441\x442\x43e\x43a\x20\x20\x20\x20\x20"' & @CRLF & _
    'CONTROL "0",IDPB_0,"T8PicPushbutton",WS_TABSTOP | 0x300,729,558,' & @CRLF & _
    ' 54,23' & @CRLF & _
    'CONTROL "1",IDPB_1,"T8PicPushbutton",WS_TABSTOP | 0x300,650,558,' & @CRLF & _
    ' 54,23' & @CRLF & _
    'CONTROL "DEL",IDPB_BACKSPACE,"T8PicPushbutton",WS_TABSTOP |' & @CRLF & _
    ' 0x300,571,558,54,23'

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

    $aRE = StringRegExp($Text, "\\x([\da-f]+)", 3)
    $aRE = _ArrayUnique_($aRE)

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

    If Not @error And IsArray($aRE) Then
    For $i In $aRE
    $Text = StringReplace($Text, "\x" & $i, Chr(Dec($i)))
    Next
    EndIf
    ConsoleWrite($Text)

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

    ;By Eukalyptus, AspirinJunkie
    Func _ArrayUnique_(ByRef $aA)
    Local Static $oD = ObjCreate('Scripting.Dictionary')
    For $i In $aA
    If Not $oD.Exists($i) Then $oD.Add($i, 0)
    Next
    Local $aR = $oD.Keys()
    $oD.RemoveAll
    Return $aR
    EndFunc

    [/autoit]

    Den Fehler mit deiner Zeile 13 wo nichts übersetzt wird kann ich allerdings bei mir nicht rekonstruieren.
    Probier es nochmal mit der Variante hier.
    Ansonsten brauchst du das ganze ja nicht Zeilen zu trennen da du die Filterung ja gleich auf die ganze Datei anwenden kannst.
    Und wenn du doch Zeilen brauchst dann wäre FileReadLine wohl besser da dort nicht die gesamte Datei in den Arbeitsspeicher gehauen wird.

  • Hmm.. scheint so rum besser zu sein, werde ich später versuchen und auch gleich auf zeilenweises lesen ändern. Hatte meinen letzten Beitrag gerade geändert als Du deinen geschreiben hattest, Zeile 9 mit dem nicht geänderten Text war wg. _StringBetween.

    Anton