Attribute VB_Name = "lib_printF" '------------------------------------------------------------------------------- 'File : lib_printF.bas ' Copyright mpl by ERB software ' All rights reserved ' http://wiki.yaslaw.info/wikka/vbaPrintF 'Environment : VBA XP - 2010 'Version : 1.6.0 'Name : PrintF 'Author : Stefan Erb (ERS) 'History : 04.04.2013 - 1.0.0 - ERS - Creation ' 24.04.2013 - 1.1.0 - ERS - Befehle wie \n, \r und \t hinzugefügt ' 27.05.2013 - 1.2.0 - ERS - Bei String eine Markierung der ' innerhalb des Strings hinzugefügt ' 05.06.2013 - 1.3.0 - ERS - bei %d die Vorformatierung von cInt auf cLng gewechselt ' 25.06.2013 - 1.4.0 - ERS - Wenn ien Tag mit \ amskiert ist, den \ am Schluss entfernen ' 05.07.2013 - 1.4.1 - ERS - Korrektur eines Fehlers der mit V1.4 reingerutscht ist. ' 14.11.2013 - 1.5.0 - ERS - Auf Late Binding umgestellt ' 18.11.2013 - 1.5.1 - ERS - Fehler aus 1.5.0 ausgebügelt ' 15.07.2013 - 1.6.0 - ERS - eval() aus dem Code verbannt. ' 'Description : Aus der PHP-Doku: ' - An optional sign specifier that forces a sign (- or +) to be used on a number. By default, only the - sign is used on a number if it's negative. ' This specifier forces positive numbers to have the + sign attached as well, and was added in PHP 4.3.0. ' - An optional padding specifier that says what character will be used for padding the results to the right string size. This may be a space character or a 0 ' (zero character). The default is to pad with spaces. An alternate padding character can be specified by prefixing it with a single quote (') ' - An optional alignment specifier that says if the result should be left-justified or right-justified. The default is right-justified; a - character ' here will make it left-justified. ' - An optional number, a width specifier that says how many characters (minimum) this conversion should result in. ' - An optional precision specifier in the form of a period (`.') followed by an optional decimal digit string that says how many decimal digits ' should be displayed for floating-point numbers. When using this specifier on a string, it acts as a cutoff point, setting a maximum character ' limit to the string. ' ' A type specifier that says what type the argument data should be treated as. Possible types: ' % - a literal percent character. No argument is required. ' //b - the argument is treated as an integer, and presented as a binary number. ' c - the argument is treated as an integer, and presented as the character with that ASCII value. ' d - the argument is treated as an integer, and presented as a (signed) decimal number. ' e - the argument is treated as scientific notation (e.g. 1.2e+2). The precision specifier stands for the number of digits after the decimal point ' E - like %e but uses uppercase letter (e.g. 1.2E+2). ' u - the argument is treated as an integer, and presented as an unsigned decimal number. ' f - the argument is treated as a float, and presented as a floating-point number (locale aware). ' //F - the argument is treated as a float, and presented as a floating-point number (non-locale aware). Available since PHP 4.3.10 and PHP 5.0.3. ' g - shorter of %e and %f. ' G - shorter of %E and %f. ' o - the argument is treated as an integer, and presented as an octal number. ' s - the argument is treated as and presented as a string. ' x - the argument is treated as an integer and presented as a hexadecimal number (with lowercase letters). ' X - the argument is treated as an integer and presented as a hexadecimal number (with uppercase letters). ' ' Zusätzlich noch die Formatierungen analog http://www.freevbcode.com/ShowCode.asp?ID=5014 ' Um einen davon nicht zu parsen einfach mit einem \ marieren: '\\t' wird nicht zu Tabulator sondern zu '\t' geparst ' \n Newline (Line Feed) ' \r Carriage Return ' \t Horizontal Tab '------------------------------------------------------------------------------- Option Explicit Option Compare Database '----------------------------------------- '--- Public members '----------------------------------------- 'Error: Zu wenige Werte wurden übergeben Public Const ERR_INSUFFICIENT_PARAMS = vbObjectError - 10 'Es wird keine Zahl geliefert wo eine erwartet wird Public Const ERR_NOT_NUMBER = vbObjectError - 11 'Das Format ist ungültig Public Const ERR_INVALID_FORMAT = vbObjectError - 12 '----------------------------------------- '--- Private members '----------------------------------------- 'Pattern um den String zu zerlegen Private Const D_ELEMENT_PATTERN = "%(?:(\d+)\$|)([^scdfuoxXeEgG]*)([scdfuoxXeEgG])" Private Const C_FORMAT_PATTERN = "([-+]?)(?:(0)|'(.)|)([1-9]\d*|)(?:\.([0-9]+)|)" 'Die Auflistung der Submatches des geparsten Elements (Pattern D_ELEMENT_PATTERN) Private Enum eSubElements emIndex = 0 emFormat emType End Enum 'Die Auflistung der Submatches des geparsten Formates (Pattern C_FORMAT_PATTERN) Private Enum eSubFormat emWithSign = 0 emFillZeroChar emFillChar emFillLength emInternalDecimalPlaces End Enum 'Definition ob die Auswertung des Formaters als String oder als Zahl passieren muss Private Enum eTypeDef emString 'Die zusätzlichen Formatierungen werden für String angewendet emNumber 'Die zusätzlichen Formatierungen werden für Zahlen angewendet emNonFormat 'Die zusätzlichen Formatierungen werden ignoriert End Enum 'Die sammling der Infos aus dem Pattern und der Typendefinition Private Type tParts paramIndex As Integer typeS As String valueV As Variant 'Value als Variant, Also in Der Rohform aus dem Input values As String 'Formatierter Value als String format As String 'Das Format typeDef As eTypeDef 'Typendefinition preFormat As String 'Vorformatierung preFormatFunc As String End Type 'Infos aus dem Format Private Type tFormat withSgn As Boolean 'Flag: das Vorzeigen immer angezeigt werden muss sgn As String 'Vorzeichen hasInternalDecimalPlaces As Boolean 'Flag: hat Fixe nachkommastellen internalDecimalPlaces As Integer 'Fixe Nachkommastellen fillChar As String 'Füllzeichen hasFillLength As Boolean 'Flag: Hat fixe Vorkommalänge fillLength As Integer 'Fixe Vorkommalänge End Type 'Chachen der Definitionen und der RegExp, damit sie nicht jedesmal neu erstellt werden müssen. 'Der RegExp muss so nicht mehr jedesmal den Pattern parsen. Private cacheTypeDefs As Object Private cacheFormatDefs As Object Private cacheRegexElement As Object Private cacheRegexFormat As Object Private cacheRegexFormats As collection '----------------------------------------- '--- Definitions '----------------------------------------- '/* ' * Die Typendefinitionen erstellen ' */ Private Sub initDeffinitions() 'Sicherstellen dass die cacheTypeDefs nur geladen werden wenn sie noch nicht exisiteren If Not cacheTypeDefs Is Nothing And Not cacheFormatDefs Is Nothing Then Exit Sub 'Typen nur neu definieren wenn sie noch nicht im Cache sind If cacheTypeDefs Is Nothing Then Set cacheTypeDefs = New dictionary Call cacheTypeDefs.RemoveAll 'Die verschiedenen Typendefinitionen laden ' * addTypeDef ' * Erstellt eine Typendefinition und fügt sei dem Container hinzu ' * @param iType Format-Buchstaben ' * @param iTypeDef Definition gem. Enum eTypeDef ' * @param iPreFormat Das Standartformat 's - the argument is treated as and presented as a string. Call addTypeDef("s", emString) 'd - the argument is treated as an integer, and presented as a (signed) decimal number. Call addTypeDef("d", emNumber) 'f - the argument is treated as a float, and presented as a floating-point number (locale aware). Call addTypeDef("f", emNumber) 'u - the argument is treated as an integer, and presented as an unsigned decimal number. Call addTypeDef("u", emNumber) 'c - the argument is treated as an integer, and presented as the character with that ASCII value. Call addTypeDef("c", emNonFormat) 'o - the argument is treated as an integer, and presented as an octal number. Call addTypeDef("o", emNonFormat) 'x - the argument is treated as an integer and presented as a hexadecimal number (with lowercase letters). Call addTypeDef("x", emNonFormat) 'X - the argument is treated as an integer and presented as a hexadecimal number (with uppercase letters). Call addTypeDef("X", emNonFormat) 'e - the argument is treated as scientific notation (e.g. 1.2e+2). The precision specifier stands for the number of digits after the decimal point Call addTypeDef("e", emNonFormat) 'E - like %e but uses uppercase letter (e.g. 1.2E+2). Call addTypeDef("E", emNonFormat) 'g - shorter of %e and %f. Call addTypeDef("g", emNonFormat) 'G - shorter of %E and %f. Call addTypeDef("G", emNonFormat) End If 'Unabhängige Formatierungsdefinition erstellen sofern sie nicht im Cache sind ' * addFormatDef ' * füt eine Formatdefinition dem Container hinzufügen ' * @param iPattern Formatpattern ' * @param iReplace Wert zum ersetzen If cacheFormatDefs Is Nothing Then Set cacheRegexFormats = New collection Set cacheFormatDefs = CreateObject("scripting.Dictionary") Call cacheFormatDefs.RemoveAll ' \n Newline (Line Feed) Call addFormatDef("\n", vbLf) ' \r Carriage Return Call addFormatDef("\r", vbCr) ' \t Horizontal Tab Call addFormatDef("\t", vbTab) End If End Sub '----------------------------------------- '--- Public Methodes '----------------------------------------- '/** ' * Parse einen String mit Werten ' * @param Zu formatierender String ' * @param * die dazugehörigen Werte ' * @return ' */ Public Function sPrintF(ByVal iFormatString As String, ParamArray iParams() As Variant) As String Dim i As Integer Dim params() As Variant On Error GoTo Err_Handler Call initDeffinitions If UBound(iParams) = -1 Then sPrintF = replaceFormat(iFormatString) GoTo Exit_Handler End If 'ParamArray in einen Array kopieren ReDim params(UBound(iParams)) For i = 0 To UBound(iParams) params(i) = iParams(i) Next i 'mit vsPrintF() parsen sPrintF = vsPrintF(iFormatString, params) Exit_Handler: On Error Resume Next Exit Function Err_Handler: Call Err.Raise(Err.Number, "sPrintF." & Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Function '/** ' * Parse einen String mit den Werten als Array. ' * @param Zu formatierender String ' * @param Array die dazugehörigen Werte ' * @return ' */ Public Function vsPrintF(ByVal iFormatString As String, ByRef iParams() As Variant) As String Dim i As Integer Dim matches As Object Dim ma As Object Dim pParts As tParts Dim active As Boolean Dim pattern As String On Error GoTo Err_Handler Call initDeffinitions 'Formatedefinitionen /t /n etc ausführen vsPrintF = replaceFormat(iFormatString) pattern = vsPrintF 'Wenn keine Parameters angegeben sind, dann beenden If UBound(iParams) = -1 Then GoTo Exit_Handler 'regExpPattern initialisieren falls nicht im Cache geladen If cacheRegexElement Is Nothing Then Set cacheRegexElement = CreateObject("VBScript.RegExp") cacheRegexElement.pattern = D_ELEMENT_PATTERN cacheRegexElement.Multiline = True cacheRegexElement.Global = True End If 'String parsen Set matches = cacheRegexElement.Execute(vsPrintF) 'Prüfen ob Formatdefinitionen im String gefunden wurden If matches.count = 0 Then GoTo Exit_Handler For i = 0 To matches.count - 1 Set ma = matches.item(i) 'Prüfen ob $ nicht auskomentiert ist. Würde ich gerne mit RegExp machen, dieser versteht aber '(? UBound(iParams) Then Call Err.Raise(ERR_INSUFFICIENT_PARAMS, , "Not enough values für (v)sprintf(). ") 'unformatierten Wert aus den Parametern auslsen .valueV = iParams(.paramIndex) 'Parsen und Formatieren. Select Case .typeDef Case emNonFormat: Call parseNonFomrat(pParts) Case emNumber: Call parseNumber(pParts) Case emString: Call parseString(pParts) End Select End With 'Nur das erste Vorkomnis des patterns ersetzen vsPrintF = Replace(vsPrintF, matches.item(i).value, pParts.values, , 1) Else 'Den \ der den Tag maskiert entfernen vsPrintF = Replace(vsPrintF, "\" & ma.value, ma.value, , 1) End If Next i Exit_Handler: On Error Resume Next Set ma = Nothing Set matches = Nothing Exit Function Err_Handler: Call Err.Raise(Err.Number, "vsPrintF." & Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Function '/** ' * Entfernen der Definitionen ' * Wird eigentlich nur zu entwicklungszwecken verwendet. Die aktuelle Definitionen werden entfernt ' * damit sie bei der nächsten Anwendung neu geladen werden ' */ Public Sub clearPrintFCache() Dim i Set cacheTypeDefs = Nothing Set cacheFormatDefs = Nothing Set cacheRegexElement = Nothing Set cacheRegexFormat = Nothing 'Collection sauber aufräumen For i = 1 To cacheRegexFormats.count Call cacheRegexFormats.remove(1) Next i Set cacheRegexFormats = Nothing End Sub '----------------------------------------- '--- Private Methodes '----------------------------------------- '/** ' * addTypeDef ' * Erstellt eine Typendefinition und fügt sei dem Container hinzu ' * @param iType Format-Buchstaben ' * @param iTypeDef Definition gem. Enum eTypeDef ' * @param iPreFormat Das Standartformat ' */ Private Sub addTypeDef( _ ByVal iType As String, _ ByVal iTypeDef As eTypeDef, _ Optional ByVal iPreFormat As String, _ Optional ByVal iPreFormatFunc As String _ ) Dim typeDef As Object: Set typeDef = CreateObject("scripting.Dictionary") Call typeDef.add("type", iType) Call typeDef.add("typeDef", iTypeDef) Call typeDef.add("preFormat", iPreFormat) Call typeDef.add("preFormatFunc", iPreFormatFunc) Call cacheTypeDefs.add(iType, typeDef) End Sub '/** ' * addFormatDef ' * füt eine Formatdefinition dem Container hinzufügen ' * @param iPattern Formatpattern ' * @param iReplace Wert zum ersetzen ' */ Private Sub addFormatDef( _ ByVal iPattern As String, _ ByVal iReplace As String _ ) Dim formatDef As Object: Set formatDef = CreateObject("scripting.Dictionary") Dim regex As Object: Set regex = CreateObject("VBScript.RegExp") 'Format speichern Call formatDef.add("pattern", iPattern) Call formatDef.add("replace", "$1" & iReplace & "$2") Call cacheFormatDefs.add(iPattern, formatDef) 'RegExp erstellen und cachen regex.Global = False regex.Multiline = True regex.IgnoreCase = False regex.pattern = "(.*[^\\])\" & iPattern & "(.*)" Call cacheRegexFormats.add(regex, iPattern) End Sub '/** ' * Parst Formatierungen wei \n, \t etc ' * @param Zu formatierender String ' * @return ' */ Private Function replaceFormat(ByVal iFormatString As String) As String Dim txt As String Dim i As Long Dim key As Variant On Error GoTo Err_Handler txt = iFormatString i = 0 For Each key In cacheFormatDefs Do While cacheRegexFormats.item(i + 1).Test(txt) txt = cacheRegexFormats(i + 1).Replace(txt, cacheFormatDefs(key).item("replace")) Loop i = i + 1 Next replaceFormat = Replace(txt, "\\", "\") Exit_Handler: On Error Resume Next Exit Function Err_Handler: Call Err.Raise(Err.Number, "vsPrintF." & Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Function '/* ' * Nur das preFormat wird ausgeführt. Der format-Part des Patterns wird ignoriert ' * @param ' */ Private Sub parseNonFomrat(ByRef ioParts As tParts) On Error GoTo Err_Handler With ioParts Select Case .typeS Case "c": .valueV = Chr(CInt(.valueV)) Case "o": .valueV = Oct(.valueV) Case "x": .valueV = LCase(Hex(.valueV)) Case "X": .valueV = UCase(Hex(.valueV)) Case "e": .valueV = LCase(format(.valueV, "Scientific")) Case "E": .valueV = UCase(format(.valueV, "Scientific")) Case "g": .valueV = IIf(Len(".ValueV") <= Len(format(.valueV, "Scientific")), CDbl(.valueV), LCase(format(.valueV, "Scientific"))) Case "G": .valueV = IIf(Len(".ValueV") <= Len(format(.valueV, "Scientific")), CDbl(.valueV), UCase(format(.valueV, "Scientific"))) End Select End With Exit_Handler: On Error Resume Next Exit Sub Err_Handler: Call Err.Raise(Err.Number, "parseNonFomrat." & Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Sub '/* ' * Parse a Number ' * @param ' */ Private Sub parseNumber(ByRef ioParts As tParts) Dim formatS As String Dim fd As tFormat On Error GoTo Err_Handler With ioParts If Not IsNumeric(.valueV) Then Call Err.Raise(ERR_NOT_NUMBER, , "Parameter is not a Number") Select Case .typeS Case "d": .valueV = CLng(.valueV) Case "f": .valueV = CDbl(.valueV) Case "u": .valueV = Abs(CInt(.valueV)) End Select 'Wenn kein Format definiert ist, einfach die Variable un .values = CStr(.valueV) If Not .format = vbNullString Then 'Formatdetails auselsen fd = parseFormat(.format, 0) If fd.hasFillLength Then formatS = str_repeat("0", fd.fillLength) If fd.hasInternalDecimalPlaces Then If fd.internalDecimalPlaces > 0 Then formatS = formatS & "." & str_repeat("0", fd.internalDecimalPlaces) Else If .valueV <> Fix(.valueV) Then formatS = formatS & "." & str_repeat("#", Len(.valueV - Fix(.valueV))) End If End If If fd.withSgn Then formatS = "+" & formatS & ";-" & formatS .values = format(.valueV, formatS) End If End With Exit_Handler: On Error Resume Next Exit Sub Err_Handler: Call Err.Raise(Err.Number, "parseNumber." & Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Sub '/* ' * Parse a String ' * @param ' */ Private Sub parseString(ByRef ioParts As tParts) Dim fd As tFormat On Error GoTo Err_Handler With ioParts ' '-Zeichen im Text markieren .valueV = Replace(CStr(.valueV), "'", "''") 'Wenn kein Format definiert ist, einfach die Variable un .values = CStr(.valueV) If Not .format = vbNullString Then 'Formatdetails auselsen. Die Items von fd haben die Namen von der Nummerformatierung fd = parseFormat(.format, " ") 'Wenn ien Vorzeichen gesetzt ist, das ganez Rechtbündig nehmen 'An optional alignment specifier that says if the result should be left-justified or right-justified. The default is right-justified; a - character 'here will make it left-justified. If fd.sgn = "-" Then If fd.hasFillLength Then .values = rPad(.values, fd.fillChar, fd.fillLength) End If If fd.hasInternalDecimalPlaces Then .values = Right(.values, fd.internalDecimalPlaces) End If Else If fd.hasFillLength Then .values = Lpad(.values, fd.fillChar, fd.fillLength) End If If fd.hasInternalDecimalPlaces Then .values = Left(.values, fd.internalDecimalPlaces) End If End If End If End With Exit_Handler: On Error Resume Next Exit Sub Err_Handler: Call Err.Raise(Err.Number, "parseString." & Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Sub '/** ' * Parst den Format-Teil des Patterns ' * @param Formatteil ' * @param Standart-Füllzeichen ' * @return ' */ Private Function parseFormat(ByVal iFormat As String, ByVal iDefaultFillChar As Variant) As tFormat Dim matches As Object Dim m As Object On Error GoTo Err_Handler 'Format parsen If cacheRegexFormat Is Nothing Then Set cacheRegexFormat = New regExp cacheRegexFormat.pattern = C_FORMAT_PATTERN cacheRegexFormat.Multiline = False cacheRegexFormat.Global = True End If Set matches = cacheRegexFormat.Execute(iFormat) If matches.count = 0 Then Call Err.Raise(ERR_INVALID_FORMAT, , "Wrong Format '" & iFormat & "'") Set m = matches.item(0).SubMatches 'Die einzelnen Teile auslesen und auswerten With parseFormat 'Füllzeichen ermitteln .fillChar = m(emFillZeroChar) If .fillChar = Empty Then .fillChar = m(emFillChar) If .fillChar = Empty Then .fillChar = iDefaultFillChar 'Füllänge .hasFillLength = m(emFillLength) <> Empty If .hasFillLength Then .fillLength = CInt(m(emFillLength)) 'Nachkommastellen .hasInternalDecimalPlaces = m(emInternalDecimalPlaces) <> Empty If .hasInternalDecimalPlaces Then .internalDecimalPlaces = CInt(m(emInternalDecimalPlaces)) 'Vorzeichen (bei String, Links-Rechts Bündig) .withSgn = m(emWithSign) <> Empty 'Das Vorzeichen selber wird so nur beim String verwendet .sgn = IIf(.withSgn, m(emWithSign), "+") End With Exit_Handler: On Error Resume Next Set m = Nothing Set matches = Nothing Exit Function Err_Handler: Call Err.Raise(Err.Number, "parseFormat." & Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Function '----------------------------------------- '--- Verwendete allgemeine Funktionen '----------------------------------------- 'http://support.microsoft.com/kb/96458 '===================================================================== 'The following function will left pad a string with a specified 'character. It accepts a base string which is to be left padded with 'characters, a character to be used as the pad character, and a 'length which specifies the total length of the padded result. '===================================================================== Private Function Lpad(ByVal MyValue$, ByVal MyPadCharacter$, ByVal MyPaddedLength%) Dim x As Integer Dim PadLength As Integer PadLength = MyPaddedLength - Len(MyValue) Dim PadString As String For x = 1 To PadLength PadString = PadString & MyPadCharacter Next Lpad = PadString + MyValue End Function '===================================================================== 'The following function will right pad a string with a specified 'character. It accepts a base string which is to be right padded with 'characters, a character to be used as the pad character, and a 'length which specifies the total length of the padded result. '===================================================================== Private Function rPad(ByVal MyValue$, ByVal MyPadCharacter$, ByVal MyPaddedLength%) Dim x As Integer Dim PadLength As Integer PadLength = MyPaddedLength - Len(MyValue) Dim PadString As String For x = 1 To PadLength PadString = MyPadCharacter & PadString Next rPad = MyValue + PadString End Function '/** ' * Wiederholt einen String ' * http://wiki.yaslaw.info/wikka/vbvbaStringFunctions ' * @param String zu wiederholender String ' * @param Integer Anzahl weiderholungen ' * @return String Resultat ' */ Private Function str_repeat(ByVal str As String, ByVal multiplier As Integer) As String Dim i As Integer For i = 1 To multiplier str_repeat = str_repeat & str Next i End Function