print_r() zeigt Informationen über eine Variable in menschenlesbarer Form an.1)
In VBA vermisse ich eine Möglichkeit, die Inhalte von Arrays, Dictionaries etc zu Debugzwecken
sinnvoll auszugeben. Klar, es gibt im Debug-Mode den Watch. Doch jedes mal alles durchzuklicken ist umständlich.
Bei Dictionaries versagt der Watch total und man sieht immer nur den Key des nächsten Levels.
Des weiteren wünschte ich mir eine lesbare Ausgabe im Debug-Fenster um das Resultat in Foren oder
dieses Wiki kopieren zu können. Unter PHP gibt es da die Befehle var_dump() und print_r(). Diese dienten mir als Vorlage zu dieser VBA-Funktion print_r().
Die folgenden Vorteile hat print_r() gegenüber dem mitgelieferten debug.print:
Leider konnte ich die folgenden Punkte noch nicht umsetzen:
Es gibt neben der eigentlichen Funktion noch fast analoge, bei denen ggf die Paramter anderst vordefiniert sind
print_r()
Die eigentliche Funktiond()
Ein Alias zu print_r() mit denselben Paramtern. Ist praktisch im Debugfenster, da er so kurz istc()
Gibt das Resultat in einer Form zurück, die gleich wieder für weitere Zwecke verwendet werden kannprint_l
Wendet bei meinen bekannten ListenObjekten den Print_r nur auf die Liste an, nicht auf das ganze Objektprint_rm()
Wendet print_r() auf mehrere Elemente andRx()
Spezialfall zum vereinfachten Testen von RegEx Patternsanalyze()
Alias um ohne Paramter einen Rückgabewert zu erstellen. Praktisch in AbfragenIch beginne mit den Beispielen, da diese recht gut aufzeigen, wass die Funktion alles kann
Einfach einige Beispiele die ich im Direktfenster ausgeführt habe
print_r 123.4 <Double> 123.4 print_r 12345 <Integer> 12345 print_r "Hallo Welt " <String> 'Hallo Welt ' print_r NULL <Null> Print_r split("a b v") <String()> ( [0] => <String> 'a' [1] => <String> 'b' [2] => <String> 'v' )
'Text mit leerzeichen am Schluss. Das ist bei debug.print nichterkennbar ? "Hallo Welt " Hallo Welt 'Beim print_r() hingegen schon d "Hallo Welt " <String> 'Hallo Welt ' 'Und dasselbe mit nicht angezeigten Spezialteichen ? "Hallo Welt" & chr(1) Hallo Welt d "Hallo Welt" & chr(1) <String> 'Hallo Welt\u0001' 'Spezielle Leerzeichen (Zeilenumbruch, Tabulator) ? "Hallo" & vbtab & "Welt" & vbcrlf & "Wie geht es dir?" Wie geht es dir? Hallo Welt 'Normalsettingsfür print_f d "Hallo" & vbtab & "Welt" & vbcrlf & "Wie geht es dir?" <String> 'Hallo\tWelt\r\nWie geht es dir?' 'Ohne das spezelle Darstellen der Spaces d "Hallo" & vbtab & "Welt" & vbcrlf & "Wie geht es dir?", -prEscapeSpaces <String> 'Hallo\u0009Welt\u000D\u000AWie geht es dir?' 'Ohne Unicodeübersetzung von Sonderzeichen d "Hallo" & vbtab & "Welt" & vbcrlf & "Wie geht es dir?", -prEscapeSpaces-prEscapeNotPrintableChars <String> 'Hallo Welt Wie geht es dir?' 'Ganz ohne Paramters d "Hallo" & vbtab & "Welt" & vbcrlf & "Wie geht es dir?", prNoParams Hallo Welt Wie geht es dir?
Für diesen Test verwende ich die Cast-Funktionen von [VBA] JSON
'Normale Ausgabe im ImmadiateWindow ? obj2json(array(1, "a", "c""d", array(4, 5))) [1,"a","c\"d",[4,5]] 'Normaler print_r(). In dem Fall über den Alias d() d obj2json(array(1, "a", "c""d", array(4, 5))) <String> '[1,"a","c\"d",[4,5]]' 'Und zur Codeweiterverarbeitung geeignet c obj2json(array(1, "a", "c""d", array(4, 5))) "[1,""a"",""c\""d"",[4,5]]" 'Ausgabe direkt kopiert zum weitertesten d json2obj("[1,""a"",""c\""d"",[4,5]]", jrtArray) <Variant()> ( [0] => <Byte> 1 [1] => <String> 'a' [2] => <String> 'c"d' [3] => <Variant()> ( [0] => <Byte> 4 [1] => <Byte> 5 ) )
print_l wird vorerst nur für die folgenden Klassen unterstätzt. Ansonsten funktioniert er wie d(). Und da uch nur, wenn diese Objekt direkt übergeben wird. In einer Verschachtelung nicht mehr [VBA] Iterator & [VBA] ListStream
'Objekt initalisieren Set it = Iterator.instance(array(1,2)) 'Herkömlich mit d() d it <Class Module::Iterator> ( [paramDaoValue] => <Boolean> False [paramErrorAtEmptyList] => <Boolean> False [paramIndexInsteadKey] => <Boolean> False [paramListNextNoParamsAsToNext] => <Boolean> True [paramNothingAsEmptyList] => <Boolean> False [isInitialized] => <Boolean> True [isEmpty] => <Boolean> False [count] => <Long> 2 [BOF] => <Boolean> True [EOF] => <Boolean> False [absolutePosition] => <Long> -1 [index] => <Long> -1 [key] => <Long> -1 [source] => <Variant()> ( [#0] => <Integer> 1 [#1] => <Integer> 2 ) ) 'und so kommt es mit print_l print_l it <Variant()> ( [#0] => <Integer> 1 [#1] => <Integer> 2 )
Hier ein kleines Testbeispiel bei dem diverse verschiedene Typen ineinander verschachtelt sind
Public Sub testPrintR() Dim col As New Collection Dim dict As New Dictionary Dim arr(1) As Variant Dim FSO As New FileSystemObject 'Ein Array zusammenstellen arr(0) = "abc" arr(1) = 1345.43 'Ein Dictionaray zusammenstellen Call dict.add("a", "AAA") Call dict.add("int", 123) Call dict.add("dbl", 12.3) Call dict.add("arr", arr) Call dict.add("Null-Wert", Null) Call dict.add("Empty-Wert", Empty) Call dict.add("FSO", FSO) 'Und eine Collection Call col.add(Now()) Call col.add(dict) print_r col Set FSO = Nothing Set dict = Nothing Set col = Nothing End Sub
Und das ist die Ausgabe im Direkt-Fenster
<Collection> ( [1] => <Date> 29.10.2013 09:51:45 [2] => <Dictionary> ( [a] => <String> 'AAA' [int] => <Integer> 123 [dbl] => <Double> 12.3 [arr] => <Variant()> ( [0] => <String> 'abc' [1] => <Double> 1345.43 ) [Null-Wert] => <Null> [Empty-Wert] => <Empty> [FSO] => <FileSystemObject> ) )
Die ganzen MatchCollection, Match und Submatches können einfach über print_r ausgegeben werden
Public Sub testPrintRRegExp() Dim rx As New regExp rx.Pattern = "([a-c])#(\d+)" rx.Global = True 'Das Objekt rx analysieren print_r rx 'Und die Treffer print_r rx.execute("Wir haben die Schlüssel a#1, b#12 und c#9 zur Auswahl") Set rx = Nothing End Sub
<IRegExp2> ( [Pattern] => <String> '([a-c])#(\d+)' [Global] => <Boolean> True [IgnoreCase] => <Boolean> False [Multiline] => <Boolean> False ) <IMatchCollection2> ( [0] => <IMatch2> ( [Match] => <String> 'a#1' [FirstIndex] => <Long> 24 [SubMatches] => <ISubMatches> ( [0] => <String> 'a' [1] => <String> '1' ) ) [1] => <IMatch2> ( [Match] => <String> 'b#12' [FirstIndex] => <Long> 29 [SubMatches] => <ISubMatches> ( [0] => <String> 'b' [1] => <String> '12' ) ) [2] => <IMatch2> ( [Match] => <String> 'c#9' [FirstIndex] => <Long> 38 [SubMatches] => <ISubMatches> ( [0] => <String> 'c' [1] => <String> '9' ) ) )
Dim a(1, 1, 1) As Integer a(0, 0, 0) = 0 a(0, 0, 1) = 1 a(0, 1, 0) = 2 a(0, 1, 1) = 3 a(1, 0, 0) = 4 a(1, 0, 1) = 5 a(1, 1, 0) = 6 a(1, 1, 1) = 7 print_r a
ergibt
<Integer()> ( [0.0.0] => <Integer> 0 [0.0.1] => <Integer> 1 [0.1.0] => <Integer> 2 [0.1.1] => <Integer> 3 [1.0.0] => <Integer> 4 [1.0.1] => <Integer> 5 [1.1.0] => <Integer> 6 [1.1.1] => <Integer> 7 )
Als erstes mal ein kleines Klassenmodul mit 3 Properties. Nachname und Vorname als Get und Set. Dazu noch Name nur als Get, da dieser aus Vor- und Nachname zusammengesetzt ist.
Option Compare Database Private pVorname As String Private pNachname As String Public Property Get nachname() As String nachname = pNachname End Property Public Property Let nachname(ByVal vNewValue As String) pNachname = vNewValue End Property Public Property Get vorname() As String vorname = pVorname End Property Public Property Let vorname(ByVal vNewValue As String) pVorname = vNewValue End Property Public Property Get name() As String name = vorname & " " & nachname End Property
Dann mal ein kleines Testscript
Dim yaslaw As New TestClass yaslaw.vorname = "Yaslaw" yaslaw.nachname = "Kowalejeff" print_r yaslaw
Und so sieht es dann aus
<Class Module::TEstClass> ( [nachname] => <String> 'Kowalejeff' [vorname] => <String> 'Yaslaw' [name] => <String> 'Yaslaw Kowalejeff' )
Das ist die Standarteinstellung
print_r 123 print_r 123, prConsole
Dim txt as String txt = print_r(123, prReturn)
print_r 123, prConsole + prClipboard
print_rm "Hallo Welt", strToDate("01022013", "mmddyyyy"), split("1,2,3", ",") <String> 'Hallo Welt' <Date> 02.01.2013 <String()> ( [0] => <String> '1' [1] => <String> '2' [2] => <String> '3' )
'Standart mit Tabulator und Nicht druckbaren Leerzeichen print_r chr(29) & " A " & vbTab & " B " & chr(1) & " C" <String> '\u001D A \t B \u0001 C' 'Dito, aber ohne ersetzen des Tabulators print_r chr(29) & " A " & vbTab & " B " & chr(1) & " C", -prEscapeSpaces <String> '\u001D A B \u0001 C' 'Ausgabe von Zahlen und Texten ohne Anfühurnungszeichen print_r Array("a", "b" & vbTab & "c", 101), -prStringSingleQuotes <Variant()> ( [0] => <String> a [1] => <String> b\tc [2] => <Integer> 101 ) 'Ditp, mit Dippoelten Anführungszeichen print_r Array("a", "b" & vbTab & "c", 101), prParamsDefault -prStringSingleQuotes + prStringDoubleQuotes <Variant()> ( [0] => <String> "a" [1] => <String> "b\tc" [2] => <Integer> 101 ) 'Ausgabe ohne weitere formatierung print_r Array("a", "b" & vbTab & "c", 101), prNoParams ( [0] => a [1] => b c [2] => 101 ) 'Ausgabe eines Strings mit doppelten Anführungszeichen zur Weiterverarbeitung print_r "abc(""Hallo Welt"& chr(34) & ")", prEscapeDoubleQuotes+prStringDoubleQuotes "abc(""Hallo Welt"")"
Die Patterns sind nach [VBA] RegExp testen im Direktfenster zusammengestellt.
Siehe auch [VBA] cRegExp(), cRx()
Nur Pattern. Das RegExp Objekt wird ausgegeben
dRx "^(\d{3}).*\.(\w{3,})$" <IRegExp2> ( [Pattern] => <String> '^(\d{3}).*\.(\w{3,})$' [Global] => <Boolean> False [IgnoreCase] => <Boolean> False [Multiline] => <Boolean> False )
Mit einem Text zum Parsen wird automatisch geparst un dann ausgewertet
dRx "^(\d{3}).*\.(\w{3,})$", "123 Mein test.xlsx" <IMatchCollection2> ( [0] => <IMatch2> ( [Match] => <String> '123 Mein test.xlsx' [FirstIndex] => <Long> 0 [SubMatches] => <ISubMatches> ( [0] => <String> '123' [1] => <String> 'xlsx' ) ) )
Mit einem Replace-Text wird dies ebenfalls ausgewertet
dRx "^(\d{3}).*\.(\w{3,})$", "123 Mein test.xlsx", "OUT_$1.$2" <String> 'OUT_123.xlsx'
Natürlich kann man auch die normalen print_r() Paramters mitgeben
dRx "^(\d{3}).*\.(\w{3,})$", "123 Mein test.xlsx", "OUT_$1.$2", prStringDoubleQuotes "OUT_123.xlsx" dRx "^(\d{3}).*\.(\w{3,})$", "123 Mein test.xlsx", "OUT_$1.$2", prNoParams OUT_123.xlsx ? dRx("^(\d{3}).*\.(\w{3,})$", "123 Mein test.xlsx", "OUT_$1.$2", prNoParams, prReturn) OUT_123.xlsx
Die Funktion gibt mit den Standardeinstellungen den Text zurück. Das ist praktisch für den Einsatz in einer Abfrage/SQL.
Abfrageeditor
DebugMyField: analyze([MY_FIELD])
Im SQL
analyze([MY_FIELD]) AS DebugMyField
SELECT *, analyze([MY_FIELD]) AS DebugMyField FROM test_analyze; DebugMyField | ID | MY_FIELD | DESCRIPTION ------------------|----|----------|----------------------------------------------- <String> 'a' | 1 | a | Einfacher String <String> 'a ' | 2 | a | String mit folgenden Leerzeichen <Null> | 3 | | <String> ' ' | 4 | | Ein String der nur aus Leerzeichen besteht <String> '\n\r' | 5 | | Ein Zeilenumbruch (Wagenrücklauf & neue Zeile) | | <String> '\u0001' | 6 | | Das Zeichen mit dem ASCII-Code 1
Public Function print_r( _ ByRef iExpression As Variant, _ Optional ByVal iParams As enuPrintRParams = prParamsDefault, _ Optional ByVal iReturn As enuPrintROutputMethode = prConsole _ ) As String
Diverse Parameter zur Formatierung der Ausgabe.
Auswahl was mir der Analyse geschehen soll. Die Werte lassen sich mit + kombinieren
Falls ein array übergeben wurde, werden die Werte in einem Format angezeigt, das sowohl die Schlüssel als
auch die Elemente darstellt. Für Variablen vom Typ Collection und Dictionary gilt das Gleiche.
Für die restlichen Objekte wird nur der Klassenname angezeigt.
Für alle einfachen Variablen wird der Type und der Inhalt angezeigt
Ruft print_r mit dem Parameter prConsole für eine ganze Liste von Variablen auf
Public Sub print_rm(ParamArray iExpressions() As Variant)
d() ist ein Alais zu print_r() und steht für [D]ebug
Beschreibung der Komponenten, siehe print_r()
Public Function d( _ ByRef iExpression As Variant, _ Optional ByVal iParams As enuPrintRParams = prParamsDefault, _ Optional ByVal iReturn As enuPrintROutputMethode = prConsole _ ) As String
Wie print_r mit den den Parametern prEscapeDoubleQuotes + prStringDoubleQuotes, sowieprReturn + prConsole
\ c steht für [C]ode
Eignet sich, wenn man das Resultat später weiterverwenden will
'/** ' * Wie print_r mit den den Parametern prEscapeDoubleQuotes + prStringDoubleQuotes, sowieprReturn + prConsole ' * c für [C]ode ' * Eignet sich, wenn man das Resultat später weiterverwenden will ' * @example: c "a" & chr(34) & "b" -> "a""b" ' * @param Variant Zu prüfende Variable ' * @return Variant Nichts oder die Analyse als String Public Function c(ByRef iExpression As Variant) As String
Ein Spezialfall zum Testen von RegEx Patterns
Die Patterns sind nach [VBA] RegExp testen im Direktfenster zusammengestellt.
Siehe auch [VBA] cRegExp(), cRx()
'/** ' * Spezialfall zum einfach RegExp testen zu können ' * [D]ebug[R]eg[E]xp ' * @param String Pattern ' * @param String zu untersuchneder Text. Wenn Leer, dann wird der RegExp analysiert ' * @param String Replace. Wenn leer, dann wird der Exeute analysiert, ansonsten das Replaceergebnis ' * @param enuPrintRParams ' * @param enuPrintROutputMethode ' * @return Variant Nichts oder die Analyse ' */ Public Function dRx( _ ByVal iPattern As String, _ Optional ByVal iText As String, _ Optional ByVal iReplace As String, _ Optional ByVal iParams As enuPrintRParams = prParamsDefault, _ Optional ByVal iReturn As enuPrintROutputMethode = prConsole _ )
'/** ' * Eine ganz einfache Form, die den Wert zurückgibt. Ist Praktisch um in einem SQL angewendet zu werden ' * @param Variant Zu prüfende Variable ' * @return Variant die Analyse als String ' */ Public Function analyze(ByRef iExpression As Variant) As String analyze = print_r(iExpression, prParamsDefault, prReturn) End Function
Im Moment kann die Funktion die folgenden Objekte/Variablen auswerten
Bei allen restlichen Objekten wird nur der Klassenname angegeben
Die Folgenden will ich demnächst mal noch implementieren.
Diese Funktion kann problemlos für die Analyse von weiteren Klassenobjekten ausgebaut werden. Dazu einfach eine private Funktion prMyClass schreiben (Beispiele hat es genügend im Code) und entsprechend in der Funktion parseObj() einen Abzweiger zu dieser Funktion hinzufügen.