I am trying to analyze a String with VBA code.
I read a String that looks like that :
myStringInput = "FRAG_INST = someValue,DR = otherValue, FRAG = anotherValue"
And in my code I would like to associate some variable according to the value read in the string, I would like to initialize my variables like that :
Dim dr, frag, fraginst As String
fraginst = someValue
dr = otherValue
frag = anotherValue
I have tried things like Trim/Split/InStr combination but I always ended up with wrong values.
I cannot just use "Mid" function because length of the values mays change from one execution to another...
To be clearer, I need to design function like this
fraginst = NiceFunction("FRAG_INST",myStringInput)
and it would return "someValue"
Is there an easy way to do what I want ?
Thanks
This solution is working fine. May be you can try this. I have not used any Regular expressions though.
Approach:
I first splitted the string by delimiter ,(comma). Traversed through each of the array elements and splitted each element by '='. Compared the value to the string present on the left side of '=' and returned value to the right of '=' after trimming. Mid function was needed.
myStringInput = "FRAG_INST = someValue,DR = otherValue, FRAG = anotherValue"
fraginst = NiceFunction("FRAG_INST",myStringInput)
MsgBox fraginst
Function NiceFunction(str1, str2)
tempArr1 = Split(str2,",")
For i=0 To UBound(tempArr1)
tempArr2 = Split(tempArr1(i),"=")
If StrComp(Trim(tempArr2(0)),str1,1)=0 Then
NiceFunction = Trim(tempArr2(1))
Exit For
End If
Next
End Function
Function NiceFunction( varName, inputString )
Dim match
With CreateObject("VBScript.RegExp")
.IgnoreCase = True
.Global = False
.Pattern = "(?:^|,)\s*" & varName & "\s*=\s*([^,]*)"
For Each match in .Execute( inputString )
NiceFunction = match.subMatches.Item(0)
Next
End With
End Function
You can use a RegExp object to extract the part of the string you need.
Instead of insisting on 'single' variables (DR, ...) which would need the dynamic creation of variables at run-time, you should use a dictionary:
Option Explicit
Function s2d(s)
If IsEmpty(gR) Then
Set gR = New RegExp
gR.Global = True
gR.Pattern = "(\w+)\s?=\s?(\w+)"
End If
Set s2d = CreateObject("Scripting.Dictionary")
Dim m
For Each m In gR.Execute(s)
If s2d.Exists(m.SubMatches(0)) Then
' Error: dup key
Else
s2d.Add m.SubMatches(0), m.SubMatches(1)
End If
Next
End Function
Function qq(s)
qq = """" & s & """"
End Function
Dim gR ' "static" in s2d()
Dim s : s = "FRAG_INST = someValue,DR = otherValue, FRAG = anotherValue"
If 0 < WScript.Arguments.Count Then s = WScript.Arguments(0)
Dim d : Set d = s2d(s)
Dim k
For Each k In d.Keys()
WScript.Echo qq(k), "=>", qq(d(k))
Next
If d.Exists("DR") Then WScript.Echo "DR:", d("DR") ' access 'single' var
Output:
cscript 44282497.vbs
"FRAG_INST" => "someValue"
"DR" => "otherValue"
"FRAG" => "anotherValue"
DR: otherValue
P.S.
I used a RegExp, because variable names must be 'words'/match "\w+" and your sample data values dito, whereas the separators look like a mess/use spaces creatively. See here fore how to use RegExps in VBA.
Related
I have a column which contain cells that have some list of alphanumeric number system as follows:
4A(4,5,6,7,8,9); 4B(4,5,7,8); 3A(1,2,3); 3B(1,2,3), 3C(1,2)
On a cell next to it, I use a UDF function to get rid of special characters "(),;" in order to leave the data as
4A456789 4B4578 3A123 3B123 3C12
Function RemoveSpecial(Str As String) As String
Dim SpecialChars As String
Dim i As Long
SpecialChars = "(),;-abcdefghijklmnopqrstuvwxyz"
For i = 1 To Len(SpecialChars)
Str = Replace$(Str, Mid$(SpecialChars, i, 1), "")
Next
RemoveSpecial = Str
End Function
For the most part this works well. However, on certain occasions, the cell would contain an unorthodox pattern such as when a space is included between the 4A and the parenthesized items:
4A (4,5,6,7,8,9);
or when a text appears inside the parenthesis (including two spaces on each side):
4A (4,5, skip 8,9);
or a space appears between the first two characters:
4 A(4,5,6)
How would you fix this so that the random spaces are removed except to delaminate the actual combination of data?
One strategy would be to substitute the patterns you want to keep before eliminating the "special" characters, then restore the desired patterns.
From your sample data, it look like you want to keep a space only if it follow ); or ),
Something like this:
Function RemoveSpecial(Data As Variant) As Variant
Dim SpecialChars As String
Dim KeepStr As Variant, PlaceHolder As Variant, ReplaceStr As Variant
Dim i As Long
Dim DataStr As String
SpecialChars = " (),;-abcdefghijklmnopqrstuvwxyz"
KeepStr = Array("); ", "), ")
PlaceHolder = Array("~0~", "~1~") ' choose a PlaceHolder that won't appear in the data
ReplaceStr = Array(" ", " ")
DataStr = Data
For i = LBound(KeepStr) To UBound(KeepStr)
DataStr = Replace$(DataStr, KeepStr(i), PlaceHolder(i))
Next
For i = 1 To Len(SpecialChars)
DataStr = Replace$(DataStr, Mid$(SpecialChars, i, 1), vbNullString)
Next
For i = LBound(KeepStr) To UBound(KeepStr)
DataStr = Replace$(DataStr, PlaceHolder(i), ReplaceStr(i))
Next
RemoveSpecial = Application.Trim(DataStr)
End Function
Another strategy would be regular expressions (RegEx)
It looks like a regular expression could come in handy here, for example:
Function RemoveSpecial(Str As String) As String
With CreateObject("vbscript.regexp")
.Global = True
.Pattern = "\)[;,]( )|[^A-Z\d]+"
RemoveSpecial = .Replace(Str, "$1")
End With
End Function
I have used the regular expression:
\)[;,]( )|[^A-Z\d]+
You can see an online demo to see the result in your browser. The way this works is to apply a form of what some would call "The best regex trick ever!"
\)[;,]( ) - Escape a closing paranthesis, then match either a comma or semicolon before we capture a space character in our 1st capture group.
| - Or use the following alternation:
[^A-Z\d]+ - Any 1+ char any other than in given character class.
EDIT:
In case you have values like 4A; or 4A, you can use:
(?:([A-Z])|\))[;,]( )|[^A-Z\d]+
And replace with $1$2. See an online demo.
I have a lot of data that is written in code, that I need to make more readable. For this purpose, I make use of replace in vba, especially because there is a lot of possible codes that need to be removed or replaced with different symbols, and I still need to add a few more possible texts.
The following UDF works, but whenever I find a new possible part of the code that needs to be replaced, I have to write another whole 'Let#' to compensate. I feel like there is a better way to write this function, so I was wondering if someone could help out.
Function Unit(Req_Header As String) As String
Dim Func As String
Dim Amount As String
Func = AkeFind(Req_Header) 'AkeFind is a different UDF that looks up the correct string in a dataset (and returns as a string)
Dim LetA As String
Dim LetB As String
Dim LetC As String
Dim LetD As String
Dim LetE As String
Dim LetF As String
LetA = Replace(Func, "formaat_papier_", "")
LetB = Replace(LetA, "motief_", "")
LetC = Replace(LetB, "materiaal_", "")
LetD = Replace(LetC, "vorm_", "")
LetE = Replace(LetD, "_", " ")
LetF = Replace(LetE, "•", "-")
Amount = LetF
Select Case Amount
Case "CENTIMETER", "Centimeter", "centimeter"
Unit = "cm"
Case "MILLIMETER", "Millimeter", "millimeter"
Unit = "mm"
Case "METER", "Meter", "meter"
Unit = "m"
Case "CENTILITER", "Centiliter", "centiliter"
Unit = "cl"
Case "MILLILITER", "Milliliter", "milliliter"
Unit = "ml"
Case "LITER", "Liter", "liter"
Unit = "l"
Case "KILOGRAM", "Kilogram", "kilogram"
Unit = "kg"
Case "MILLIGRAM", "Milligram", "milligram"
Unit = "mg"
Case "GRAM", "Gram", "gram"
Unit = "g"
Case "PIECE", "Piece", "piece"
Unit = "Stuks"
Case "plastic", "kunstof"
Unit = "Kunststof"
Case "metaal", "staal"
Unit = "Metaal"
Case Else
Unit = Amount
End Select
End Function
You could use something along these lines. Requires maintainence for new units, so i'd propably go along the table approach too. Probably best to separate out the match part of the function and test for errors before fetching from aReplace.
Function GetReplacementUnits(strInputUnit As String)
Dim aOrig() As Variant
Dim aReplace() As Variant
aOrig = Array("CENTIMETRE", "KILOGRAM", "METER")
aReplace = Array("cm", "Kg", "m")
GetReplacementUnits = Application.Index(aReplace, Application.Match(UCase(strInputUnit), aOrig, 0), 1)
End Function
Use like so
GetReplacementUnits("CENTIMETRE") returns cm
So based on everyone's suggestions I made the following:
Function Unit(Req_Header As String) As String
Dim Func As String
Func = LCase$(AkeFind(Req_Header))
Dim Rem_Range As Range
Dim Replc_Range As Range
Dim a_Nr As Integer
Set Rem_Range = Sheets("Variables").Range("Rem_Table[Rem_Code]")
Set Replc_Range = Sheets("Variables").Range("Rem_Table[Replc]")
For Each a In Rem_Range
If Func = a Then
Unit = a.Offset(0, 1).Value
Exit For
Else
Unit = Func
End If
Next a
End Function
The Rem_Table is a table with the first column being each possible code, and the second column giving the appropriate term. In context this actually makes more sense to use rather than replacing parts of the code. It is also easier to add new variables that I might come across later.
Thanks for everyone's input!
I have a problem when I try to convert my Array into a comma-separated String. In the example below, I retrieve all the files in my email and send them to my server to add them. My server sends me back an ID that is stored in the Array. Then I try to convert this Array into a String.
Dim filesId(1 To 100) As String
Dim tmpString As String
Dim count As Integer
count = 0
Set db = Session.Currentdatabase
Set CurrentDocColl = db.Unprocesseddocuments
Set doc = CurrentDocColl.Getfirstdocument
While Not doc Is Nothing
Set item = doc.GETFIRSTITEM("Body")
If doc.HasEmbedded Then
ForAll attachment In item.EmbeddedObjects
jsonBody="value={""filename"":"""+attachment.Name+"""}"
Set http=session.CreateHTTPRequest()
http.preferstrings = True
Call http.SetHeaderField("ContentType","application/json")
ret = http.Post(url, jsonBody)
If IsNumeric(ret) Then
count = count + 1
filesId(count) = ret
Else
MessageBox ret
End If
End ForAll
End If
Set doc=CurrentDocColl.Getnextdocument(doc)
Wend
ForAll itemValue In filesId
If CStr(itemValue ) <> "" Then
If tmpString = "" Then
tmpString = itemValue
Else
tmpString = tmpString & "," & itemValue
End If
End If
End ForAll
MessageBox tmpString
The problem is that the final String contains only the first value of the array and not the next values.
Example with this Array: [3567,3568,3569,3570]
Desired result String: 3567,3568,3569,3570
Result received: 3567
I don't understand where this problem comes from, especially since it also doesn't work with the Join() and Implode() functions.
EDIT
Indeed after having looked in the debugger, we can see that my data are present in the Array but in a particular format because the quotes of the strings do not close. What can I do to fix this?
Thank you very much for your help
Your http post result contains line break at its end. That is why the string looks so "strange" in debugger. This resuls in the following tmpString:
"3267
,3268
,3269
,3270"
Messagebox is not able to show all line breaks... so it only shows the string until the first line break.
You need to remove line breaks from your string before concatenating:
Dim badChars(2) as String
Dim goodChars(2) as String
badChars(0) = Chr$(0)
badChars(1) = Chr$(10)
badChars(2) = Chr$(13)
...
filesId(count) = Replace( ret, badChars, goodChars )
As I do not know WHICH line break / carriage return is there in your string I replace the three most common ones with blank in above code... Might be another unprintable character in there that you have to get rid of, then you need to examine Right( ret , 1) and check, what is in there and replace that.
I don't see a dim statement for tmpString. If there is no dim, then it is a variant. If it is a variant, then your assignment tmpString = item is dynamically defining tmpString as a numeric type. That would cause your assignment tmpString = tmpString & "," & item to fail. Since you appear to be expecting numeric data for ret, and you are assigning that into the filesId array, and item is a value from filesId, you need to be using CStr(item) in both of your assignments.
I have a column where i pickup increasing numbering values, and their format is xx_yy
so the first is 1_0, second 1_1 and so forth, no we are at 23_31
I want to get the right side of the string, and i am already getting the left side correctly.
using
newActionId = Left(lastActionID, (Application.WorksheetFunction.Find("_", lastActionID, 1) - 1))
i wish to do the following, human writing below
nextSubid = entire stringvalue AFTER special character "_"
I tried just switching left to right, didnt go so well, do you have a suggestion?
You can use Split function to get the relevant text.
Syntax: Split(expression, [ delimiter, [ limit, [ compare ]]])
Option Explicit
Sub Sample()
Dim id As String
Dim beforeSplChr As String
Dim afterSplChr As String
id = "23_31"
beforeSplChr = Split(id, "_")(0)
afterSplChr = Split(id, "_")(1)
Debug.Print beforeSplChr
Debug.Print afterSplChr
End Sub
Another way
Debug.Print Left(id, (InStrRev(id, "_", -1) - 1)) '<~~ Left Part
Debug.Print Right(id, (InStrRev(id, "_", -1) - 1)) '<~~ Right Part
Even though Siddharth Rout has given what can probably be considered a better answer here, I felt that this was worth adding:
To get the second part of the string using your original method, you would want to use the Mid function in place of Left, rather than trying to use Right.
Mid(string, start, [ length ])
Returns length characters from string, starting at the start position
If length is omitted, then will return characters from the start position until the end of the string
newActionId = Mid(lastActionID, Application.WorksheetFunction.Find("_", lastActionID, 1) + 1)
Just for fun (Split is the way to go here), an alternative way using regular expressions:
Sub Test()
Dim str As String: str = "23_31"
With CreateObject("VBScript.RegExp")
.Global = True
.Pattern = "\d+"
Debug.Print .Execute(str)(0) 'Left Part
Debug.Print .Execute(str)(1) 'Right Part
End With
End Sub
Btw, as per my comment, your first value could also be achieved through:
Debug.Print Val(str)
Split function of string is very usefull for this type of query.
Like:
String s = "23_34";
String left = s.split("_")[0];
String right = s.split("_")[1];
Or you can also use combination of indexOf and substring method together.
String left = s.substring(0,s.indexOf('_')+1)
String right = s.substring(s.indexOf('_'));
I am newbie in programming and would like to ask if I could separate the string below. I am using Visual Basic. Basically I have two string below :
String 1 : gOldStr= TEDDYBEARBLACKCAT
String 2 = gNewStr= BLACKCATWHITECAT
I wanted to separated the string 2 by looking the exact value in String 1
so that I have String2 that is part of string 1 = BLACKCAT
String 2 that is new = WHITECAT
I have tried below script but it doesn't work all the time. Could suggest me the better logic? Thanks2
For i=1 to Len(gOldStr)
TempStr = Left$(gNewStr,i)
Ctr1 = InStr(gOldStr, TempStr)
gTemporary = Mid$(gOldStr,Ctr1)
gTemporary = Trim(gTemporary)
Ctr2 = StrComp(gOldStr, gTemporary)
If Ctr2=1 Then
gTemporary2 = Replace(gNewStr,gTemporary,"")
Exit For
End If
Next i
If the common part is always at the end of the first one and at the beginning of the 2nd one, you could look from the end, like this:
Dim strMatchedWord As String
For i=1 to Len(gOldStr)
If i>Len(gNewStr) Then Exit For 'We need this to avoid an error
Test1 = Right$(gOldStr, i)
Test2 = Left$(gNewStr, i)
If Test1 = Test2 Then
strMatchedWord = Test1 'Store your match is Test1
End If
Next
Debug.Print strMatchedWord 'Once the loop finishes it contains the longest match
I modified the code so that the loop does not exit until it went through the full string. This way it will get you the longest match by the end of the loop.