Is there an efficient way to combine numerous subtitutions and replacements? - excel

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!

Related

Lotusscript: Problem to convert an Array into a String

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.

How do i get part of string after a special character?

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('_'));

Manipulates String with VBA

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.

Replace a string with a format string

I have the following input:
Dim str_format as string = "XXXXX00000"
Dim str as string = "INV"
Dim int as integer = "56"
How can I replace XXXXX with INV and replace 00000 with 56?
For the example above the result should be INVXX00056.
X can only replace with alphabet and 0 can only replace with integer, if str has more than five alphabet. The extra alphabets will be thrown away because str_format only has five X. The same algorithm is true for the integer.
Example 2
Dim str_format as string = "XXX00000"
Dim str as string = "ABCD"
Dim int as integer = 654321
Expected result: ABC54321
Process:
1. ABCD XXX00000 654321
2. ABC DXX000006 54321
3. AB CDX00065 4321
4. A BCD00654 321
5. ABC06543 21
6. ABC65432 1
7. ABC54321
As Spidey mentioned... show some code. That said the process you describe is a bit long-winded.
The Letter part of the solution can be done by grabbing the first 3 characters of str using Left(str,3) this will bring in the leftmost 3 character (if there are less it will get what is there). Then check that you have 3 characters using str.Length(). If the length is less than 3 then append the appropriate number of 'X'.
The Numeric part can be done in a similar way. Your int is actually a string in your code above. If it was a real integer you can cast it to string. Use Right(int,5). Again check to see you have 5 digits and if not prepend with appropriate number of 0.
Have a go... if you run into problems post your code and someone is bound to help.
UPDATE
As there have been actual answers posted here is my solution
Function FormatMyString(str As String, num as String) As String
Dim result As String
result = Left(str,3).PadRight(3, "X"c).ToUpper() & Right(num,5).PadLeft(5, "0"c)
Return result
End Function
UPDATE 2
based on Wiktors answer... made an amendment to my solution to cope with different formats
Function FormatMyString(str As String, num as String, alpha as Integer, digits as Integer) As String
Dim result As String
result = Left(str, alpha).PadRight(alpha, "X"c).ToUpper() & Right(num, digits).PadLeft(digits, "0"c)
Return result
End Function
To use...
FormatMyString("ABCDE", "56",3 5) will return ABC00056
FormatMyString("ABCDE", "123456",4 3) will return ABCD456
FormatMyString("AB", "123456",4 3) will return ABXX456
Here is a possible solution that just uses basic string methods and PadLeft/PadRight and a specific method to count occurrences of specific chars in the string. It assumes the format string can only contain X and 0 in the known order.
Public Function CountCharacter(ByVal value As String, ByVal ch As Char) As Integer
Return value.Count(Function(c As Char) c = ch)
End Function
Public Sub run1()
Dim str_format As String = "XXXXX00000" '"XXX00000"
Dim str As String = "INV"
Dim int As Integer = 56 ' ABC54321
Dim xCnt As Integer = CountCharacter(str_format, "X")
Dim zCnt As Integer = CountCharacter(str_format, "0")
Dim result As String
If xCnt > str.Length Then
result = str.PadRight(xCnt, "X")
Else
result = str.Substring(0, xCnt)
End If
If zCnt > int.ToString().Length Then
result = result & int.ToString().PadLeft(zCnt, "0")
Else
result = result & int.ToString().Substring(int.ToString().Length-zCnt
End If
Console.WriteLine(result)
End Sub
Output for your both scenarios is as expected.
Take a look at this sample
Dim str_format As String = str_format.Replace("XXX", "ABC")
Msgbox(str_format )
As we assume that the X is 3 only. I dont want to give you more it is a start and everything will be easy.
If that kind of format is fix I mean the number of X will go or down then you can make a conditional statement based on the length of string

Building Variable Names with Concatenate

I have a function that takes optional arguments in pairs: firstRange_1, secondRange_2; firstRange_2, secondRange_2; etc.
For each optional argument I need to execute a series of statements if the argument is passed to the function.
For example
dim firstRange_1 as range
dim secondRange_1 as range
dim firstRange_2 as range
dim secondRange_2 as range
etc.
dim firstCell_1 as string
dim lastCell_1 as string
dim firstCell_2 as string
dim lastCell_2 as string
etc.
If IsMissing(firstRange_1) = False Then
firstCell_1 = secondRange_1.Cells(1,1).Address
lastCell_1 = secondRange_1.Cells(secondRange_1.Rows.Count, secondRange_1.Rows.Count)
End if
if IsMissing(firstRange_2) = False Then
firstCell_2 = secondRange_2.Cells(1,1).Address
lastCell_2 = secondRange_2.Cells(secondRange_2.Rows.Count, secondRange_2.Rows.Count)
End If
Is it possible to "build" (sorry if the terminology isn't correct, I'm not yet experienced in programming or vba) the variables on the fly?
for example a loop like
For n=1 to 100
If IsMissing(firstRange_ & "n") = False Then
firstCell_ & "n" = secondRange_ & "n".Cells(1,1).Address
lastCell_ & "n" = secondRange_ & "n".Cells(secondRange_ & "n".Rows.Count, secondRange_ & "n".Rows.Count)
End If
Next
Edit:
See my comments to Branislav Kollár for updates.
I think what you need to rewrite the function to use ParamArrays (see the "Using an Indefinite Number of Arguments" section). Something like this:
myFunction(ParamArray userRanges()) As Range'or whatever Data Types you need
This way, you could use the LBound and UBound functions to see how many arguments were passed into function, leaving the necessity to check if they are missing.
For example you can create a 2 new arrays inside the function (not the argument array) for determining the first and last cells of each argument range. This is not the only way, you can use 2D arrays or put everything into one array. This is just one way.
Function myFunction(ParamArray userRanges()) As Range
Dim firstCell() As Range
Dim lastCell() As Range
ReDim firstCell(UBound(userRanges))
ReDim lastCell(UBound(userRanges))
For x = 0 To UBound(userRanges)
Set firstCell(x) = userRanges(x).Range("A1")
Set lastCell(x) = firstCell_1(x).Offset(userRanges(x).Rows.Count - 1, userRanges(x).Columns.Count - 1)
Next x
'other code to actually do something with the cells
'...
End Function
Try this, if you have any trouble, please let us know.
One more link to learn about this Understanding the ParamArray
Edit 1
Based on comment from OP, I rewritten the code, so that now each input range userRanges will have firstCell and lastCell stored in appropriate arrays. I didn't realize the limitation of my previous post before.
The only think to keed in mind now, is that the index 0 is first range; 1 is second range; 2 is third range; etc.
Or you can use Option Base 1 to make it more naturally indexed, but that is not recommended for some reason.
You can't dynamically name variables, but you can use Arrays. They are stupid powerful, so it's worth learning about them.
Essentially you will make 2 arrays. One for your input (variable1_n) and one of your outputs (output_1_n).
Dim inputArray(1 to 100) as String 'or whatever type these are supposed to be
Dim outputArray(1 to 100) as Range 'perhaps these are ranges?
For i = 1 to 100
Set outputArray(i) = function(inputArray(i))
Next i
Now you have an array full of ranges!

Resources