RegEx CR LF Tab Kombinationen austauschen

  • Allgemein

Es gibt 26 Antworten in diesem Thema. Der letzte Beitrag () ist von Niko Ortner.

    RegEx CR LF Tab Kombinationen austauschen

    Da ich mich mit RegEx nicht auskenne, frage ich hier mal:

    Ich habe verschiedene Barcode-Etiketten mit verschiedenen Scannern einzulesen.

    Mein Programm funktioniert, ich möchte es nur vereinfachen und universeller machen.

    Beispiel

    Zeile1 CR
    Zeile2 CR
    Zeile3 CR

    oder

    Zeile1 CRLF
    Zeile2 CRLF
    Zeile3 CRLF

    oder

    Zeile1 CRLF
    LF
    Zeile2 CRLF
    LF
    Zeile3 CRLF
    LF
    CRLF

    Das soll alles am Schluss so aussehen:

    Zeile1 |Zeile2 |Zeile3 |

    Also sollen beliebige Kombinationen von CarriageReturn (CR) und LineFeed (LF) in je ein Pipe (|) gewandelt werden.

    Wie, bzw. geht so etwas mit Regex?

    Eierlein schrieb:

    Wofür Regex?


    Weil ich es auf deine Methode schon kann. :D

    Und zweitens möchte ich, wenn mehrere CR oder LFs oder deren Kombinationen hintereinander kommen, diese
    in einem Rutsch in ein einziges "|" wandeln. Mit Replace benötige ich mehrere Schritte.
    Hi
    praktischer wäre es einen Enumerator zu nehmen und alle Zeichen zu überprüfen. Bin für so eine triviale Aufgabe nicht für Regex zu haben, da das wesentlich benutzerunfreundlicher ist, als 'ne boolsche Variable und nen Enumerator. Falls du das in C# programmieren könntest, wäre auch unsafe möglich.

    Gruß
    ~blaze~

    Lightsource schrieb:

    Weil ich es auf deine Methode schon kann. :D
    Schreibst Du für jedes Problem 17 Programme mit unterschiedlichen Methoden bzw. Lösungswegen?
    Oder willst Du einfach RegEx testen?
    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!
    Und zweitens möchte ich, wenn mehrere CR oder LFs oder deren Kombinationen hintereinander kommen, diese
    in einem Rutsch in ein einziges "|" wandeln.


    Ein Schritt:

    VB.NET-Quellcode

    1. Dim s As String = "Zeile1" & vbCrLf & _
    2. "Zeile2" & vbCr & _
    3. "Zeile3" & vbCrLf & _
    4. vbLf & _
    5. "Zeile4" & vbCrLf
    6. Debug.Print(s)
    7. s = s.Replace(vbLf, "").Replace(vbCr, "|")
    8. Debug.Print(s)
    @Eierlein:
    Hast Du das auch getestet?
    Da würde jedes CrLf in zwei || umgewandelt werden, weil CrLf ist nichts anderes als Cr & Lf, welche beide separat in | umgewandelt werden würden.

    @LightSource:
    Zuerst alle CrLf in | umwandeln (dadurch kommen zwei hintereinander schon mal nicht mehr vor), dann Cr, dann Lf.

    VB.NET-Quellcode

    1. Dim CRChar = Convert.ToChar(13)
    2. Dim LFChar = Convert.Tochar(10)
    3. Output = Input.Replace(CRChar & LFChar, "|"c).Replace(CRChar, "|"c).Replace(LFChar, "|"c)


    Aber vorsicht: Hier wird der gesamte String drei mal durchlaufen. Das kann unter Umständen bei längeren Strings zeitkritisch werden. Ich habe es nicht getestet, deshalb kann ich es nicht genau sagen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Was dann, wenn der User Lf eingibt?

    VB.NET-Quellcode

    1. Public Shared Function CreateFrom(ByVal value As String) As String
    2. Dim buffer As New System.Text.StringBuilder(value.Length)
    3. Dim replacedChars() As Char = New Char() {CChar(vbTab), CChar(vbCr), CChar(vbLf)}
    4. Dim replaced As Boolean = False
    5. For i As Integer = 0 To value.Length - 1
    6. If Array.IndexOf(replacedChars, value(i)) = -1 Then
    7. buffer.Append(value(i))
    8. replaced = False
    9. ElseIf Not replaced Then
    10. buffer.Append("|"c)
    11. replaced = True
    12. End If
    13. Next
    14. Return buffer.ToString()
    15. End Function


    Gruß
    ~blaze~

    RodFromGermany schrieb:

    Lightsource schrieb:

    Weil ich es auf deine Methode schon kann. :D
    Schreibst Du für jedes Problem 17 Programme mit unterschiedlichen Methoden bzw. Lösungswegen?
    Oder willst Du einfach RegEx testen?



    Das ist eine gute Frage. Ich hatte tatsächlich schon dran gedacht, eine Art Modulsystem zu verwenden.
    sobald neue Barcodes oder neue Scanner dazu kommen, müsste ich dann ein neues Modul schreiben.
    Um das klar zu stellen: Barcodes können von verschiedenen Quellen kommen, die CR und LF unterschiedlich
    handhaben. Und Scanner kann man zwar meistens konfigurieren, aber da wir etwa 10 Scanner verwenden
    muss ein Ersatzgerät sofort funktionieren und nicht erst noch von mir konfiguriert werden.
    Wenn ich eine wirklich allgemein verwendbare Funktion hätte, müsste ich nicht jedesmal das Programm
    neu umschreiben.


    @Niko
    Dein Einwand ist genau das wie ich es auch verstehe. Es gibt alle möglichen Kombinationen von
    LFs und CRs. Vorwärts, rückwärts doppelt und was weiß ich nicht alles. Es sollen nie
    doppelte "||" entstehen.

    Es ist auch so, dass das mit den CRs und LFs nur der Anfang ist. Leider werden manchmal
    CHR(16) und ähnliche Zeichen mit geschickt. Diese müssen auch eliminiert werden.

    Die Geschwindigkeit ist auch ein Thema.
    Da diese Scanner wie ein Keyboard funktionieren lasse ich die Daten in eine Textbox fließen.
    von dort werden sie zurecht gestutzt und in eine Oracle-Datenbank geschrieben.
    Das gehört hier aber nur am Rande zum Thema.

    Im Prinzip will ich einfach nur alle Delimiter als ein Delimiter haben, und alle unnötigen Zeichen löschen.



    Wo ist nun das Problem mit Regex?
    Ist es für diese Zwecke nicht geeignet, zu unübersichtlich, nicht wartbar, zu langsam?
    Ich schätze Regex als relativ langsam für kleine Strings ein. Bei größeren Strings sollte der Aufwand geringer werden, sofern der resultierende Code bzw. die Fallanalyse optimiert ist. Im Normalfall ist allerdings eine manuelle Programmierung fallspezifischer. Regex wächst auch nicht einfach aus dem Boden. Teilweise wird der Code erst kompiliert (weiß nicht, wie es das FW-interne Regex handhabt, aber ich glaube, dass das als optionales Flag übergeben werden konnte) und die Codegenerierung, etc. dauert halt doch ein ganzes Stück, da es nicht als konstanter Ausdruck gewertet werden dürfte. Außerdem macht es nichts anderes, als den manuellen Code. Insgesamt ist also vmtl. der Code ohne Regex praktischer. Wenn du schon mal ein Regexgefummel vor dir hattest, weißt du sicher, wie gut wartbar das Teil ist. Da sollte es per Index/Enumerator verständnlicher sein. Wenn du es noch etwas performanter gestalten willst, verwende C# und Zeigerarithmetik. Per

    C-Quellcode

    1. public unsafe static string CreateFrom(string value)
    2. {
    3. string res = new string('\0', value.Length);
    4. bool replaced = false;
    5. fixed (char* c = value, cb = res)
    6. {
    7. char* cdest = cb;
    8. for(char* current = c; *current != '\0'; current++)
    9. if (*current != '\t' && *current != '\n' && *current != '\r')
    10. {
    11. *cdest++ = *current;
    12. replaced = false;
    13. }
    14. else if (!replaced)
    15. {
    16. *cdest++ = '|';
    17. replaced = true;
    18. }
    19. }
    20. return res;
    21. }


    könnte man das z.B. lösen.
    Was gefällt euch an meiner Lösung nicht?

    Gruß
    ~blaze~
    @Eierlein: Sorry, hab mich verlesen. Ich hab das "" übersehen und mir "|" gedacht.
    Wie von Blaze schon erwähnt: da ist das Problem, dass dann Lf alleine einfach rausgelöscht werden.

    Tjo, die einfachste Lösung wäre tatsächlich die von petaod.
    Du könntest Dir in einer Datei die Barcode-Scanner-IDs (oder wie auch immer Du herausfindest, von welchem Scanner das kommt) hinterlegen.

    Quellcode

    1. Scanner1:13;10
    2. Scanner12345:16;10;53;12;34
    3. ...

    VB.NET-Quellcode

    1. Dim Delimiters As New Dictionary(Of ScannerID, String)
    2. 'Delimiter aus Ini lesen:
    3. '(Keine Fehlerbehandlung)
    4. For Each i As String In System.IO.File.ReadAllLines("Pfad", System.Text.Encoding.Default) 'Bin mir mit dem Encoding nicht sicher. Aber da sowieso nur Zeichen im ASCII Bereich verwendet werden sollten, kann man auf Default lassen.
    5. Dim PointIndex As Integer = i.IndexOf(":"c)
    6. Dim NewID As String = i.Remove(PointIndex)
    7. Dim CharStrings As String() = i.SubString(PointIndex + 1).Split(";"c)
    8. Dim Chars As Char() = Array.ConvertAll(CharStrings, Function(Item As String) Convert.ToChar(Convert.ToByte(Item)))
    9. Dim NewDelimiter As String = String.Join("", Chars)
    10. Delimiters.Add(NewID, NewDelimiter)
    11. Next
    12. 'Verwendung:
    13. Output = String.Join("|"c, Input.Split({Delimiters(GetCurrentScannerID())}, StringSplitOptions.None))

    Die Überladung String.Split(String(), StringSplitOptions) splittet an einem ganzen String. Den String stellen wir anhand der Zahlen, die in der Ini stehen zusammen und wählen ihn anhand der ID des Scanners aus... Du kannst auch eine Einstellungsmöglichkeit machen, bei der man auswählen kann, welchen Scanner man verwendet. wenn er vorhanden ist, verwendet man den Delimiter, wenn nicht, legt man den Delimiter neu an.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Niko Ortner“ ()

    VB.NET-Quellcode

    1. Module Module1
    2. Sub Main()
    3. Dim s As String = "Zeile1" & vbCrLf & _
    4. "Zeile2" & vbCr & _
    5. "Zeile3" & vbCrLf & vbLf & _
    6. "Zeile4" & vbLf & _
    7. "Zeile5" & Chr(16) & _
    8. "Zeile6" & vbLf & vbCr & _
    9. "Zeile7" & vbCrLf
    10. Debug.Print(s)
    11. s = s.Replace(vbLf, "|").Replace(vbCr, "|").Replace(Chr(16), "|")
    12. While s.Contains("||")
    13. s = s.Replace("||", "|")
    14. End While
    15. Debug.Print(s)
    16. End Sub
    17. End Module
    Die Split-Lösung werde ich mal testen.

    Leider gibt es keine Scanner-IDs.
    Ich muss anhand der Daten heraus finden welche Etiketten es sind.

    Das Scannerprogramm liegt zentral auf dem Server und wird von
    verschiedenen Programmen aufgerufen. Welches Programm dies
    tut übergebe ich in einer Programmstart-Variablen

    @Eierlein
    Klar, ist schon am Übersichtlichsten.
    Das "While" gefällt mir halt nicht so gut. Das war auch ein Grund warum ich an Regex dachte.

    Aber wenn das stimmt, dass intern RegEx sozusagen in Replace gewandelt wird, ist es
    wohl doch nicht so toll. Gegen Regex spricht auch die Wartbarkeit, sowie meine
    Unerfahrenheit damit.

    Grade erinnere ich mich noch an ein Problem bei diesen Daten.
    Um festzustellen, ob der Code fertig eingelesen ist, habe ich bisher
    immer auf eine bestimmte Kombination von CR und LF gewartet.
    Also ein Austausch aller CRs und LFs on the fly ist auch nicht drin.
    So ein Beispiel hatte hier ja auch schon jemand vorgeschlagen...

    Eierlein schrieb:

    VB.NET-Quellcode

    1. While s.Contains("||")
    2. s = s.Replace("||", "|")
    3. End While
    Also entweder ich habe grad eine falsche Vorstellung der Replace-Funktion oder die While-Schleife ist unnötig. Imo ersetzt Replace alle Vorkommen des Strings, danach kommt eh kein "||" mehr vor.

    VB.NET-Quellcode

    1. ResultString = String.Join("|", SourceString.Split({Chr(10), Chr(13), Chr(16)}, StringSplitOptions.RemoveEmtpyEntries))
    Ungetestet, da momentan keine Entwicklungsumgebung verfügbar.

    Ich habe keine Ahnung, was das in punkto Performance gegenüber der Variante von blaze ausmacht.
    Aber dafür ist das Statement kurz und verständlich.
    Performance wird sowieso überbewertet, solange es sich nicht gerade um zig Millionen von Datensätzen oder Gigabyte-Dokumente handelt.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --
    Nein, aus Regex wird kein Replace-Konstrukt gemacht. Es wird einfach der Source-String so interpretiert oder kompiliert, dass eine Auswertung des Ausdrucks so erfolgt, wie angegeben. Dabei kann nie etwas performanteres rauskommen, als wenn man selbst den Code optimal programmiert. Solche Codes kann man aber häufig mit geringem Aufwand selber erzeugen. Regex bringt im Fall von konstanten Ausdrücken auf jeden Fall eine Performance-Einbuße gegenüber der selbstprogrammierten Variante mit (abgesehen halt von den Sprachrestriktionen).

    Solange es dann anständig funktioniert spielt die Performance kaum eine Rolle, aber warum für ein Kernelement 10 Zeilen weniger?

    Gruß
    ~blaze~
    @Eierlein:
    Vorsicht:
    Das geht nicht gut, wenn tatsächlich ein leerer String zwischen zwei Delimitern steht.

    @petaod:
    Das ersetzt alle Chr(10), alle Chr(13) und alle Chr(16) durch "|".
    Dadurch lassen sich die Kombinationen nicht als einzelne ersetzen.

    @Beide
    Das geht natürlich in Ordnung, wenn nie leere Strings zwischen Delimitern stehen können.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils