Make sure that a string is exactly a 5 digit number - vba

I want to return true if some strings = 'XXXXX'
Where every X is a number 0 through 9
I know there must be a dozen ways to do this but I would like to know the best way.

yourString Like "#####"

If you want the simplest way, you can go with this:
Function MyFunction(myString As String) As Boolean
MyFunction = ((Len(myString) = 5) And (IsNumeric(myString)))
End Function
If you want the more efficient way, you'd have to run some tests for the different methods people suggested.
Edit: The previous solution doesn't work well (see the first 2 comments) but I'm letting it there since it's what has been accepted. Here is what I would do :
Function MyFunction(myString As String) As Boolean
Dim myDouble As Double
Dim myLong As Long
myDouble = Val(myString)
myLong = Int(myDouble / 10000)
MyFunction = ((Len(myString) = 5) And (myLong > 0) And (myLong < 10))
End Function
There is no error "protection" in that function, so if you try to check a too large number like 22222222222222, it will not work.

Similar question previously asked: link text
Basically want to check
(Len(s) = 5) And IsNumeric(s)

You can also use regular expressions to solve this problem. If you include Microsoft VBScript Regular Expressions 5.5 in your VBA project, you can use RegExp and MatchCollection variables as in the function below. (This is a modification of the response to this post at ozgrid.com.)
Public Function FiveDigitString(strData As String) As Boolean
On Error GoTo HandleError
Dim RE As New RegExp
Dim REMatches As MatchCollection
With RE
.MultiLine = False
.Global = False
.IgnoreCase = True
.Pattern = "^[0-9][0-9][0-9][0-9][0-9]$"
End With
Set REMatches = RE.Execute(strData)
If REMatches.Count = 1 Then
FiveDigitString = True
Else
FiveDigitString = False
End If
Exit Function
HandleError:
Debug.Print "Error in FiveDigitString: " & Err.Description
FiveDigitString = False
End Function

Related

Add regular expression reference before running the macro vba

code :
Public Sub CallDeleteAllText(control As IRibbonControl)
Call LeaveNumbers
End Sub
Public Function PullOnly(strSrc As String, CharType As String)
Dim RE As RegExp
Dim regexpPattern As String
Set RE = New RegExp
CharType = LCase(CharType)
Select Case CharType
Case Is = "digits":
regexpPattern = "\D"
Case Is = "letters":
regexpPattern = "\d"
Case Else:
regexpPattern = ""
End Select
RE.Pattern = regexpPattern
RE.Global = True
PullOnly = RE.Replace(strSrc, "")
End Function
Sub LeaveNumbers()
Dim cCell As Range
For Each cCell In Selection
If cCell <> "" Then
cCell.Value = "'" & PullOnly(cCell.Text, "digits")
End If
Next cCell
With Selection
.NumberFormat = "0"
.Value = .Value
End With
End Sub
This code removes all text from the cell and leave all the numbers. But for this code to run, the user has to manually add Microsoft VBScript Regular Expressions reference from Tools > References. Is there a way to add the reference within the code itself so that, first it adds the reference and then removes all the text?
Change these two lines of regex declaration and assignment in the PullOnly function to static late binding.
Dim RE As RegExp
...
Set RE = New RegExp
'becomes,
static RE As object
...
if re is nothing then Set RE = createobject("VBScript.RegExp")
Static vars are 'remembered' by the sub procedure or function where they are declared. Normally, RE would be 'forgotten' (and destructed) when the function was completed and exited. However, with a static RE, the second time (and all subsequent times) the function is entered it 'remembers' that it has already been set to a regex scripting object so it is unnecessary to set it again.
This does not mean that a static var is globally public; it is only available within the function or sub procedure where it was declared.

VBA - How to check if a String is a valid hex color code?

To prevent errors, I need to check if a String retrieved from a custom input box is not a valid hex color code. So far I found various solutions for other languages, but none for VBA.
Working on the following code, giving a not hex value input will cause a run time error. That's critical to my project, since I am working on a protected sheet.
Public Function HexWindow(MyCell As String, Description As String, Caption As String)
Dim myValue As Variant
Dim priorValue As Variant
priorValue = Range(MyCell).Value
myValue = InputBox(Description, Caption, Range(MyCell).Value)
Range(MyCell).Value = myValue
If myValue = Empty Then
Range(MyCell).Value = priorValue
End If
tHex = Mid(Range(MyCell).Text, 6, 2) & Mid(Range(MyCell).Text, 4, 2) & Mid(Range(MyCell).Text, 2, 2)
Range(MyCell).Interior.Color = WorksheetFunction.Hex2Dec(tHex)
End Function
How can I set a condition that recognizes a value not being in the format of "#" & 6 characters from 0-9 and A-F in any case?
Couple ways to do this. The easiest way is with a regular expression:
'Requires reference to Microsoft VBScript Regular Expressions x.x
Private Function IsHex(inValue As String) As Boolean
With New RegExp
.Pattern = "^#[0-9A-F]{1,6}$"
.IgnoreCase = True 'Optional depending on your requirement
IsHex = .Test(inValue)
End With
End Function
If for some reason that doesn't appeal to you, you could also take advantage of VBA's permissive casting of hex strings to numbers:
Private Function IsHex(ByVal inValue As String) As Boolean
If Left$(inValue, 1) <> "#" Then Exit Function
inValue = Replace$(inValue, "#", "&H")
On Error Resume Next
Dim hexValue As Long
hexValue = CLng(inValue) 'Type mismatch if not a number.
If Err.Number = 0 And hexValue < 16 ^ 6 Then
IsHex = True
End If
End Function
I would use regular expressions for this. First you must go to Tools-->Referencesin the VBA editor (alt-f11) and make sure this library is checked
Microsoft VBScript Regular Expressions 5.5
Then you could modify this sample code to meet your needs
Sub RegEx_Tester()
Set objRegExp_1 = CreateObject("vbscript.regexp")
objRegExp_1.Global = True
objRegExp_1.IgnoreCase = True
objRegExp_1.Pattern = "#[a-z0-9]{6}"
strToSearch = "#AAFFDD"
Set regExp_Matches = objRegExp_1.Execute(strToSearch)
If regExp_Matches.Count = 1 Then
MsgBox ("This string is a valid hex code.")
End If
End Sub
The main feature of this code is this
objRegExp_1.Pattern = "#[a-z,A-Z,0-9]{6}"
It says that you will accept a string that has a # followed by any 6 characters that are a combination of upper case or lower case strings or numbers 0-9. strToSearch is just the string you are testing to see if it is a valid color hex string. I believe this should help you.
I should credit this site. You may want to check it out if you want a crash course on regular expressions. They're great once you learn how to use them.

Check for pattern in a string in VBA

My requirement is that I need to check whether a string variable has first character "P" followed by digits.
For example
P0
P123
P22
To-date I have looked at options for using Like as per here.
You could use a simple RegExp:
Function strOut(strIn As String) As String
Dim objRegex As Object
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Pattern = "^P\d+$"
strOut = .Test(strIn)
End With
End Function
test code
Sub Test()
Debug.Print strOut("P22")
Debug.Print strOut("aP22")
Debug.Print strOut("P12344")
End Sub
You can also use the native Like operator:
Sub Foo()
Dim x As String, matches As Boolean
x = "P123"
matches = (x Like "P" & Application.Rept("[0-9]", Len(x) - 1))
Debug.Print matches
End Sub
(mystr Like "P#") or (mystr Like "P##") or (mystr Like "P###")
is probably the fastest solution, and the simplest one.
mystr like left("P######", len(mystr))
could work as well, and be more generic.
Anyway, if this is to be used in a query, a generic LIKE should be much more efficient than any VBA function.
my 0.02 cents
Sub Foo2()
x = "P0123"
Debug.Print IsNumeric(Replace(x, "P", "",,1))
End Sub

Find and replace all names of variables in VBA module

Let's assume that we have one module with only one Sub in it, and there are no comments. How to identify all variable names ? Is it possible to identify names of variables which are not defined using Dim ? I would like to identify them and replace each with some random name to obfuscate my code (O0011011010100101 for example), replace part is much easier.
List of characters which could be use in names of macros, functions and variables :
ABCDEFGHIJKLMNOPQRSTUVWXYZdefghijklmnopqrstuvwxyzg€‚„…†‡‰Š‹ŚŤŽŹ‘’“”•–—™š›śťžź ˇ˘Ł¤Ą¦§¨©Ş«¬­®Ż°±˛ł´µ¶·¸ąş»Ľ˝ľżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙ÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙
Below are my function I've wrote recenlty :
Function randomName(n as integer) as string
y="O"
For i = 2 To n:
If Rnd() > 0.5 Then
y = y & "0"
Else
y = y & "1"
End If
Next i
randomName=y
End Function
In goal to replace given strings in another string which represent the code of module I use below sub :
Sub substituteNames()
'count lines in "Module1" which is part of current workbook
linesCount = ActiveWorkbook.VBProject.VBComponents("Module1").CodeModule.CountOfLines
'read code from module
code = ActiveWorkbook.VBProject.VBComponents("Module1").CodeModule.Lines(StartLine:=1, Count:=linesCount)
inputStr = Array("name1", "name2", "name2") 'some hardwritten array with string to replace
namesLength = 20 'length of new variables names
For i = LBound(inputStr) To UBound(inputStr)
outputString = randomName(namesLength-1)
code = Replace(code, inputStr(i), outputString)
Next i
Debug.Print code 'view code
End Sub
then we simply substitute old code with new one, but how to identify strings with names of variables ?
Edition
Using **Option Explicit ** decrease safety of my simple method of obfuscation, because to reverse changes you only have to follow Dim statements and replace ugly names with something normal. Except that to make such substitution harder, I think it's good idea to break the line in the middle of variable name :
O0O000O0OO0O0000 _
0O00000O0OO0
the simple method is also replacing some strings with chains based on chr functions chr(104)&chr(101)&chr(108)&chr(108)&chr(111) :
Sub stringIntoChrChain()
strInput = "hello"
strOutput = ""
For i = 1 To Len(strInput)
strOutput = strOutput & "chr(" & Asc(Mid(strInput, i, 1)) & ")&"
Next i
Debug.Print Mid(strOutput, 1, Len(strOutput) - 1)
End Sub
comments like below could make impression on user and make him think that he does not poses right tool to deal with macro etc.:
'(k=Äó¬)w}ż^¦ů‡ÜOyúm=ěËnóÚŽb W™ÄQó’ (—*-ĹTIäb
'R“ąNPÔKZMţ†üÍQ‡
'y6ű˛Š˛ŁŽ¬=iýQ|˛^˙ ‡ńb ¬ĂÇr'ń‡e˘źäžŇ/âéç;1qýěĂj$&E!V?¶ßšÍ´cĆ$Âű׺Ůî’ﲦŔ?TáÄu[nG¦•¸î»éüĽ˙xVPĚ.|
'ÖĚ/łó®Üă9Ę]ż/ĹÍT¶Mµę¶mÍ
'q[—qëýY~Pc©=jÍ8˘‡,Ú+ń8ŐűŻEüńWü1ďëDZ†ć}ęńwŠbŢ,>ó’Űçµ™Š_…qÝăt±+‡ĽČg­řÍ!·eŠP âńđ:ŶOážű?őë®ÁšńýĎáËTbž}|Ö…ăË[®™
You can use a regular expression to find variable assignments by looking for the equals sign. You'll need to add a reference to the Microsoft VBScript Regular Expressions 5.5 and Microsoft Visual Basic for Applications Extensibility 5.3 libraries as I've used early binding.
Please be sure to back up your work and test this before using it. I could have gotten the regex wrong.
UPDATE:
I've refined the regular expressions so that it no longer catches datatypes of strongly typed constants (Const ImAConstant As String = "Oh Noes!" previously returned String). I've also added another regex to return those constants as well. The last version of the regex also mistakenly caught things like .Global = true. That was corrected. The code below should return all variable and constant names for a given code module. The regular expressions still aren't perfect, as you'll note that I was unable to stop false positives on double quotes. Also, my array handling could be done better.
Sub printVars()
Dim linesCount As Long
Dim code As String
Dim vbPrj As VBIDE.VBProject
Dim codeMod As VBIDE.CodeModule
Dim regex As VBScript_RegExp_55.RegExp
Dim m As VBScript_RegExp_55.match
Dim matches As VBScript_RegExp_55.MatchCollection
Dim i As Long
Dim j As Long
Dim isInDatatypes As Boolean
Dim isInVariables As Boolean
Dim datatypes() As String
Dim variables() As String
Set vbPrj = VBE.ActiveVBProject
Set codeMod = vbPrj.VBComponents("Module1").CodeModule
code = codeMod.Lines(1, codeMod.CountOfLines)
Set regex = New RegExp
With regex
.Global = True ' match all instances
.IgnoreCase = True
.MultiLine = True ' "code" var contains multiple lines
.Pattern = "(\sAs\s)([\w]*)(?=\s)" ' get list of datatypes we've used
' match any whole word after the word " As "
Set matches = .Execute(code)
End With
ReDim datatypes(matches.count - 1)
For i = 0 To matches.count - 1
datatypes(i) = matches(i).SubMatches(1) ' return second submatch so we don't get the word " As " in our array
Next i
With regex
.Pattern = "(\s)([^\.\s][\w]*)(?=\s\=)" ' list of variables
' begins with a space; next character is not a period (handles "with" assignments) or space; any alphanumeric character; repeat until... space
Set matches = .Execute(code)
End With
ReDim variables(matches.count - 1)
For i = 0 To matches.count - 1
isInDatatypes = False
isInVariables = False
' check to see if current match is a datatype
For j = LBound(datatypes) To UBound(datatypes)
If matches(i).SubMatches(1) = datatypes(j) Then
isInDatatypes = True
Exit For
End If
'Debug.Print matches(i).SubMatches(1)
Next j
' check to see if we already have this variable
For j = LBound(variables) To i
If matches(i).SubMatches(1) = variables(j) Then
isInVariables = True
Exit For
End If
Next j
' add to variables array
If Not isInDatatypes And Not isInVariables Then
variables(i) = matches(i).SubMatches(1)
End If
Next i
With regex
.Pattern = "(\sConst\s)(.*)(?=\sAs\s)" 'strongly typed constants
' match anything between the words " Const " and " As "
Set matches = .Execute(code)
End With
For i = 0 To matches.count - 1
'add one slot to end of array
j = UBound(variables) + 1
ReDim Preserve variables(j)
variables(j) = matches(i).SubMatches(1) ' again, return the second submatch
Next i
' print variables to immediate window
For i = LBound(variables) To UBound(variables)
If variables(i) <> "" And variables(i) <> Chr(34) Then ' for the life of me I just can't get the regex to not match doublequotes
Debug.Print variables(i)
End If
Next i
End Sub

Get the value between the brackets

I have a column with some stuff that looks like the following string: V2397(+60)
How do I get the value between the brackets? In this case the +60.
The number (and character) before the brackets is not something standardized and neither the number between the brackets (it can be 100, 10 -10 or even 0...).
VBA code:
cellValue = "V2397(+60)"
openingParen = instr(cellValue, "(")
closingParen = instr(cellValue, ")")
enclosedValue = mid(cellValue, openingParen+1, closingParen-openingParen-1)
Obviously cellValue should be read from the cell.
Alternatively, if cell A1 has one of these values, then the following formula can be used to extrcat the enclosed value to a different cell:
=Mid(A1, Find("(", A1)+1, Find(")",A1)-Find("(",A1)-1)
I would use a regular expression for this as it easily handles
a no match case
multiple matches in one string if required
more complex matches if your parsing needs evolve
The Test sub runs three sample string tests
The code below uses a UDF which you could call directly in Excel as well, ie = GetParen(A10)
Function GetParen(strIn As String) As String
Dim objRegex As Object
Dim objRegMC As Object
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Pattern = "\((.+?)\)"
If .Test(strIn) Then
Set objRegMC = .Execute(strIn)
GetParen = objRegMC(0).submatches(0)
Else
GetParen = "No match"
End If
End With
Set objRegex = Nothing
End Function
Sub Test()
MsgBox GetParen("V2397(+60)")
MsgBox GetParen("Not me")
MsgBox GetParen(ActiveSheet.Range("A1"))
End Sub
Use InStr to get the index of the open bracket character and of the close bracket character; then use Mid to retrieve the desired substring.
Using InStr$ and Mid$ will perform better, if the parameters are not variants.
Thanks to Andrew Cooper for his answer.
For anyone interested I refactored into a function...
Private Function GetEnclosedValue(query As String, openingParen As String, closingParen As String) As String
Dim pos1 As Long
Dim pos2 As Long
pos1 = InStr(query, openingParen)
pos2 = InStr(query, closingParen)
GetEnclosedValue = Mid(query, (pos1 + 1), (pos2 - pos1) - 1)
End Function
To use
value = GetEnclosedValue("V2397(+60)", "(", ")" )