Attribute VB_Name = "udf_rndString" '------------------------------------------------------------------------------- 'File : udf_rndString.bas ' All rights reserved ' http://wiki.yaslaw.info/dokuwiki/doku.php/vba 'Environment : VBA 2010 + 'Version : 1.0.1 'Name : rndString 'Author : Stefan Erb (ERS) 'History : 04.05.2017 - ERS - Creation ' 04.09.2019 - ERS - Array() durch emptyArrayVariant() ersetzt '------------------------------------------------------------------------------- Option Explicit 'Set: [...] Einfach bunt gemischt ' (...) Von jedem muss etwas vorhanden sein 'Repeat: {x} Anzahl x Zeichen ' {y, x} Mindestens y Zeichen, maximal x Zeichen ' \ Escapen ' ^ Exclude '---------------------------------------------L---------------------------------- ' Private Members '------------------------------------------------------------------------------- 'Im Moment umgesetzte Patterns. Reiehnfolge muss mit getValiableChars() übereinstimmen Private Const C_AVIABLE_PATTERN_CHARS = "dluLaUApbsS" Private Declare Function emptyArrayVariant Lib "oleaut32" Alias "SafeArrayCreateVector" (Optional ByVal vt As VbVarType = vbVariant, Optional ByVal lLow As Long = 0, Optional ByVal lCount As Long = 0) As Variant() 'Analog zu KeePass Private Const C_DIGIT = "0123456789" 'd Digit Private Const C_LOWER_LETTER = "abcdefghijklmnopqrstuvwxyz" 'l Lower-Case Letter Private Const C_UPPER_LETTER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 'u Upper-Case Letter Private Const C_MIXED_LETTER = C_LOWER_LETTER & C_UPPER_LETTER 'L Mixed-Case Letter Private Const C_LOWER_ALPAH = C_LOWER_LETTER & C_DIGIT 'a Lower-Case Alphanumeric Private Const C_UPPER_ALPAH = C_UPPER_LETTER & C_DIGIT 'U Upper-Case Alphanumeric Private Const C_MIXED_ALPHA = C_UPPER_LETTER & C_LOWER_LETTER & C_DIGIT 'A Mixed-Case Alphanumeric Private Const c_PUNCTUATION = ",.;:" 'p Punctuation Private Const C_BRACKET = "()[]{}<>" 'b Bracket Private Const C_SPEZ_CHARS = "!""#$%&'()*+,-./:;<=>?@[\]^_`{|}~" 's Printable 7-Bit Special Character Private Const C_ASCII_CHARS = C_MIXED_ALPHA & C_SPEZ_CHARS 'S Printable 7-Bit ASCII 'Pattern für den Repeat Private Const P_REPEAT = "(?:\}(?!\\)(\d+)(?:\s*,\s*(\d+))?\{(?!\\))?" '------------------------------------------------------------------------------- ' Public Methodes '------------------------------------------------------------------------------- '/** ' * Erstellt ein Zfallswerd gemäss Patterndefinition ' * @example rndString("L(a{10}[\#\_\-\|])") -> fo2kvnx1#c8z725 ' * @param String Pattern ' * @return String ' */ Public Function rndString(ByVal iPattern As String) As String Dim tmp As String: tmp = StrReverse(iPattern) Dim resultRev As String Dim retArr() As Variant Dim mixArr() As Variant Do While rxSetRev.test(tmp) Dim m As Object: Set m = rxSetRev.execute(tmp)(0) 'Parsen was vor den <> steht If m.firstIndex > 0 Then parseCharSetRev Left(tmp, m.firstIndex), retArr End If mixArr = emptyArrayVariant parseCharSetRev m.subMatches(0), mixArr 'mischen mixArray mixArr pushArray retArr, Join(mixArr, "") tmp = rxSetRev.replace(tmp, "") tmp = Mid(tmp, m.firstIndex + 1) Loop 'Rest parsen parseCharSetRev tmp, retArr rndString = StrReverse(Join(retArr, "")) End Function '------------------------------------------------------------------------------- ' Private Methodes '------------------------------------------------------------------------------- '/** ' * Parst CharSets [...] ' * @param String ' * @param Array ' */ Private Sub parseCharSetRev(ByVal iPattern As String, ByRef ioArray() As Variant) Dim tmp As String: tmp = iPattern Dim m As Object 'Sucht nach Charsets [....] und wertet sie aus Do While rxCharSetRev.test(tmp) Set m = rxCharSetRev.execute(tmp)(0) 'Vor dem Charset hat es noch Text. Diesen parsen If m.firstIndex > 0 Then parseCharsRev Left(tmp, m.firstIndex), ioArray End If 'Charsetauswerten Dim repeat As Long: repeat = getRepeat(m.subMatches(0), m.subMatches(1)) parsePattern m.subMatches(2), repeat, ioArray tmp = rxCharSetRev.replace(tmp, "") tmp = Mid(tmp, m.firstIndex + 1) Loop 'Reststring noch auswerten If Len(tmp) > 0 Then parseCharsRev tmp, ioArray End If End Sub '/** ' * Parst Einzelcharakters ' * @param String ' * @param Array ' */ Private Sub parseCharsRev(ByVal iPattern As String, ByRef ioArray() As Variant) Dim tmp As String: tmp = iPattern Dim exclude As String 'Sucht nach Patterns Do While rxCharRev.test(tmp) Dim m As Object: Set m = rxCharRev.execute(tmp)(0) Dim repeat As Long: repeat = getRepeat(m.subMatches(0), m.subMatches(1)) If Not isEmpty(m.subMatches(5)) Then exclude = getValiableChars(m.subMatches(2)) 'Einzelpattern auswerten parsePattern m.subMatches(5), repeat, ioArray, exclude ElseIf Not isEmpty(m.subMatches(4)) Then 'Treffer für Fixe Werte pushArray ioArray, String(repeat, m.subMatches(4)) End If tmp = rxCharRev.replace(tmp, "") Loop End Sub '/** ' * Parst Patterrns ' * @param String ' * @param Long ' * @param Array ' * Private Sub parsePattern(ByVal iCharPattern As String, ByVal iRepeat As Long, ByRef ioArray() As Variant, Optional ByVal iExclude As String = Empty) Dim chars As String: chars = getValiableChars(iCharPattern, iExclude) Dim i As Long: For i = 1 To iRepeat Dim rndPos As Long: rndPos = rndBetween(Len(chars), 1) 'Int(Len(chars) * Rnd(Now / 1000)) + 1 pushArray ioArray, Mid(chars, rndPos, 1) Next i End Sub '/** ' * Setzt die möglichen Zeichen zu einem String zusammen ' * @param String Pattern ' * @return String ' */ Public Function getValiableChars(ByVal iPattern As String, Optional ByVal iExclude As String = Empty) As String Dim tmp As String: tmp = iPattern Dim chars As String Dim m As Object Do While rxEscapedRev.test(tmp) Set m = rxEscapedRev.execute(tmp)(0) chars = chars & m.subMatches(0) tmp = rxEscapedRev.replace(tmp, "") 'tmp = Left(tmp, m.firstIndex) & Mid(tmp, m.firstIndex + m.length + 1) Loop Dim i As Long: For i = 1 To Len(tmp) chars = chars & NZ(Choose(InStr(1, C_AVIABLE_PATTERN_CHARS, Mid(tmp, i, 1), vbBinaryCompare), _ C_DIGIT, _ C_LOWER_LETTER, C_UPPER_LETTER, C_MIXED_LETTER, _ C_LOWER_ALPAH, C_UPPER_ALPAH, C_MIXED_ALPHA, _ c_PUNCTUATION, C_BRACKET, C_SPEZ_CHARS, C_ASCII_CHARS _ )) Next i getValiableChars = removeDoupleChars(chars) getValiableChars = removeChars(getValiableChars, iExclude) End Function '/** ' * Mischt ein Array durch ' * @param Array ' */ Private Sub mixArray(ByRef ioArray As Variant) Dim delta As Long: delta = LBound(ioArray) Dim maxI As Long: maxI = UBound(ioArray) Dim index() As Long: ReDim index(maxI - delta) Dim retArr() As Variant: ReDim retArr(LBound(ioArray) To UBound(ioArray)) 'Index aufbauen Dim i As Long: For i = 0 To maxI index(i) = i + delta Next i 'ANzahl mal auswürfeln For i = maxI To 0 Step -1 Dim idx As Long: idx = Int((i + 1) * Rnd(Now / 1000)) retArr(i + delta) = ioArray(index(idx)) If i > 0 Then arrayRemoveItem index, idx Next i ioArray = retArr End Sub '/** ' * Ermittelt den Repeat-Wert ' * @param Variant Upper Limit ' * @param Variant Lower Limit ' * @return Long ' */ Private Function getRepeat(ByVal iUpper As Variant, Optional ByVal iLower As Variant = Empty) As Long getRepeat = 1 If NZ(iUpper) = Empty Then Exit Function getRepeat = CLng(StrReverse(iUpper)) If NZ(iLower) = Empty Then Exit Function getRepeat = rndBetween(getRepeat, CLng(StrReverse(iLower))) End Function '/** ' * Runden mit Limiten ' * @param Variant Upper Limit ' * @param Variant Lower Limit ' * @return Long ' */ Public Function rndBetween(ByVal iUpper As Long, Optional ByVal iLower As Long = 0) As Long rndBetween = Int((iUpper - iLower + 1) * Rnd(Rnd) + iLower) End Function '/** ' * Fügt einen Eintrag am Ende eines Arrays ein ' * @param Array ' * @param Variant ' */ Private Function pushArray(ByRef ioArray() As Variant, ByVal iValue As Variant) Dim idx As Long On Error Resume Next idx = UBound(ioArray) + 1 If Err.Number <> 0 Then idx = 0 On Error GoTo 0 ReDim Preserve ioArray(idx) ioArray(idx) = iValue End Function '/** '* Entfernt ein Eintrag aus einem Array und verkürzt diesen ' * @param Variant Der zu verkürzende Array ' * @param Long Index der gelöscht werden soll ' */ Private Function arrayRemoveItem(ByRef ioArray As Variant, ByVal iIndex As Long) As Variant If iIndex < LBound(ioArray) Or UBound(ioArray) < iIndex Then Exit Function If LBound(ioArray) = UBound(ioArray) Then ioArray = emptyArrayVariant: Exit Function 'Ab dem zu löschenden index alles um eins Nach vorne schieben Dim i As Long: For i = iIndex To UBound(ioArray) - 1 ioArray(i) = ioArray(i + 1) Next i 'Den Array verkürzen ReDim Preserve ioArray(LBound(ioArray) To UBound(ioArray) - 1) End Function '/** ' * entfernt doppelte Zeichen in einem String. ' * @example removeDoupleChars("abcABdef") -> abcABdef ' * removeDoupleChars("abcABdef", vbTextCompare) -> abcdef ' * @param String ' * @param VbCompareMethod ' * @return String ' */ Private Function removeDoupleChars(ByVal iText As String, Optional ByVal iCompMethode As VbCompareMethod = vbBinaryCompare) As String removeDoupleChars = iText Dim i As Long: For i = Len(iText) To 1 Step -1 If InStr(1, Left(removeDoupleChars, i - 1), Mid(removeDoupleChars, i, 1), iCompMethode) > 0 Then removeDoupleChars = Left(removeDoupleChars, i - 1) & Mid(removeDoupleChars, i + 1) End If Next i End Function '/** ' * entfernt ein spezifisches Zeichen in einem String. ' * @param String ' * @param String ' * @param VbCompareMethod ' * @return String ' */ Public Function removeChar(ByVal iText As String, ByVal iChar As String, Optional ByVal iCompMethode As VbCompareMethod = vbBinaryCompare) As String removeChar = iText If iChar = Empty Then Exit Function Dim pos As Long: pos = InStr(1, removeChar, iChar, iCompMethode) Do While Not pos = 0 removeChar = Left(removeChar, pos - 1) & Mid(removeChar, pos + Len(iChar)) pos = InStr(1, removeChar, iChar, iCompMethode) Loop End Function Public Function removeChars(ByVal iText As String, ByVal iChars As String, Optional ByVal iCompMethode As VbCompareMethod = vbBinaryCompare) As String removeChars = iText Dim i As Long: For i = 1 To Len(iText) removeChars = removeChar(removeChars, Mid(iChars, i, 1), iCompMethode) Next i End Function '------------------------------------------------------------------------------- ' Private Properties '------------------------------------------------------------------------------- '/** ' * Achtung: Geht nur mit Reverse String ' * (patterb) ' * 0: Pattern ' * @return RegExp ' */ Private Property Get rxSetRev() As Object Static rx As Object: If rx Is Nothing Then Set rx = cRx("/\)(?!\\)(.*?)\((?!\\)/") Set rxSetRev = rx End Property '/** ' * Achtung: Geht nur mit Reverse String ' * [charSet]{repeatMin,repeatMax} ' * 0: RepeatMax 1: RepeatMin 2: CharSet ' * @return RegExp ' */ Private Property Get rxCharSetRev() As Object Static rx As Object: If rx Is Nothing Then Set rx = cRx("/" & P_REPEAT & "\](?!\\)(.*?)\[(?![\\\^])/") Set rxCharSetRev = rx End Property '/** ' * Achtung: Geht nur mit Reverse String ' * Pattern{repeatMin,repeatMax} oder \EcscapedWert{repeatMin,repeatMax} ' * 0: RepeatMax 1: RepeatMin 3: ExcludePattern 4: Escapten Wert 5: Patternwert ' * @return RegExp ' */ Private Property Get rxCharRev() As Object '"/" & P_REPEAT & "(?:((.)\\)|([" & C_AVIABLE_PATTERN_CHARS & "](?!\\)))/" ===> (?:\}(?!\\)(\d+)(?:\s*,\s*(\d+))?\{(?!\\))?(?:((.)\\)|([dluLaUApbsS](?!\\))) Static rx As Object: If rx Is Nothing Then Set rx = cRx("/" & P_REPEAT & "(?:(.+)\^)?(?:((.)\\)|([" & C_AVIABLE_PATTERN_CHARS & "](?!\\)))/") Set rxCharRev = rx End Property '/** ' * Achtung: Geht nur mit Reverse String ' * Escapten Wert ' * @return RegExp ' */ Private Property Get rxEscapedRev() As Object Static rx As Object: If rx Is Nothing Then Set rx = cRx("/(.)\\(?!\\)/") Set rxEscapedRev = rx End Property '------------------------------------------------------------------------------- ' Private Libraries '------------------------------------------------------------------------------- '/** ' * 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 RegExp-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