TableData - effizientere Arbeit mit tabellenstrukturierten Daten

  • Worum geht es?

    In AutoIt haben wir es oft mit tabellenartig strukturierten Daten zu tun.
    Je nach Quelle müssen wir teils einen erheblichen Codeaufwand betreiben um die Daten in eine Form zu bringen, mit der wir weiterarbeiten können.
    Anschließend wird das Arbeiten mit diesen Daten auch nicht unbedingt einfacher, da wir, anstatt auf die sprechenden Namen der Attribute der Daten zuzugreifen, wir es mit numerischen Indizes zu tun haben bei denen man sehr schnell die Übersicht verliert.

    Beide Probleme werden durch die UDF adressiert.
    Im Grundansatz arbeitet die UDF mit einem Table-Objekt (lediglich eine AutoIt-Map), in welcher die Daten vom Header getrennt werden.
    Das ermöglicht eine sauberere Prozessierung der Daten.
    Sie bietet Funktionen um aus verschiedenen Quellen Daten einzulesen (CSV, Array, string with fixed-width-columns, Strings mit eigenen Spaltentrennungen).
    Innerhalb dieses Aufrufes werden die Daten vom Header getrennt oder diesen überhaupt erst Headerdaten hinzugefügt.
    Die Daten werden dann entsprechend ihrem Format aufgearbeitet (bei csv Quotes und Escapes entfernt, bei fixed-width Leerräume entfernt...).
    Darüber hinaus kann der User direkt für jede Spalte einzeln festlegen wie genau die Daten aufgearbeitet werden sollen. Er ist hierbei komplett frei.
    Auf diese Art erhalten die Daten bereits beim Einlesen das Endformat in welchem sie weiterverarbeitet werden sollen.

    Anschließend können die Daten spaltenbasiert oder Attributbasiert behandelt werden.
    Sprich: Anstatt mit Indizes können auch einfach die Attributnamen der Daten verwendet werden - das ermöglicht eine bessere Übersichtlichkeit im Code.

    Beispiel (umfangreiche weitere Beispiele im Unterordner "examples")

    Wir möchten die offenen Ports auf einem Rechner mit AutoIt auswerten.
    Der Kommandozeilenbefehl lautet hierfür netstat -t und bringt uns folgende Ausgabe (bei euch je nach System evtl. anders strukturiert):

    Code
    Active Internet connections (w/o servers)
    
    Proto Recv-Q Send-Q Local Address           Foreign Address         State
    tcp        0    116 192.168.64.110:ssh      192.168.29.200:65069    ESTABLISHED
    tcp        0      0 192.168.64.110:ssh      192.168.29.200:65068    ESTABLISHED

    Um diese Daten sinnvoll weiterzuverarbeiten müssen wir gegebenenfalls folgende Schritte in AutoIt durchführen:

    • Löschen der ersten unnützen Zeilen
    • Extraktion der Kopfzeile (sie wird anders behandelt als die Daten)
    • Erzeugung eines Arrays mit korrekter Dimension um die Daten zu halten
    • Trennung der Daten entweder anhand von Leerräumen oder anhand der jeweils festen Spaltenbreite
    • Befreiung der Daten von unnötigen Leerräumen
    • Konvertierung der Recv-Q und Send-Q-Spalte in einen Zahlendatentyp (für Sortierung usw)
    • Auftrennung der Adressdaten in IP-Adresse und Port

    Das kann eine Menge (fehleranfälligen) Code-Aufwand bedeuten.
    Mit dieser UDF hingegen kann man das ganze folgendermaßen in einem einzigen Aufruf lösen:

    und wir erhalten als Resultat:

    Wenn man nun Funktionen wie _td_toObjects() nutzt, dann kann man die einzelnen Daten mit Ausdrücken wie $aData.Proto oder $aData.State prozessieren.
    Das sollte deutlich übersichtlicher sein als sich mit den Arrayindizes herumzuschlagen.

    Funktionsumfang

    Funktion

    Beschreibung

    Eingabe

    _td_fromStringkonvertiert einen Tabellenartig strukturierten String, bei welchem die Spaltentrenner über einen regulären Ausdruck beschrieben werden können, in ein Tabellenobjekt
    _td_fromCsvLiest eine Datei oder String in ein Tabellenobjekt, welcher als csv, tsv oder ähnlichem (anpassbaren) Format strukturiert ist.
    _td_fromFixWidthKonvertiert einen tabellenartig strukturierten String in ein Tabellenobjekt, bei dem die Spalten feste Zeichenbreiten haben (üblicherweise Konsolenausgaben oder printf-Tabellen)
    _td_fromArrayErzeugt ein Tabellenobjekt aus einem vorhandenen Array

    Ausgabe

    _td_toCsvKonvertiert ein Tabellenobjekt in einen csv/tsv-formatierten String
    _td_toFixWidthKonvertiert ein Tabellenobjekt in einen String, in welchem die Spalten feste Breiten haben
    _td_displayStellt ein Tabellenobjekt ähnlich _ArrayDisplay dar (mit Headerbezeichnern und Sortiermöglichkeit)
    _td_toArrayKonvertiert ein Tabellenobjekt in ein 2D-Array bei welchem der Header in der ersten Zeile steht (wenn man nur die Daten haben möchte stattdessen $mTable.Data nutzen)

    Bearbeitung von Tabellen

    _td_joinVerknüpft 2 Tabellenobjekte ähnlich wie ein SQL-Join
    _td_filterFiltert Datensätze in Tabellenobjekten ähnlich zu einem SQL-Where

    Aufbereitung zur effizienteren Weiterbehandlung

    _td_toObjectsKonvertiert ein Tabellenobjekt in eine Liste von Datenobjekten. Jeder Eintrag ist damit eine Map bei welcher die Attribute über den Namen angesprochen werden können.
    _td_MapsToTableKonvertiert eine Liste von Maps (wie von _td_TableToMaps) in ein 2D-Array
    _td_toPrimaryKeysKonvertiert ein Tabellenobjekt in eine Map für die Datensätze, bei welcher ein Primärschlüsselattribut der Daten als Key verwendet wird. (ermöglicht Zugriff auf die Daten mit Namen anstatt Indizes)
    _td_toColumnsKonvertiert ein Tabellobjekt in eine Map, welche als Einträge die einzelnen Spalten als 1D-Array enthalten.
    _td_getColumnExtrahiert eine Spalte aus einem Tabellenobjekt


    >>Download und Quellcode auf GitHub<<

    4 Mal editiert, zuletzt von AspirinJunkie (21. Februar 2024 um 11:24)

  • Coole UDf die den Umgang mit Tabellendaten sehr erleichtert :thumbup:
    Eine Kleinigkeit ist mir aufgefallen:
    Im Header der Funktion _td_fromString findet sich der Parameter $nSkipRows 2x unter Parameters angeführt. Vermutlich Überbleibsel von copy & paste.

  • Vermutlich Überbleibsel von copy & paste.

    Das ist wohl mehr als wahrscheinlich.
    Das ganze Konstrukt schlummert schon seit ca. 5 Jahren auf meiner Festplatte - angefangen als vollumfänglicher csv-Parser.
    Aber eine richtige Gesamtidee für eine UDF war nicht wirklich erkennbar.
    Daher hatte es eine ganze Weile gebraucht um da eine sinnvolle Klammer drumherum zu machen.
    Bei der abschließenden Fleißarbeit drumherum (Kommentare, Beispiele etc.) werden wohl auf die Art ein paar Schnitzer reingelangt sein.

  • Kann ich mir gut vortellen ;)
    Schade finde ich nur, dass wir hier weiterhin keine Möglichkeit haben eine Übersicht von hilfreichen UDFs zu erstellen (analog zum engl. Forum).
    Das wäre für viele Coder sicher sehr hilfreich.

  • water : Definitiv beides (diese UDF, als auch der "Einwand" der hilfreichen UDF's) Sinnvoll.

    Schon mal überlegt, ob es ggf. Sinnvoll wäre das um 1 Funktion zu erweitern: Optionaler Parameter um eine Spalte (entweder per Name oder Nummer) als "unique Identifier" (ab hier: UID) zu definieren (sollte in der Regel ja die ganz links sein, muss aber nicht).
    Über diesen dann z.B. per $mData.UID[$Column] bzw. $mData.UID.Proto oder ähnlich diesen direkt ansprechen zu können. Solche identifier hat man ja insbesondere gerne mal bei Datenbanktabellen.
    Oder bin ich mit dem Gedanken gerade alleine? :P

  • Interessanter Ansatz.
    Meinst du das in der Art?:

  • Sieht erstmal passend aus, wobei ich das als optionaler Parameter bei _td_fromArray sehen würde, also:

    Function _td_fromArray($aArray, ???, $Primarykey = False, $vPrimaryColumn = 0)

    Sprich: In der Funktion _td_fromArray dann wenn $Primarykey true ist am Ende auch _td_dataByPrimaryKey ausführen mit $vPrimaryColumn als 2. Parameter.
    Wird es nicht angegeben, wird es nicht ausgeführt und nicht gebraucht.

    Aber als Rückgabe würde ich dann auch erwarten, das er Aufruf $mData.Anna.salary wäre (sonst halt wie gehabt bei dir vorher). Geht das? Müsste doch eigentlich.

    Aber ansonsten fast 1:1 getroffen und respekt bei der schnellen Umsetzung.

    Einmal editiert, zuletzt von Moombas (18. Januar 2024 um 11:55)

  • Hm ne würde ich ungern machen.
    Neben der weiter anwachsenden Parameteranzahl würde ich auch ein paar "Prinzipien" in der UDF durcheinander hauen.
    Die _td_fromXXX-Funktionen haben gemeinsam, dass Sie alle das Table-Objekt mit .data und .header zurückgeben.

    Funktionen, welche diese dann je in andere Strukturen umwandeln, kommen dann danach als zweiter Schritt.
    Also die Funktionen _td_toColumns, _td_getColumn, _td_TableToMaps.
    Diese Funktion hier würde sich vom Ansatz her in letztere einordnen.

    Bei der Gelegenheit fällt mir aber auf, dass die Funktionsnamen noch nicht optimal sind.
    Wenn ich auf der einen Seite "-from"-Funktionen habe, dann sollten die anderen konsequent "-to"-Funktionen heißen.
    Das werde ich wohl sicher so noch mal überarbeiten.

  • OK ich habe die Benamung der "-to"-Funktionen jetzt komplett umgestellt und auch die vorgeschlagene Funktion _td_toPrimaryKeys() mit aufgenommen (Danke Moombas für die gute Idee).
    Wer für die Funktion evtl. einen schmissigeren Namen hat - gerne her damit - ich bin noch nicht ganz zufrieden damit.
    Außerdem ist noch eine kleine Funktion _td_toArray dazugekommen.

  • ich habe rein aus Interesse einfach mal meine sqllite tabelle aus dem db browser for SQLite kopiert und habe es einfach mal in einer text datei gesichert.(ansi)
    auf was beziehen sich immer die Freizeichen ? leider war die hälfte der Ergebnisse im Array Display abgeschnitten.


    edit: nun mit csv ausprobiert damit funktioniert es sehr leicht und ohne problme :)

    Einmal editiert, zuletzt von MojoeB (18. Januar 2024 um 15:37)

  • ==> Variable must be of type "Object".:
    MsgBox(0, "homepage", $mTable.homepage.WERT)
    MsgBox(0, "homepage", $mTable^ ERROR


    Wenn ich mir das array anschaue ist es so aufgbaut wie aus deinem beispiel von : example - toPrimaryKeys
    was mach ich falsch o.o`?

  • als beispiel : das als csv absichern:

    Halt ich verbessere die tabelle nochmal.... habs grad selbst mit dem string versucht da klappt das nicht ist flasch ausgerichtitet mom ich kopiere den gleich hier rein ich ändere nur ein paar daten.

    VARNAME,INWINDWO,ART,WERT,BESCHREIBUNG,FRAME
    homepage,unloggt,link,http://192.168.66.77/,Loginpage,
    homepage_backup,unloggt,link,http://192.168.54.22/,Loginpage,
    input_benutzer,unloggt,xpath,"//*[@id=""ctl00_ContentPlaceHolder1_Login1_UserName""]",Input Benutzerfeld,

    Einmal editiert, zuletzt von MojoeB (18. Januar 2024 um 16:32)

  • Du hast beim Einlesen mit _td_fromCsv() angegeben, dass in der CSV-Datei keine Header-Zeile vorhanden ist (4. Parameter = False).
    Die Spalten haben also keinen zugeordneten Namen, stattdessen wurde die Header-Zeile als normale Datenzeile behandelt.
    Da die Spalten keinen Namen haben kann auch _td_toPrimaryKeys nichts mit diesen Bezeichnungen anfangen.

  • Code
    VARNAME,INWINDWO,ART,WERT,BESCHREIBUNG,FRAME
    homepage,unloggt,link,http://192.168.66.77/,Loginpage,
    homepage_backup,unloggt,link,http://192.168.54.22/,Loginpage,
    input_benutzer,unloggt,xpath,"//*[@id=""ctl00_ContentPlaceHolder1_Login1_UserName""]",Input Benutzerfeld,
    input_passwort,unloggt,xpath,"//*[@id=""ctl00_ContentPlaceHolder1_Login1_Password""]",Input Passwortfeld,
    loginbtn,unloggt,xpath,"//*[@id=""ctl00_ContentPlaceHolder1_Login1_LoginButton""]",btn zum einloggen,
    login_error,unloggt,xpath,"//*[@id=""ctl00_ContentPlaceHolder1_Login1""]/tbody/tr/td/table/tbody/tr[6]/td",Fehler MSG beim Anmelden.,
    start2,Start2,hlink,linkt/gelink/Level09/Start2.aspx,wird am letzten teil einer Adresse hinzugefügt um einen Link aufzurufen.,
    tele,ExtCn,hlink,linkt/gelink/level05/ExtCntrl.aspx,wird am letzten teil einer Adresse hinzugefügt um einen Link aufzurufen.,

    hiermit funktioniert wieder das array.

  • achso das heißt ich muss den header auf true setzen damit
    VARNAME,INWINDWO,ART,WERT,BESCHREIBUNG,FRAME

    diese als spaltennamen angezeigt werden und dann sollte das mit _td_toPrimaryKeys funktionieren ?

    ich teste es sofort :)

    Ja so funktioniert es :) ich hatte nur mein array an dein beispiel angepasst, jetzt ergibt es auch sinn :D

  • Ja das ist das Konzept der UDF.
    Die Daten werden vom Header getrennt behandelt.
    Auf die Art kann man die Spalten und Attribute über die Namen ansprechen.
    Das bedingt aber halt, dass Headerinformationen da sind.

    Ein Blick in die Funktionsbeschreibung zeigt auf welche Arten man diese Header-Informationen per $vHeader-Parameter erhalten kann:

    • False: Es gibt keine Headerinfos - der User kann selbst im Nachgang diese hinzufügen, wenn er $mTable.Header mit einem entsprechenden 1D-Array beschreibt
    • True: Die erste Zeile enthält den Header
    • Array: Der User setzt eigene Headerelemente per Array
    • String: Der User setzt eigene Headerelemente per String (getrennt durch $sDelim - meistens "|")
  • also ich werde wahrscheinlich deswegen keine Probleme haben weil ich für meine daten selber sqlite nutze und dort die erste spalte immer als unique angeben habe.

    aber was geschieht wenn man in meinem beispiel 2mal homepage vorkommt?
    wie geht man dann mit den daten um?

    Das die UDF gefällt mir da ich auch grad fast alle meine guis mit maps erstelle.

  • aber was geschieht wenn man in meinem beispiel 2mal homepage vorkommt?

    Dann kannst du _td_toPrimaryKeys() halt einfach nicht nutzen, da Primärschlüssel nunmal eindeutig sein müssen.

    wie geht man dann mit den daten um?

    So wie du es brauchst und es sinnvoll ist.
    Du kannst die Daten als 2D-Array behandeln, wenn du $mData.Data nimmst.
    Du kannst einzelne oder mehrere Spalten per Name extrahieren wenn du _td_getColumns() verwendest.
    Du kannst Prinzipiell spaltenorientiert arbeiten, wenn du _td_toColumns() nimmst.
    Du kannst die Datensätze mit _td_toObjects() als Liste von Objekten behandeln, so dass du auf deren Attribute einfach per Name zugreifst.
    Du kannst die Daten anzeigen lassen (_td_Display), als csv-formatierten String ausgeben (_td_toCsv), als fixed-width-String ausgeben (_td_toFixWidth) oder als Array ausgeben (_td_toArray).

    Es kommt halt ganz auf den Anwendungsfall an.
    Zu allen Funktionen habe ich im example-Ordner Beispiele und die jeweiligen Funktionsheader sollten auch hilfreich sein.