Block Finder - Like Function - vba

I have a large string (more than 255 char) called strBlockText. This string includes random text and block numbers. The block numbers should to be in the format ###Block####-## (IE: 245Block6533-56) but sometimes someone enters the wrong block number format in the text - for example ##Block####-## or ###Block###-## or ##Block###-##...etc.
**Note, this is for plain text only.
I want to write a function that will be able to state, "Wrong block number format identified." when the block number is fat fingered.
This is the text I'm using as a sample:
This is a Test that we need to figure out why this isn’t working.
24Block1234-23 This is a Test that we need to figure out why this
isn’t working. 245Block4234-14 This is a Test that we need to figure
out why this isn’t working. This is a Test that 245Block6533-56 we
need to figure out why this isn’t working.
This is the code...that I feel should work but isn't:
Dim strBlockText As String
Dim strBlockCheck As String
If (((strBlockText Like "*##Block####-##*") or _
(strBlockText Like "*###Block###-##*") or _
(strBlockText Like "*##Block###-##*")) And _
(Not strBlockText Like "*###Block####-##*")) Then
strBlockCheck = "Wrong block number format identified."
Else
strBlockCheck = "Block number format acceptable."
End If
Would it be better to use a regex for this instead of like?...is there a reason like isn't working?

Consider this Sub using RegExp object with late binding:
Sub testRegExp2(strS)
Dim regexOne As Object, Matches As Object, Match As Object
'Set regexOne = New RegExp
Set regexOne = CreateObject("VBScript.RegExp")
regexOne.Pattern = "[0-9]+Block[0-9]+-[0-9]+"
regexOne.Global = True
Set Matches = regexOne.Execute(strS)
For Each Match In Matches
If Not Match Like "###Block####-##" Then
Debug.Print "Wrong block number format identified: " & Match
Else
Debug.Print "Block number format acceptable: " & Match
End If
Next
End Sub

Related

VBA Function Passing Multi Variables back to Sub

I have a large string over 500 char which is called strEssay. I want to use a function(since I will need to look for several patterns) to return two values if (for example the name) Frank is found or not.
This is the function I'm trying to use:
Function NameFinder(strEssay as String, strName as String)
Dim varNameCounter as Variant
Dim strNameFinderResult as String
varNameCounter = 0
strNameFinderResult = ""
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
End Function
I want to be able to return back to my subroutine both 'strNameFinderResult' and 'varNameFinderCounter'.
Is there any way that I can return both values?
If I can't return both simultaneously can I return one through the function and the other through a textbox or something? What would calling the function look like in the subroutine and/or how would I need to change my function?
NameFinder() function, returning array of 3 elements. It is called and returned by TestMe(), writing the following to the console:
Function NameFinder(essay As String, name As String)
Dim nameFinderResult As String
Dim namefinderCounter As String
nameFinderResult = "" & essay & name
namefinderCounter = 0.001 + 12
NameFinder = Array(nameFinderResult, namefinderCounter, "something else")
End Function
Public Sub TestMe()
Dim myArray As Variant
myArray = NameFinder("foo", "bar")
Dim i As Long
For i = LBound(myArray) To UBound(myArray)
Debug.Print myArray(i)
Next i
End Sub
As a general rule, you have to give the routine a type like this:
Function NameFinder(strEssay as String, strName as String) as string
But, that returns only ONE value.
So, a function (as opposed to a sub) returns one value (as a general rule).
However, you CAN also return parameters that you pass. I mean, in above, you can't make TWO assignments to one variable, can you?
So, you can use a Sub like this:
Sub NameFinder(strEssay as String, strName as String, _
strNameFinderResult as string, _
varNameFinderCounter as double)
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
So in code, you now can go:
dim strMyResult as string
dim strFinderCount as Double
Call NameFinder("MyEassy", "Joe Blow", strMyResult, strFinderCount)
So, you can return values with the parameters.
Now, I suppose it possible for some strange reason, that you want to use a function to return two values with a single assignment?
What you would do is this in your code module.
Define a custom type, and use that.
eg this:
Option Compare Database
Option Explicit
Type SearchResult
strName As String
FindCount As Double
End Type
Function NameFinder(strEssay As String, strName As String) As SearchResult
NameFinder.FindCount = 0
NameFinder.strName = ""
If strEssay Like "*" & strName & "*" Then
NameFinder.strName = strName
NameFinder.FindCount = 1
Else
NameFinder.strName = ""
NameFinder.FindCount = 0.001
End If
End Function
So, now to use in code? You can go like this:
dim MyResults as SearchResult
MyResults = NameFinder("My eassy", "Joe Blow")
debug.print "Name found result = " & MyResults.strName
debug.print "Count of find = " & MyResult.FindCount
The VERY nice thing about above is you get full intel-sense in your code editor.
eg this:
So by building a custom data type, you can use "one" assignment for the return type. And you get nice type checking and inteli-sense in the VBA code editor.
And you can even do this:
But, to get both variables, then you would in theory wind up calling the function two times. So, you can actually use the function without declarer of variables like this:
Debug.Print NameFinder("MyEassy", "Joe blow").strName
Debug.Print NameFinder("MyEassy", "Joe blow").FindCount
So, I don't recommend the above, but in the case in which you ONLY want one of the return values, then the raw expression (function) like above would be a use case (and no need to even declare a return variable).
But, without a doubt, define a custom type in code as per above. The reason is now you get a really nice VBA editor type-checking, inteli-sense, and also that you only have to declare "one" variable that holds two values.
In fact, the results are very much like JavaScript, or even c# in which you declare a "class" type. So with a custom "type" you are declaring a data type of your own. And the beauty of this is if you need say 3 values, then once again you create a type with 3 "inside" values.
The you ONLY have to declare that one variable as the custom type.
With this you get:
Very valuable compile time syntax and data type checking of the var types you are using.
You get GREAT VBA inteli-sense while coding - which means less coding mistakes.
And you type far less typing in the VBA editor as it will pop-up the choices for you as you write code. And you can't type or choose the wrong sub - type, as the compiler will catch this.

Parsing XML Response in VBA and extracting only last data

I'm trying to do a XML request through excel vba for one of the internal links (in our company). when i send request and receive response using the code below, i get the following as response text:
[{"CPN":"700-42887-01","ExtractDt":"2018-04-02
00:00:00","Demand":"8645"},"CPN":"700-42887-01","ExtractDt":"2018-04-09
00:00:00","Demand":"8985"},{"CPN":"700-42887-01","ExtractDt":"2018-04-16
00:00:00","Demand":"9341"},{"CPN":"700-42887-01","ExtractDt":"2018-04-23
00:00:00","Demand":"9589"},{"CPN":"700-42887-01","ExtractDt":"2018-04-30
00:00:00","Demand":"9210"},{"CPN":"700-42887-01","ExtractDt":"2018-05-07
00:00:00","Demand":"9698"},{"CPN":"700-42887-01","ExtractDt":"2018-05-14
00:00:00","Demand":"9542"},{"CPN":"700-42887-01","ExtractDt":"2018-05-21
00:00:00","Demand":"9692"},{"CPN":"700-42887-01","ExtractDt":"2018-05-28
00:00:00","Demand":"10416"},{"CPN":"700-42887-01","ExtractDt":"2018-06-04
00:00:00","Demand":"6777"},{"CPN":"700-42887-01","ExtractDt":"2018-06-11
00:00:00","Demand":"12774"},{"CPN":"700-42887-01","ExtractDt":"2018-06-18
00:00:00","Demand":"12912"},{"CPN":"700-42887-01","ExtractDt":"2018-06-25
00:00:00","Demand":"12693"},{"CPN":"700-42887-01","ExtractDt":"2018-07-02
00:00:00","Demand":"12895"},{"CPN":"700-42887-01","ExtractDt":"2018-07-09
00:00:00","Demand":"13366"},{"CPN":"700-42887-01","ExtractDt":"2018-07-16
00:00:00","Demand":"13550"},{"CPN":"700-42887-01","ExtractDt":"2018-07-23
00:00:00","Demand":"7971"},{"CPN":"700-42887-01","ExtractDt":"2018-07-30
00:00:00","Demand":"12442"},{"CPN":"700-42887-01","ExtractDt":"2018-08-06
00:00:00","Demand":"12960"},{"CPN":"700-42887-01","ExtractDt":"2018-08-13
00:00:00","Demand":"14106"},{"CPN":"700-42887-01","ExtractDt":"2018-08-20
00:00:00","Demand":"13543"},{"CPN":"700-42887-01","ExtractDt":"2018-08-27
00:00:00","Demand":"13570"},{"CPN":"700-42887-01","ExtractDt":"2018-09-03
00:00:00","Demand":"13506"},{"CPN":"700-42887-01","ExtractDt":"2018-09-10
00:00:00","Demand":"13914"},{"CPN":"700-42887-01","ExtractDt":"2018-09-17
00:00:00","Demand":"13241"},{"CPN":"700-42887-01","ExtractDt":"2018-09-24
00:00:00","Demand":"13449"}]
I want to extract only the last Value - Namely 13449. What is the code that i need to write to accomplish this.
Thanks in Advance!`
Code used
Sub xmlparsing()
Dim jstring As String
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "**INTERNAL COMPANY LINK HERE**", False
.send
If .Status <> 200 Then Exit Sub
jstring = .responseText
Debug.Print jstring
End With`
End Sub
You could use InStrRev
Mid$(responseText, InStrRev(responseText, ":") + 2, (InStrRev(responseText, "}") - 1) - (InStrRev(responseText, ":") + 2))
InStrRev walks the string from right to left. We know you want the value at the end of the string so this direction is useful. We specify as an argument the character to find. The overall string is the responseText.
The first character to find is ":", from right to left. This will be where you have :"13449"}]. Offset from this + 2 to get the actual start of the value you want, in this case the 1 in 13449.
Same logic to determine end point of string. I use "}" as end point then make an adjustment to move forward to the numbers. Mid allows you to specify a string, start point and number of characters. I pass the arguments to extract the required string to Mid. I used typed functions (with the $ at the end) as more efficient when working with strings.
Considering the fact, that you have already parsed the XML to string, then the easiest fact is to try to slice the string. To see how it works, put the string from .responseText to A1 range and run this:
Sub TestMe()
Dim responseText As String
responseText = Range("A1")
Dim myArr As Variant
myArr = Split(responseText, "Demand"":""")
Debug.Print Left(myArr(UBound(myArr)), Len(myArr(UBound(myArr))) - 4)
End Sub
What it does is to split the string into array by the word Demand":" and to take anything but the last 4 characters of the last unit of the array.

string modifiers and properties do not work

I am trying to use Methods and Properties of the String Class to modify a string, but I keep getting an Invalid qualifier compile error. I even copied the following code* directly from the MSDN website and it throws the same error.
Public Sub Main()
Dim original As String
original = "aaabbb"
Dim modified As String
modified = original.Insert(3, " ")
End Sub
'This is the original code, but I had to change it slightly because the word-vba
'programming environment didn't like the syntax and highlighted everything red.
'Public Sub Main()
'Dim original As String = "aaabbb"
'Console.WriteLine("The original string: '{0}'", original)
'Dim modified As String = original.Insert(3, " ")
'Console.WriteLine("The modified string: '{0}'", modified)
'End Sub
Does word-vba not support string class modifiers and properties, am I not initializing the string correctly, or is there some other problem?
modified = original.Insert(3, " ")
You're thinking in VB.NET, but writing VBA. Strings (or any primitive or UDT type) don't have members in VBA. Not your fault, finding official VBA documentation is getting harder every day, with every "VBA" search yielding results for VB.NET.
That original code is clearly VB.NET.
If you mean to concatenate 3 spaces in front of original, then what you want to do is this:
modified = String(3, " ") & original
If you mean to get a new string in which a specified string is inserted at a specified index position in this instance (MSDN), then you want to do this (thanks #A.S.H!):
modified = Left$(original, 3) & " " & Right$(original, Len(original) - 3)

How to extract numbers UNTIL a space is reached in a string using Excel 2010?

I need to pull the code from the following string: 72381 Test 4Dx for Worms. The code is 72381 and the function that I'm using does a wonderful job of pulling ALL the numbers from a string and gives me back 723814, which pulls the 4 from the description of the code. The actual code is only the 72381. The codes are of varying length and are always followed by a space before the description begins; however there are spaces in the descriptions as well. This is the function I am using that I found from a previous search:
Function OnlyNums(sWord As String)
Dim sChar As String
Dim x As Integer
Dim sTemp As String
sTemp = ""
For x = 1 To Len(sWord)
sChar = Mid(sWord, x, 1)
If Asc(sChar) >= 48 And _
Asc(sChar) <= 57 Then
sTemp = sTemp & sChar
End If
Next
OnlyNums = Val(sTemp)
End Function
If the first character in the description part of your string is never numeric, you could use the VBA Val(string) function to return all of the numeric characters before the first non-numeric character.
Function GetNum(sWord As String)
GetNum = Val(sWord)
End Function
See the syntax of the Val(string) function for full details of it's usage.
You're looking for the find function.. Example:
or in VBA instr() and left()
Since you know the pattern is always code followed by space just use left of the string for the number of characters to the first space found using instr. Sample in immediate window above. Loop is going to be slow, and while it may validate they are numeric why bother if you know pattern is code then space?
In similar situations in C# code, I leave the loop early after finding the first instance of a space character (32). In VBA, you'd use Exit For.
You can get rid of the function altogether and use this:
split("72381 Test 4Dx for Worms"," ")(0)
This will split the string into an array using " " as the split char. Then it shows us address 0 in the array (the first element)
In the context of your function if you are dead set on using one it is this:
Function OnlyNums(sWord As String)
OnlyNums = Split(sWord, " ")(0)
End Function
While I like the simplicity of Mark's solution, you could use an efficient parser below to improve your character by character search (to cope with strings that don't start with numbers).
test
Sub test()
MsgBox StrOut("72381 Test 4Dx")
End Sub
code
Function StrOut(strIn As String)
Dim objRegex As Object
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Pattern = "^(\d+)(\s.+)$"
If .test(strIn) Then
StrOut = .Replace(strIn, "$1")
Else
StrOut = "no match"
End If
End With
End Function

Count instances of specific string in Email

I've looked for similar questions regarding this with no luck. I currently have working code that looks for a specific instance of string in the email body and subject. Upon finding this string, it has userform that takes it in another direction. My intentions are to have it run through the entire email and count how many times it finds this iteration and give that a callable variable for that userform(popup). Here is my code. It comes back with an error that says "InvalidCastException" so i'm guessing this is a conversion error. Any ideas? Thanks!
Ok so I added your comments together and came up with the following. I'm getting alot of errors, as the regex.Pattern I'm assuming doesn't exist. Any ideas? Also thank you for the literature.
Dim regEx ' Create variable.
Dim numfound As Integer
regEx = New RegExp ' Create a regular expression.
'Here it tells me that the regEx.Patter doesn't exist or Pattern is a member of the regex class
regEx.Pattern = "\*#{9}\*" ' Set pattern.
regEx.IgnoreCase = True ' Set case insensitivity.
regEx.Global = True ' Set global applicability.
If regEx.Execute(mailItem.Body) Then
' Getting a "not declared" runtime what should it be declared a Integer such as Dim numfound as Object
numfound = regEx.count
End If
Did some more digging and basically I'm back where I started with an InvalidCastException,
Conversion from string "111111111
123121233
" to type 'Long' is not valid.
Basically in the body of my test email I had those two strings of numbers and it can't convert them to a string to then run through the regexp iteration. Any ideas?
Dim sBody : sBody = (mailItem.Body) Or (mailItem.Subject) 'This is where is gives me the error
Dim Search : Search = New RegExp
Search.Global = True
Search.Pattern = "\*#{9}\*"
MsgBox(Search.Execute(sBody).Count, MsgBoxStyle.OkOnly)
To get you started, if you want to use a RegExp:
(1) Start your reading here and here
(2) Demo code to give you food for thought:
>> Dim sBody : sBody = "Three instances of ###: ### and a part of ####."
>> Dim Search : Set Search = New RegExp
>> Search.Global = True
>> Search.Pattern = "#{3,3}"
>> WScript.Echo Search.Execute(sBody).Count
>>
3
>>
The easiest way to do this would be just create an instance of RegEx and use it to parse the Item.Body.
Set Search = new RegExp
Search.IgnoreCase = True
Search.Global = True
Search.Pattern = "\*#{9}\*"
If Search.Test(Item.Body) Then
'will hold how many times it has been found
numFound = Search.Count
End If
Here is a link with a bit more information on RegExp http://msdn.microsoft.com/en-us/library/ms974570.aspx