Attribute VB_Name = "lib_printF" '------------------------------------------------------------------------------- 'File : lib_printF.bas ' Copyright mpl by ERB software ' All rights reserved ' http://wiki.yaslaw.info/doku.php/vba/functions/printf/index 'Environment : VBA XP - 2010 'Version : 2.7.1 'Name : PrintF 'Author : Stefan Erb (ERS) 'History : 04.04.2013 - 1.0.0 - ERS - Creation ' ... ' 03.03.2015 - 2.1.0 - ERS - NZ für Excel eingefügt (Siehe #cont isAccess), eval("#" & value & "#") durch DateValue(value) ersetzt ' 29.01.2016 - 2.2.0 - ERS - Formate für Datum hinzugefügt, < Als Positionszeichen hinzugefügt ' 26.09.2016 - 2.3.0 - ERS - Neues Datumsformat o Und O hinzugeügt. Fehler behoben: Wenn in einem Pattern ein Datum und ein anderes Format war, funktioniert es nicht. ' Neues User-Datumsformat. Bis zu 10 Formate speicherbar und mittels %tu# abrufbar ' 30.10.2018 - 2.5.2 - ERS - Hotfix beim Ersetzen ' 15.11.2018 - 2.5.3 - ERS - Hotfox \t, \n, \r Am Anfang eines Patterns funktioniert jetzt ' 09.05.2019 - 2.6.0 - ERS - Neu q eingeführt. Analog zu s aber als SQL-String. ?sprintf("abc=%1$'#6q", "abc") => abc='###abc' ' 10.05.2019 - 2.6.1 - ERS - Fehler bei formatierung von Integer korrigiert ' 06.02.2020 - 2.7.0 - ERS - \\ am Ende durch \ ersetzen. Jeder \ der als String verwendet werden soll, muss mit einem \ maskiert werden ' Null Parameter werden automatisch mit NZ() in empty umgewandelt '$f' ' ?sprintf("Null-Test: Zahl:'\\%f' Text:'\\% Null-Test: Zahl:'\0' Text:'\' ' 13.02.2020 - 20701 - ERS - strReplace() durch replace() ersetzt. strReplace war von einer früheren Version drin ' '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. ' q - the argument is treated as and presented as a string in sql-form: '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). ' t, T - date/time Prefix for date and time conversion characters. See Date/Time Conversions. ' S - date/time Prefix for date and time conversion characters for MS Access SQL. See Date/Time Conversions. ' ' Datumsformate: ' http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html ' f ' The following conversion characters are used for formatting times: ' H Hour of the day for the 24-hour clock, formatted as two digits with a leading zero as necessary i.e. 00 - 23. ' I Hour for the 12-hour clock, formatted as two digits with a leading zero as necessary, i.e. 01 - 12. ' k Hour of the day for the 24-hour clock, i.e. 0 - 23. ' i Hour for the 12-hour clock, i.e. 1 - 12. ' M Minute within the hour formatted as two digits with a leading zero as necessary, i.e. 00 - 59. ' S Seconds within the minute, formatted as two digits with a leading zero as necessary, i.e. 00 - 60 ("60" is a special value required to support leap seconds). ' The following conversion characters are used for formatting dates: ' B Locale-specific full month name, e.g. "January", "February". ' b Locale-specific abbreviated month name, e.g. "Jan", "Feb". ' A Locale-specific full name of the day of the week, e.g. "Sunday", "Monday" ' a Locale-specific short name of the day of the week, e.g. "Sun", "Mon" ' C Four-digit year divided by 100, formatted as two digits with leading zero as necessary, i.e. 00 - 99 ' Y Year, formatted as at least four digits with leading zeros as necessary, e.g. 0092 equals 92 CE for the Gregorian calendar. ' y Last two digits of the year, formatted with leading zeros as necessary, i.e. 00 - 99. ' j Day of year, formatted as three digits with leading zeros as necessary, e.g. 001 - 366 for the Gregorian calendar. ' m Month, formatted as two digits with leading zeros as necessary, i.e. 01 - 13. ' d Day of month, formatted as two digits with leading zeros as necessary, i.e. 01 - 31 ' e Day of month, formatted as two digits, i.e. 1 - 31. ' The following conversion characters are used for formatting common date/time compositions. ' R Time formatted for the 24-hour clock as "%tH:%tM" ' T Time formatted for the 24-hour clock as "%tH:%tM:%tS". ' r Time formatted for the 12-hour clock as "%tI:%tM:%tS %Tp". The location of the morning or afternoon marker ('%Tp') may be locale-dependent. ' D Date formatted as "#%tm/%td/%ty#". ' t DateTime formatted as "%tm/%td/%ty %tH:%tM:%tS". ' F ISO 8601 complete date formatted as "%tY-%tm-%td". ' f order by Datetie for sortable Date %tY-%t-%td_%H:%M:%S ' L Date in Local "Long Date" Format ' l Date in Local "Short Date" Format ' o order by Date for sortable Date %tY%t%td ' O order by Datetime for sortable Date %tY%t%td_%H%M%S ' u User Format -> %u1. Set the Userformat with the public property printF_UserDefinedDateFormat(1) = "DD.MM" ' ' 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 '------------------------------------------------------------------------------- '------------------------------------------------------------------------------- ' -- ! SETTINGS ! '------------------------------------------------------------------------------- 'In Excel funktionieren Events nicht. Auch der NZ() gibt es dort nicht. 'Darum hier angeben ob es sich um MS Access handelt oder eben nicht. Leider gibts dazu keine Systemvariable #Const isAccess = True Option Explicit '----------------------------------------- '--- 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 '----------------------------------------- 'Die Auflistung der Submatches des geparsten Elements (Pattern D_ELEMENT_PATTERN) Private Enum eSubElements emSubType1 = 0 emCount emSubType2 emType1 emType2 emFormat emIndex emPrefPos End Enum 'Die Auflistung der Submatches des geparsten Formates (Pattern C_FORMAT_PATTERN) Private Enum eSubFormat emWithSign = 0 emFillZeroChar emFillChar emFillLength emInternalDecimalPlaces End Enum 'Die Sammlung der Infos aus dem Pattern und der Typendefinition Private Type tParts paramIndex As Integer types As String subTypeS As String count As Integer 'Ein Counter 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 String 'Typendefinition 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 Private Enum eTypeDef eString eNumber eDateTime eSpezial End Enum Private Enum sysLocale LOCALE_IFIRSTDAYOFWEEK = &H100C 'first day of week specifier 0=Mon, 6=Sun https://msdn.microsoft.com/en-us/library/windows/desktop/dd373771%28v=vs.85%29.aspx LOCALE_IFIRSTWEEKOFYEAR = &H100D 'first week of year specifier 0=direct, 1=Full, 2=4days https://msdn.microsoft.com/en-us/library/windows/desktop/dd373772%28v=vs.85%29.aspx LOCALE_SSHORTDATE = &H1F '31: short date format string End Enum Private Declare Function GetUserDefaultLCID Lib "kernel32" () As Long Private Declare Function GetSystemDefaultLCID Lib "kernel32" () As Long Private Declare Function getLocaleInfo Lib "kernel32" Alias "GetLocaleInfoA" (ByVal Locale As Long, ByVal LCType As Long, ByVal lpLCData As String, ByVal cchData As Long) As Long Private userDateFormat(0 To 9) As String '----------------------------------------- '--- 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 On Error GoTo Err_Handler: Err.Source = "sPrintF" If UBound(iParams) = -1 Then sPrintF = replaceFormat(iFormatString) Else sPrintF = vsPrintF(iFormatString, CVar(iParams)) End If Exit_Handler: On Error Resume Next Exit Function Err_Handler: Call Err.Raise(Err.Number, 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 On Error GoTo Err_Handler: Err.Source = "vsPrintF" Dim params As Variant: params = IIf(IsArray(iParams), iParams, Array(iParams)) 'Formatedefinitionen /t /n etc ausführen 'Der String muss nachher umgedreht werden, da VBA object kein (?>!..) kennt vsPrintF = replaceFormat(iFormatString) 'Wenn keine Parameters angegeben sind, dann beenden If UBound(params) = -1 Then Resume Exit_Handler 'String parsen ' vsPrintF = evalWithRx(rxDTElement, vsPrintF, params) vsPrintF = evalWithRx(rxElement, vsPrintF, params) 'Maskierte \ zurücksetzen vsPrintF = replace(vsPrintF, "\\", "\") Exit_Handler: On Error Resume Next Exit Function Err_Handler: Call Err.Raise(Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Function '----------------------------------------- '--- Public Properties '----------------------------------------- '/** ' * Ein Userdefiniertes Datumsformat ' * @param Integer Nummer des Formates. 0-9 möglich ' * @return String Datumsformat ' */ Public Property Get printF_UserDefinedDateFormat(Optional ByVal iNumber As Integer = 0) As String iNumber = IIf(iNumber > 9, 9, iNumber) iNumber = IIf(iNumber < 0, 0, iNumber) printF_UserDefinedDateFormat = IIf(userDateFormat(iNumber) = Empty, "Short Date", userDateFormat(iNumber)) End Property Public Property Let printF_UserDefinedDateFormat(ByVal iNumber As Integer, ByVal iDateFormat As String) iNumber = IIf(iNumber > 9, 9, iNumber) iNumber = IIf(iNumber < 0, 0, iNumber) userDateFormat(iNumber) = iDateFormat End Property '----------------------------------------- '--- Private Properties/Cache '----------------------------------------- '/** ' * object um die einzelnen Elemente aus dem Pattern zu extrahieren ' * 0: SubType ' * 1: Type ' * 2: Type ' * 3: Count ' * 4: Type ' * 5: Format ' * 6: Item Nummer ' * 7: Verweis auf vorhergehende Position ' * @return object ' */ Private Property Get rxElement() As Object Static rx As Object If rx Is Nothing Then Set rx = cRx("/(?:(?:([HIikLlMSLNpzZsQBbhAaCYyjmdeRTtrDFfPoO])|(\d)([u]))([tTS])|([sqcdfuoxXeEgG]))([\d-\.'#]*)(?:\$(\d+)|(<))?%(?!\\(?!\\))/gm") Set rxElement = rx End Property Private Property Get rxNonElement() As Object Static rx As Object If rx Is Nothing Then Set rx = cRx("/((?:(?:[HIikLlMSLNpzZsQBbhAaCYyjmdeRTtrDFfPoO]|\d[u])[tTS]|[sqcdfuoxXeEgG])[\d-\.'#]*(?:\$\d+|<)?%)(?:\\(?!\\))/gm") Set rxNonElement = rx End Property 'Private Property Get rxElement() As Object ' Static rx As Object ' If rx Is Nothing Then Set rx = cRx("/()([scdfuoxXeEgG])([\d-\.'#]*)(?:\$(\d+)|(<)|)%(?!\\)/gm") ' Set rxElement = rx 'End Property ' 'Private Property Get rxDTElement() As Object ' Static rx As Object ' If rx Is Nothing Then Set rx = cRx("/([HIikLlMSLNpzZsQBbhAaCYyjmdeRTrDFPoO])([tTS])([\d-\.'#]*)(?:\$(\d+)|(<)|)%(?!\\)/gm") ' Set rxDTElement = rx 'End Property '/** ' * object um die Formate zu analysieren ' * emWithSign = 0 ' * emFillZeroChar ' * emFillChar ' * emFillLength ' * emInternalDecimalPlaces ' * @return object ' */ Private Property Get rxFormat() As Object Static rx As Object If rx Is Nothing Then Set rx = cRx("/([-+]?)(?:(0)|'(.)|)([1-9]\d*|)(?:\.([0-9]+)|)/gm") Set rxFormat = rx End Property '/** ' * Typendefinition ' * @return Dictionary ' */ Private Property Get typeDefs() Static pCacheTypeDefs As Object If pCacheTypeDefs Is Nothing Then Set pCacheTypeDefs = CreateObject("scripting.Dictionary") With pCacheTypeDefs .RemoveAll 'Die verschiedenen Typendefinitionen laden .add "s", eString 's - the argument is treated as and presented as a string. .add "q", eString 'q - the argument is treated as and presented as a string. .add "d", eNumber 'd - the argument is treated as an integer, and presented as a (signed) decimal number. .add "f", eNumber 'f - the argument is treated as a float, and presented as a floating-point number (locale aware). .add "u", eNumber 'u - the argument is treated as an integer, and presented as an unsigned decimal number. .add "c", eSpezial 'c - the argument is treated as an integer, and presented as the character with that ASCII value. .add "o", eSpezial 'o - the argument is treated as an integer, and presented as an octal number. .add "x", eSpezial 'x - the argument is treated as an integer and presented as a hexadecimal number (with lowercase letters). .add "X", eSpezial 'X - the argument is treated as an integer and presented as a hexadecimal number (with uppercase letters). .add "e", eSpezial '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 .add "E", eSpezial 'E - like %e but uses uppercase letter (e.g. 1.2E+2). .add "g", eSpezial 'g - shorter of %e and %f. .add "G", eSpezial 'G - shorter of %E and %f. .add "t", eDateTime .add "T", eDateTime .add "S", eDateTime End With End If Set typeDefs = pCacheTypeDefs End Property '/** ' * die object um die Formatierungen (\t, \n etc) zu handeln ' * @return Dictionary(object => ReplaceString) ' */ Public Property Get formatDefs() As Object Static pCacheFormatDefs As Object If pCacheFormatDefs Is Nothing Then Set pCacheFormatDefs = CreateObject("scripting.Dictionary") pCacheFormatDefs.add cRx("/(.*[^\\]|^)\\n(.*)/m"), "$1" & vbLf & "$2" '\n Newline (Line Feed) pCacheFormatDefs.add cRx("/(.*[^\\]|^)\\r(.*)/m"), "$1" & vbCr & "$2" '\r Carriage Return pCacheFormatDefs.add cRx("/(.*[^\\]|^)\\t(.*)/m"), "$1" & vbTab & "$2" '\t Horizontal Tab End If Set formatDefs = pCacheFormatDefs End Property '----------------------------------------- '--- Private Methodes '----------------------------------------- '/** ' * Wertet den String anhand eines RegeExp aus ' * @param RegExp ' * @param String ' * @param Array ' * @return String ' */ Private Function evalWithRx(ByRef irx As Object, ByVal iString As String, ByRef iParams As Variant) As String On Error GoTo Err_Handler: Err.Source = "evalWithRx" evalWithRx = StrReverse(iString) If Not irx.test(evalWithRx) Then GoTo Exit_Handler Dim matches As Object: Set matches = irx.execute(evalWithRx) Dim lEof As Long: lEof = matches.count - 1 Dim parts() As tParts: ReDim parts(lEof) Dim delta As Long Dim i As Integer: For i = lEof To 0 Step -1 Dim position As Integer: position = (lEof - i) Dim ma As Object: Set ma = matches.item(i) With parts(i) 'Werte aus dem Pattern auslesen 'Der Index aus dem Pattern oder die Position im String 'Falls es sich um ein < Handelt, den vorherigen paramIndex verwenden, ausser es ist der Erste If ma.subMatches(emPrefPos) = "<" And i < lEof Then .paramIndex = parts(i + 1).paramIndex delta = delta - 1 'Keine eindeutige Position angegeben ElseIf ma.subMatches(emIndex) = Empty Then .paramIndex = position + delta 'Position aus dem Pattern übernehmen Else .paramIndex = ma.subMatches(emIndex) - 1 End If 'Formatdefinitionen aus dem Pattern .format = StrReverse(NZ(ma.subMatches(emFormat))) .types = ma.subMatches(emType1) & ma.subMatches(emType2) .subTypeS = ma.subMatches(emSubType1) & ma.subMatches(emSubType2) .count = CInt(NZ(ma.subMatches(emCount))) 'Werte der Typendefinitione übernehmen .typeDef = typeDefs(.types) 'Prüfen ob zu dem ermittelten Index auch ein Parameterwert exisitiert If .paramIndex > UBound(iParams) Then Call Err.Raise(ERR_INSUFFICIENT_PARAMS, , "Not enough values für (v)sprintf(). ") 'unformatierten Wert aus den Parametern auslsen .valueV = NZ(iParams(.paramIndex)) 'Parsen und Formatieren. Select Case .typeDef Case eSpezial: Call parseSpezial(parts(i)) Case eDateTime: Call parseDateTime(parts(i)) Case eNumber: Call parseNumber(parts(i)) Case eString: Call parseString(parts(i)) End Select End With Next i Dim rx As Object: Set rx = cRx("/" & irx.pattern & "/m") i = 0 Do While rx.test(evalWithRx) evalWithRx = rx.replace(evalWithRx, StrReverse(parts(i).values)) i = i + 1 Loop 'Maskierung bei maskierten Pattern entfernen evalWithRx = rxNonElement.replace(evalWithRx, "$1") Exit_Handler: On Error Resume Next evalWithRx = StrReverse(evalWithRx) Set ma = Nothing Set matches = Nothing Exit Function Err_Handler: Call Err.Raise(Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Function '/** ' * Parst Formatierungen wie \n, \t etc ' * @param Zu formatierender String ' * @return ' */ Private Function replaceFormat(ByVal iFormatString As String) As String On Error GoTo Err_Handler: Err.Source = "replaceFormat" replaceFormat = iFormatString Dim rx As Variant: For Each rx In formatDefs Do While rx.test(replaceFormat) replaceFormat = rx.replace(replaceFormat, formatDefs(rx)) Loop Next Exit_Handler: On Error Resume Next Exit Function Err_Handler: Call Err.Raise(Err.Number, 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 parseSpezial(ByRef ioParts As tParts) On Error GoTo Err_Handler: Err.Source = "parseSpezial" 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, 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) On Error GoTo Err_Handler: Err.Source = "parseNumber" 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 .values = parseNumberFormat(.format, .valueV) End With Exit_Handler: On Error Resume Next Exit Sub Err_Handler: Call Err.Raise(Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Sub '/** ' * Formatiert eine Nummer ' * @param String ' * @param Variant ' * @return String ' */ Private Function parseNumberFormat(ByVal iFormat As String, ByVal iValueV As Variant) As String On Error GoTo Err_Handler: Err.Source = "parseNumberFormat" parseNumberFormat = CStr(iValueV) If iFormat = vbNullString Then Exit Function 'Formatdetails auselsen Dim fd As tFormat: fd = parseFormat(iFormat, 0) Dim formats As String 'Mit 0 auffüllen formats = "0" If fd.hasFillLength Then formats = str_repeat("0", fd.fillLength) 'Dezmalzeichen If fd.hasInternalDecimalPlaces Then If fd.internalDecimalPlaces > 0 Then formats = formats & "." & str_repeat("0", fd.internalDecimalPlaces) Else If iValueV <> Fix(iValueV) Then formats = formats & "." & str_repeat("#", Len(iValueV - Fix(iValueV))) End If End If 'Vorzeichen If fd.withSgn Then formats = "+" & formats & ";-" & formats parseNumberFormat = format(iValueV, formats) Exit_Handler: On Error Resume Next Exit Function Err_Handler: Call Err.Raise(Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Function '/* ' * Parse a String ' * @param ' */ Private Sub parseString(ByRef ioParts As tParts) On Error GoTo Err_Handler: Err.Source = "parseString" With ioParts 'ERS: 08.12.2014: Keine Ahnung warum ' maskiert werden sollten ' '-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 Dim fd As tFormat: 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 If .types = "q" Then .values = "'" & .values & "'" End With Exit_Handler: On Error Resume Next Exit Sub Err_Handler: Call Err.Raise(Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Sub ' The following conversion characters are used for formatting times: ' 'H' Hour of the day for the 24-hour clock, formatted as two digits with a leading zero as necessary i.e. 00 - 23. ' 'I' Hour for the 12-hour clock, formatted as two digits with a leading zero as necessary, i.e. 01 - 12. ' 'k' Hour of the day for the 24-hour clock, i.e. 0 - 23. ' 'i' Hour for the 12-hour clock, i.e. 1 - 12. ' 'M' Minute within the hour formatted as two digits with a leading zero as necessary, i.e. 00 - 59. ' 'S' Seconds within the minute, formatted as two digits with a leading zero as necessary, i.e. 00 - 60 ("60" is a special value required to support leap seconds). '- 'L' Millisecond within the second formatted as three digits with leading zeros as necessary, i.e. 000 - 999. '- 'N' Nanosecond within the second, formatted as nine digits with leading zeros as necessary, i.e. 000000000 - 999999999. '- 'p' Locale-specific morning or afternoon marker in lower case, e.g."am" or "pm". Use of the conversion prefix 'T' forces this output to upper case. '- 'z' RFC 822 style numeric time zone offset from GMT, e.g. -0800. This value will be adjusted as necessary for Daylight Saving Time. For long, Long, and Date the time zone used is the default time zone for this instance of the Java virtual machine. '- 'Z' A string representing the abbreviation for the time zone. This value will be adjusted as necessary for Daylight Saving Time. For long, Long, and Date the time zone used is the default time zone for this instance of the Java virtual machine. The Formatter's locale will supersede the locale of the argument (if any). '- 's' Seconds since the beginning of the epoch starting at 1 January 1970 00:00:00 UTC, i.e. Long.MIN_VALUE/1000 to Long.MAX_VALUE/1000. '- 'Q' Milliseconds since the beginning of the epoch starting at 1 January 1970 00:00:00 UTC, i.e. Long.MIN_VALUE to Long.MAX_VALUE. ' ' The following conversion characters are used for formatting dates: ' 'B' Locale-specific full month name, e.g. "January", "February". ' 'b' Locale-specific abbreviated month name, e.g. "Jan", "Feb". '- 'h' Same as 'b'. ' 'A' Locale-specific full name of the day of the week, e.g. "Sunday", "Monday" ' 'a' Locale-specific short name of the day of the week, e.g. "Sun", "Mon" ' 'C' Four-digit year divided by 100, formatted as two digits with leading zero as necessary, i.e. 00 - 99 ' 'Y' Year, formatted as at least four digits with leading zeros as necessary, e.g. 0092 equals 92 CE for the Gregorian calendar. ' 'y' Last two digits of the year, formatted with leading zeros as necessary, i.e. 00 - 99. ' 'j' Day of year, formatted as three digits with leading zeros as necessary, e.g. 001 - 366 for the Gregorian calendar. ' 'm' Month, formatted as two digits with leading zeros as necessary, i.e. 01 - 13. ' 'd' Day of month, formatted as two digits with leading zeros as necessary, i.e. 01 - 31 ' 'e' Day of month, formatted as two digits, i.e. 1 - 31. ' ' The following conversion characters are used for formatting common date/time compositions. ' 'R' Time formatted for the 24-hour clock as "%tH:%tM" ' 'T' Time formatted for the 24-hour clock as "%tH:%tM:%tS". ' 'r' Time formatted for the 12-hour clock as "%tI:%tM:%tS %Tp". The location of the morning or afternoon marker ('%Tp') may be locale-dependent. ' 'D' Date formatted as "%tm/%td/%ty". ' 't' DateTime formatted as "%tm/%td/%ty %tH:%tM:%tS". ' 'F' ISO 8601 complete date formatted as "%tY-%tm-%td". ' 'f' order by Datetie for sortable Date %tY-%t-%td_%H:%M:%S ' 'L' Date in Local "Long Date" Format ' 'l' Date in Local "Short Date" Format '- 'c' Date and time formatted as "%ta %tb %td %tT %tZ %tY", e.g. "Sun Jul 20 16:17:00 EDT 1969". ' 'o' order by Date for sortable Date %tY%t%td ' 'O' order by Datetie for sortable Date %tY%t%td_%H%M%S ' 'u' User Format -> %u0 '/* ' * Parse a String ' * @param ' */ Private Sub parseDateTime(ByRef ioParts As tParts) On Error GoTo Err_Handler: Err.Source = "parseDateTime" Select Case ioParts.subTypeS Case "H": ioParts.values = format(ioParts.valueV, "HH") Case "I": ioParts.values = format(ioParts.valueV, "HHAMPM") Case "k": ioParts.values = format(ioParts.valueV, "H") Case "i": ioParts.values = format(ioParts.valueV, "HAMPM") Case "M": ioParts.values = format(ioParts.valueV, "NN") Case "S": ioParts.values = format(ioParts.valueV, "SS") Case "P": ioParts.values = format(ioParts.valueV, "AM/PM") Case "B": ioParts.values = format(ioParts.valueV, "MMMM") Case "b": ioParts.values = format(ioParts.valueV, "MMM") Case "A": ioParts.values = format(ioParts.valueV, "DDDD") Case "a": ioParts.values = format(ioParts.valueV, "DDD") Case "C": ioParts.values = CStr(year(ioParts.valueV) \ 100) Case "Y": ioParts.values = format(ioParts.valueV, "YYYY") Case "Y": ioParts.values = format(ioParts.valueV, "YY") Case "j": ioParts.values = format(format(ioParts.valueV, "Y"), "000") Case "m": ioParts.values = format(ioParts.valueV, "MM") Case "d": ioParts.values = format(ioParts.valueV, "DD") Case "e": ioParts.values = format(ioParts.valueV, "D") Case "R": ioParts.values = format(ioParts.valueV, "HH:NN") Case "T": ioParts.values = format(ioParts.valueV, "HH:NN:SS") Case "r": ioParts.values = format(ioParts.valueV, "HH:NN:SS AM/PM") Case "D": ioParts.values = format(ioParts.valueV, "MM\/DD\/YYYY") Case "t": ioParts.values = format(ioParts.valueV, "MM\/DD\/YYYY HH:NN:SS") Case "F": ioParts.values = format(ioParts.valueV, "YYYY-MM-DD") Case "f": ioParts.values = format(ioParts.valueV, "YYYY-MM-DD HH:NN:SS") Case "L": ioParts.values = format(ioParts.valueV, "Long Date") Case "l": ioParts.values = format(ioParts.valueV, "Short Date") Case "O": ioParts.values = format(ioParts.valueV, "YYYYMMDD_HHNNSS") Case "o": ioParts.values = format(ioParts.valueV, "YYYYMMDD") Case "u": ioParts.values = format(ioParts.valueV, printF_UserDefinedDateFormat) End Select 'Spezialfall SQL If ioParts.types = "S" Then Select Case ioParts.subTypeS Case "R", "T", "t", "D", "F", "f", "r": ioParts.values = "#" & ioParts.values & "#" Case "B", "b", "A", "a": ioParts.values = "'" & ioParts.values & "'" End Select End If Exit_Handler: On Error Resume Next Exit Sub Err_Handler: Call Err.Raise(Err.Number, 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 On Error GoTo Err_Handler: Err.Source = "parseFormat" If Not rxFormat.test(iFormat) Then Call Err.Raise(ERR_INVALID_FORMAT, , "Wrong Format '" & iFormat & "'") 'Format parsen Dim matches As Object: Set matches = rxFormat.execute(iFormat) Dim subMatches As Object: Set subMatches = matches.item(0).subMatches 'Die einzelnen Teile auslesen und auswerten With parseFormat 'Füllzeichen ermitteln .fillChar = subMatches(emFillZeroChar) If .fillChar = Empty Then .fillChar = subMatches(emFillChar) If .fillChar = Empty Then .fillChar = iDefaultFillChar 'Füllänge .hasFillLength = subMatches(emFillLength) <> Empty If .hasFillLength Then .fillLength = CInt(subMatches(emFillLength)) 'Nachkommastellen .hasInternalDecimalPlaces = subMatches(emInternalDecimalPlaces) <> Empty If .hasInternalDecimalPlaces Then .internalDecimalPlaces = CInt(subMatches(emInternalDecimalPlaces)) 'Vorzeichen (bei String, Links-Rechts Bündig) .withSgn = subMatches(emWithSign) <> Empty 'Das Vorzeichen selber wird so nur beim String verwendet .sgn = IIf(.withSgn, subMatches(emWithSign), "+") End With Exit_Handler: On Error Resume Next Set subMatches = Nothing Set matches = Nothing Exit Function Err_Handler: Call Err.Raise(Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext) Resume Exit_Handler Resume End Function '----------------------------------------- '--- Verwendete allgemeine Funktionen '----------------------------------------- '----------------------------------------- ' cDict V 2.1.0 '----------------------------------------- '/** ' * Wandelt verschiedene Formate in ein Dictionary um ' * http://wiki.yaslaw.info/dokuwiki/doku.php/vba/cast/cdict ' * @param ParamArray ' * @return Dictionary ' */ Private Function cDict(ParamArray iItems() As Variant) As Object Set cDict = CreateObject("scripting.Dictionary") Dim items() As Variant: items = CVar(iItems) Dim i As Integer, key As Variant, value As Variant Dim isList As Boolean If UBound(items) = -1 Then Exit Function 'Prüfen ob 2 Parametetrs übergeben wurden If UBound(items) = 1 Then 'Beide Parametrer sind Arrays -> Combine: Erster Array Keys, 2ter Values If IsArray(items(0)) And IsArray(items(1)) Then key = items(0): value = items(1) Dim delta As Long: delta = LBound(key) - LBound(value) ReDim Preserve value(LBound(value) To UBound(key) + delta) For i = LBound(key) To UBound(key) If Not cDict.exists(key(i)) Then cDict.add key(i), value(i + delta) Next i Exit Function End If End If 'Alle Items durchackern For i = 0 To UBound(items) Dim item As Variant: ref item, items(i) 'Dictionary If Not isList And TypeName(item) = "Dictionary" Then For Each key In items(i).keys If Not cDict.exists(key) Then cDict.add key, item.item(key) Next key 'einsamer Array ElseIf Not isList And IsArray(item) Then For key = LBound(item) To UBound(item) If Not cDict.exists(key) Then cDict.add key, item(key) Next key 'SetString ElseIf Not isList And Not IsArray(item) And Not IsObject(item) Then If rxSetString.test(StrReverse(item)) Then Dim mc As Object: Set mc = rxSetString.execute(StrReverse(item)) Dim k As Integer: For k = mc.count - 1 To 0 Step -1 Dim m As Object: Set m = mc(k) key = StrReverse(firstValue(m.subMatches(6), m.subMatches(5), m.subMatches(3))) value = StrReverse(firstValue(m.subMatches(2), m.subMatches(1))) Select Case m.subMatches(0) Case "#": value = dateValue(value) 'eval("#" & value & "#") Case Empty: value = CDec(value) Case Else: value = cRx("/\\(['""])/g").replace(value, "$1") End Select If Not cDict.exists(key) Then cDict.add key, value Next k Else GoTo default 'Zuerst wollte ich diesen GoTo nicht haben. Aber StrReverse() wirft ein Fehler wenn ein Objekt übergeben wird. Darum konnte der Test nicht im ElseIf durchgeführt werden. End If 'Alles andere geht in ein WertePaar. ElseIf i = 0 Or isList Then default: If i Mod 2 = 0 Then key = item Else If Not cDict.exists(key) Then cDict.add key, item End If isList = True End If Next i 'Falls es sich um eine nicht abgeschlossene Liste handelt If isList And i Mod 2 <> 0 Then If Not cDict.exists(key) Then cDict.add key, Empty End If End Function '------------------------------------------------------------------------------- '-- Private methodes / properties for cDict() '------------------------------------------------------------------------------- '/** ' * Gibt den ersten Wert zurück, der nicht Nothing, Empty oder Null ist ' * @param ParamArray ' * @return Variant ' */ Private Function firstValue(ParamArray items() As Variant) As Variant For Each firstValue In items If IsObject(firstValue) Then If Not firstValue Is Nothing Then Exit For Else If Not IsNull(firstValue) And Not firstValue = Empty Then Exit For End If Next End Function '/** ' * Gibt eine Refernez auf den Wert zurück ' * @param Variant Variable, di abgefüllt werden soll ' * @param Variant Value ' */ Private Sub ref(ByRef oItem As Variant, Optional ByRef iItem As Variant) If IsMissing(iItem) Then oItem = Empty ElseIf IsObject(iItem) Then Set oItem = iItem Else oItem = iItem End If End Sub '/** ' * Handelt den object-Cache um ein Set-String zu zerlegen ' * @return object ' */ Private Property Get rxSetString() As Object Static rxCachedSetString As Object If rxCachedSetString Is Nothing Then Set rxCachedSetString = CreateObject("VBScript.RegExp") rxCachedSetString.Global = True rxCachedSetString.pattern = "(?:(['""#])(?!\\)(.+?)\1(?!\\)|([0-9\.]+?))\s*(?:>=|[:=])\s*(?:\]([^\[]+)\[|(['""])(?!\\)(.+?)\5(?!\\)|(\w+))" End If Set rxSetString = rxCachedSetString End Property '------------------------------------------------------------------------------- '-- cRx() '------------------------------------------------------------------------------- '/** ' * Dies ist die Minimalversion von cRegExp ' * http://wiki.yaslaw.info/dokuwiki/doku.php/vba/cast/cregexp#abgespeckte_version ' * mögliche Delemiter: @&!/~#=\| ' * mögliche Modifiers: g (Global), i (IgnoreCode, m (Mulitline) ' * ' * @example myRx = cRx("/([a]{2,})/i") 'Finde alle folgen von a. Flag:IgnoreCase ' * @version 2.1.0 (01.12.2014) ' * @param String Pattern mit Delimiter und Modifier analog zu PHP ' * @return Object object-Object ' */ Private Function cRx(ByVal iPattern As String) As Object Static rxP As Object: Set cRx = CreateObject("VBScript.RegExp") If rxP Is Nothing Then: Set rxP = CreateObject("VBScript.RegExp"): rxP.pattern = "^([@&!/~#=\|])?(.*)\1(?:([Ii])|([Gg])|([Mm]))*$" Dim sm As Object: Set sm = rxP.execute(iPattern)(0).subMatches cRx.pattern = sm(1): cRx.IgnoreCase = Not isEmpty(sm(2)): cRx.Global = Not isEmpty(sm(3)): cRx.Multiline = Not isEmpty(sm(4)) End Function '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 #If Not isAccess Then '/** ' * Wandelt NULL in EMpty oder einen Defaultwert ' * @param Variant ' * @param Variant ' * @return Variant ' */ Private Function NZ(ByRef iValue As Variant, Optional ByRef iDefault As Variant = Empty) As Variant If IsNull(iValue) Then NZ = iDefault Else NZ = iValue End If End Function #End If