Searching a string for numbers including decimals in VBA - string

So I'm working on a project that has inputs from a fairly clunky database that I have zero control over what type of data it gives me. It basically gives me a string that has numbers in it including decimals.
"take 0.5 Tab by mouth 2 times daily."
Whenever it says tab I want to grab the number before tab and convert it to double format. I know how to use cdbl to convert it once I have the string "0.5" but how I get just that string is kind of difficult since InStr only searches left to right. My thought was to use InStr to find the space before the number that comes before the word "tab" but I'm having trouble figuring out how to code it. Any suggestions?

InStrRev searches right to left. Alternatively, you can use StrReverse and work with the output, but I would use VBScript.Regexp:
Dim text As String
text = "take 0.5 Tab by mouth 2 times daily"
Dim regex As Object
Set regex = CreateObject("VBScript.Regexp")
regex.Global = True
regex.Pattern = "[\d\.]+(?=\sTab)"
Dim test As Object
Set test = regex.Execute(text)
MsgBox (test(0).Value)

Update using Tab as relevant indicator
Assuming that Tab is the relevant indicator you could do the follwing:
Sub ExtractElement()
' column 4 and row 6 contains the text "take 0.5 Tab by mouth 2 times daily"
s = Cells(6, 4).Value
' split text into array for each space
sAr = Split(s, " ")
' iterate over each element of array
For i = 0 To UBound(sAr) - 1
' if the array element "Tab" is reached
If sAr(i) = "Tab" Then
' write the previous array element into the next column
Cells(6, 5).Value = sAr(i-1)
End If
Next
End Sub
Beware that each word is really seperated by a " ". I copied your text and noticed that "Tab by" was not seperated.
Sub ExtractCharCode()
s = Cells(7, 4).Value
For i = 1 To Len(s)
Cells(i, 8).Value = Mid(s, i, 1)
Cells(i, 9).Value = Asc(Mid(s, i, 1))
Next
End Sub
Update using a variation of the answer from user matzone
Instead of passing a range into the function from matzone i would only pass the Value and add a trim to it
Public Function TakeBeforeTab2(s As String) As String
s = Mid(s, 1, InStr(UCase(s), "TAB") - 1)
TakeBeforeTab2 = Trim(Mid(s, InStr(s, " ") + 1))
End Function

To get "0.5" from "take 0.5 Tab by mouth 2 times daily."
Public Function TakeBeforeTab(r As Range) As String
Dim s As String
s = r.Value
s = Mid(s, 1, InStr(UCase(s), "TAB") - 2)
TakeBeforeTab = Mid(s, InStr(s, " ") + 1)
End Function

Related

VBA how to find entire number in a string and set to variable

I have a list that was copied from a 'table of contents' page to column D. Unfortunately, each cell contains the chapter number, chapter name, and the page number.
3.14.4 chapter name placeholder.140
Sometimes there is a space between the page number and the last character. other times there is not.
I've tried
Function john(txt As String) As Long
Dim x
x = Split(Trim(txt), Chr(32))
john = Val(x(UBound(x)))
End Function
Which does work but I'd like to be able to apply this to the chapter number as well afterwards.
Private Sub FIND_LAST_NUMBER()
Dim A As String
Dim B As Integer
Dim C As String
Dim D As String
x = 3
Do While ActiveSheet.Cells(x, 4).Value <> ""
A = Range("D" & x).Value
A = Trim(A)
B = Len(A)
For Position = B To 1 Step -1
C = Mid(A, Position, 1)
'MsgBox C
If C <> " " Then
D = Right(A, B - Position)
Range("E" & x).Value = C
GoTo LastLine
'Exit Sub
End If
Next Position
LastLine:
x = x + 1
Loop
End Sub
but I'm trying to figure out how to get all of the number instead of only the last digit of the page number from the original cell
I am obviously not getting something here.
Any tips or tricks will be greatly appreciated
One, admittedly not very beautiful solution I can think of right away would be to use Replace to remove all non-numeric characters.
Dim str As String
str = Replace(str, " ", "") '<- to remove the random spaces
str = LCase(str) '<- making everything lower case
For i = 97 To 122
str = Replace(str, Chr(i), "")
Next i
Chr(i) with i from 97 to 122 will be every Character of the standard Alphabet.
This does not work if special Characters appear in the Chapter Name String. If the Chapter name contains numbers these will remain, but you could detect that case because UBound of the split array will be 1 greater than usual.
Also if you can quickly scan all the cells with your data for other unwanted Characters like - / or whatever might occur, you can also get rid of them with Replace
Performance of this solution might not be great but for a quick fix it might do..

How do I force the font color to remain the way it is rather than changing

My program basically highlights the keywords in text to the user choice font color.I have a problem only when displaying the search terms in the cell that is specified by the user not in actual execution of the program. I take the terms input, range/cell to display these terms, font color from the user. This is partial code:
Dim Ran As String
searchTerms = InputBox("Please enter words to search, if more than one string seperate by comma and space", "Need Input", 1)
Ran = InputBox("Please enter cell where you want the search terms to be displayed ideally below verbatim like C2,D2 ", "Need Input", 0, 8)
r = Range(Ran).Row
c = Range(Ran).Column
If IsEmpty(Cells(r, 1)) And c <> "A" Then
Range(Ran).Value = Range(Ran).Value & ", " & searchTerms
Else: Range(Ran).EntireRow.Insert
Range(Ran).Value = searchTerms
End If
searchTerms = Split(UCase(searchTerms), ", ")
This works as expected for 2 executions but for third time execution the previous execution font color changes to the 1st execution color. How do I force it to remain as it was before execution. Suppose after 1st time execution it was yellow, After Second execution the appended variables were green, Then when I am performing the third execution the whole cell content change to yellow before peforming the execution and the only the third execution terms will change to user defined font color. But the Second execution appended terms will be yellow as first execution color.
This is the code which is highlighting function:
Function HilightString(offSet As Integer, searchString As String, rowNum As Long, ingredCol As String, FontColor, fontSize As Integer) As Integer
Dim x As Integer
Dim newOffset As Integer
Dim targetString As Variant
If Cells(rowNum, ingredCol).HasFormula Then
Cells(rowNum, ingredCol).Value = "'" & Cells(rowNum, ingredCol).Formula
End If
targetString = Mid(Cells(rowNum, ingredCol), offSet)
foundPos = InStr(UCase(targetString), searchString)
If foundPos > 0 Then
Cells(rowNum, ingredCol).Characters(offSet + foundPos - 1, Len(searchString)).Font.ColorIndex = FontColor
Cells(rowNum, ingredCol).Characters(offSet + foundPos - 1, Len(searchString)).Font.Size = 14
newOffset = offSet + foundPos + Len(searchString)
x = HilightString(newOffset, searchString, rowNum, ingredCol, FontColor, fontSize)
Else
Exit Function
End If
End Function
First execution highlighted turn, signal in blue and created in cell c2:
Second execution highlighted Rear in green It works fine till this point
Before complete third execution highlighted blink
After 3rd execution. The second executed words turn back to blue
When you do this to add the new words:
Range(Ran).Value = Range(Ran).Value & ", " & searchTerms
you cannot preserve multiple colors of text - replacing the content will just give the whole cell the color of the first letter. That's OK for your first and second runs, but will fail from run 3 forward.
You need to add the new text using the Characters collection, not by replacing the whole cell content.
Example:
Sub Tester()
Dim c As Range
Set c = Range("A1")
AddTextWithColor c, "first", vbRed
AddTextWithColor c, "second", vbBlue
AddTextWithColor c, "third", vbGreen
End Sub
Sub AddTextWithColor(c As Range, txt As String, clr As Long)
Dim l As Long
With c
If Len(.Value) = 0 Then
.Value = txt
Else
l = .Characters.Count
'adds the new text without replacing existing formatting
.Characters(l + 1, Len(txt) + 2).Text = "," & txt
End If
With .Characters(IIf(l = 0, 1, l + 2), Len(txt)).Font
.Color = clr
.Size = 14
End With
End With
End Sub

Put symbols in between a string in Excel

I have a columns of strings as follows. How can I put the symbol '<' in between the characters ?
'ABCDE'
'BCG'
'ABCD'
The expected output should be:
A<B<C<D<E
B<C<G
A<B<C<D
=concatenate(left(A1,1),"<",mid(A1,2,1),"<",mid(A1,3,1),(if(len(A1)>3,"<"&mid(A1,4,1)&if(len(A1)>4,"<"&mid(A1,5,1),""),"")))
Will do what you want for values up to 5 letters, and as few as 3 letters. Otherwise you can change it.
Basically it adds a "<" between the first 3 letters and then checks whether the string is longer than 3 letters and if so, adds more "<" characters. If this needs to be more dynamic it's far easier in vba.
A manual, one-off, no-VBA approach would be:
use the Text to Columns tool with Fixed Width and place the markers after each character.
then use a formula like this to append values and separator
The formula could look like this if your values are in row 1
=A1&IF(LEN(B1)>0,">"&B1,"")&IF(LEN(C1)>0,">"&C1,"")&IF(LEN(D1)>0,">"&D1,"")&IF(LEN(E1)>0,">"&E1,"")
Adjust formula to suit the maximum number of characters in a cell.
Such things are not for formulas...
As you tag question as Excel-VBA too, so:
'''''''
Private Sub sb_Test_fp_AddSym()
Debug.Print fp_AddSym("abncd", "<")
End Sub
Public Function fp_AddSym(pStr$, pSym$) As String
Dim i&, j&, iLB&, iUBs&, iUBt&
Dim tSrc() As Byte, tTgt() As Byte, tSym As Byte
tSrc = pStr
tSym = Asc(pSym)
iLB = LBound(tSrc)
iUBs = UBound(tSrc)
iUBt = iUBs * 2 + 3
ReDim tTgt(iLB To iUBt)
For i = iLB To iUBs Step 2
j = i * 2
tTgt(j) = tSrc(i)
tTgt(j + 1) = tSrc(i + 1)
tTgt(j + 2) = tSym
tTgt(j + 3) = 0
Next
ReDim Preserve tTgt(iLB To (iUBt - 4))
Debug.Print tTgt
Stop
fp_AddSym = tTgt
End Function
'''
This worked for me:
Sub SymbolInsert()
Dim cl As Range, temp As String
For Each cl In Range("A1:A3") '~~~> Define your range here
For i = 1 To Len(cl)
temp = temp & Mid(cl, i, 1) & "<"
Next i
cl = IIf(VBA.Right$(temp, 1) = "<", VBA.Left$(temp, Len(temp) - 1), temp)
temp = vbNullString
Next cl
End Sub
It can probably be done with Excel formula for any length, but here is the shortest VBA solution
For Each c In Range("A:A").SpecialCells(xlCellTypeConstants)
c.Value2 = Replace( Left$( StrConv( c, vbUnicode), Len(c) * 2 - 1), vbNullChar, "<")
Next

Excel VBA - Delete Single Character from Cell without losing formatting of remainder of cell contents

I am trying to delete the first occurrence of "<" and ">" in a cell without losing formatting of the remainder of the cell's contents.
I have looked in several places here, and other, to no avail.
This is what I am trying to do:
Say "A1" contains the text:
"This is <a> long string with several <occurrences> of a <special> character."
In any case, What I am trying to do is remove the ">", and in a perfect world the "<", from the first word which contains them while maintaining the bold formatting as well as the "<" and ">" on the next word containing them.
This is ONLY other code executing prior to the code I am having issues with.
inTx = Range("A2").Value
outTx = Replace(inTx, "Init_Day", Range("A3").Value)
Range("A2").Value = outTx
Which replaces the <placeholder> text with the actual text, a two digit number in this case.
Here is the code that is not working for me:
SearchString = Range("A2").Value
Char1 = "<"
Char2 = ">"
For i = 1 To Len(SearchString)
If Mid(SearchString, i, 1) = Char1 Then
startPos = i
Exit For
End If
Next i
For i = 1 To Len(SearchString)
If Mid(SearchString, i, 1) = Char2 Then
endPos = i
Exit For
End If
Next i
Range("A2").Characters(startPos, endPos - startPos).Font.Bold = True
Range("A2").Characters(startPos - 1, 1).Delete
All code works fine until I reach the last line:
Range("A2").Characters(startPos - 1, 1).Delete
then nothing happens.
I've even tried:
Range("A2").Characters(startPos - 1, 20).Delete
Still nothing...
I know this should be easy but I can't seem to figure it out.
Thanks in advance.
The following code:
Sub Foo()
Const Char1 As String = "<", Char2 As String = ">"
Dim SearchString As String
Dim i As Integer, startPos As Integer, endPos As Integer
SearchString = Range("A2").Value
startPos = InStr(SearchString, Char1)
endPos = InStr(SearchString, Char2)
Range("A2").Characters(startPos, endPos - startPos).Font.Bold = True
Range("A2").Characters(startPos, 1).Delete
Range("A2").Characters(endPos - 1, 1).Delete
End Sub
Turns this:
Some <bold> text I just <made> up.
Into this: Some bold text I just <made> up.
Is that what you are looking for?

Parsing and comparing a complicated string

I am hoping someone could help me out with a VBA Excel macro.
I have received a worksheet in Excel 2007 which contains product names in one column, and I need to sort this into a logical format so I can use it. However, the list itself is not in any kind of logical order, is 10 000 rows long and I am going to have to do this every month!!
Basically, what I would like to do is search for certain keywords which are common to most of the entries and move them into separate cells in different columns (but in the same row as the original entry).
Regarding keywords: There are 3 different types, two of which I have a complete list of.
Example of keywords: some are measures such as cm (centimetre), mm (millimetre), m (metre) etc.). Then there are other keywords such as % and finally a last set of keywords which is wood, plastic, glass etc.
If this was not complicated enough, the measures (cm for example) are duplicated in some instances and are important details so I cant just separate them but would ideally like them in two adjacent cells.
Fortunately, there is a space after each measure, % sign and item material.
Working from right to left is the easiest way I can think of achieving this as the first description in the string varies wildly between entries and that can stay as is.
So, below is an example string, lets say this is in Cell A1. (Inverted commas are not included in the string and the word "by" appears in only about 100 cases. Usually it is missing...)
"Chair Leg Wood 100% 1m by 20cm"
I would ideally like for the string to be split up into cells as follows
Cell B1 - Chair Leg
Cell C1 - Wood
Cell D1 - 1m
Cell E1 - 2cm
Cell F1 - 100%
Having the % measures in the same column would be extremely helpful
Can anyone please help me with this or the beginnings of a macro which does this and then moves down the list - I have tried using some basic "find" and "len" formulas but really am at my wits end on how to deal with this!
The task boils down to defining a robust definition of the structure of the input data.
Form the info provided a candidate definition might be
<Description, one or more words> <Material, one word> <A value followd by %> <Dimension A> <optional "by"> <Dimension B>
The following macro will process data that conforms this this spec. The definition may need
expanding, eg two word materials (eg Mild Steel)
You will need to add error handling in case any rows don't conform, eg no % in the string, or % character elsewhere in string
Option Explicit
Dim dat As Variant
Sub ProcessData()
Dim r As Range
Dim i As Long
Set r = Intersect(ActiveSheet.UsedRange, ActiveSheet.Columns(1)).Resize(, 5)
dat = r
For i = 1 To UBound(dat, 1)
ParseRow i, CStr(dat(i, 1))
Next
r = dat
ActiveSheet.Columns(5).Style = "Percent"
End Sub
Sub ParseRow(rw As Long, s As String)
'Chair Leg Wood 100% 1m by 20cm
Dim i As Long
Dim sDim As String, sPCnt As String, sMat As String, sDesc As String
Dim sA As String, sB As String
i = InStr(s, "% ")
sDim = Trim(Replace(Mid(s, i + 2), " by ", " ")) ' text to right of %, remove "by"
sA = Trim(Left(sDim, InStr(sDim, " "))) ' split dimension string in two
sB = Trim(Mid(sDim, InStr(sDim, " ")))
s = Left(s, i)
i = InStrRev(s, " ")
sPCnt = Mid(s, i + 1) ' text back to first space before %
s = Trim(Left(s, i))
i = InStrRev(s, " ") ' last word in string
sMat = Mid(s, i + 1)
sDesc = Trim(Left(s, i)) ' whats left
dat(rw, 1) = sDesc
dat(rw, 2) = sMat
dat(rw, 3) = sA
dat(rw, 4) = sB
dat(rw, 5) = sPCnt
End Sub
First, I'd use the Split function to separate the parts into an array, this will avoid most of the string functions and string math:
Dim parts As Variant
parts = Split(A1)
Then, I'd do my comparisons to each part.
Finally, I'd concatenate the parts I didn't breakout, and place all parts on the sheet.
This is based on your example which has spaces inbetween every part, though something similar could work otherwise, you just have to do more work with each part.
Here's my stab at it. We could use about 10 more examples, but this should be a start. To use, select a one column range with your descriptions and run SplitProduct. It will split it out to the right of each cell.
Sub SplitProducts()
Dim rCell As Range
Dim vaSplit As Variant
Dim i As Long
Dim aOutput() As Variant
Dim lCnt As Long
Const lCOLDESC As Long = 1
Const lCOLMAT As Long = 2
Const lCOLPCT As Long = 3
Const lCOLREM As Long = 4
If TypeName(Selection) = "Range" Then
If Selection.Columns.Count = 1 Then
For Each rCell In Selection.Cells
'split into words
vaSplit = Split(rCell.Value, Space(1))
ReDim aOutput(1 To 1, 1 To 1)
'loop through the words
For i = LBound(vaSplit) To UBound(vaSplit)
Select Case True
Case IsPercent(vaSplit(i))
'percents always go in the same column
lCnt = lCOLPCT
If UBound(aOutput, 2) < lCnt Then
ReDim Preserve aOutput(1 To 1, 1 To lCnt)
End If
aOutput(1, lCnt) = vaSplit(i)
Case IsInList(vaSplit(i))
'list items always go in the same column
lCnt = lCOLMAT
ReDim Preserve aOutput(1 To 1, 1 To lCnt)
If UBound(aOutput, 2) < lCnt Then
ReDim Preserve aOutput(1 To 1, 1 To lCnt)
End If
aOutput(1, lCnt) = vaSplit(i)
Case IsMeasure(vaSplit(i))
'measurements go in the last column(s)
If UBound(aOutput, 2) < lCOLREM Then
lCnt = lCOLREM
Else
lCnt = UBound(aOutput, 2) + 1
End If
ReDim Preserve aOutput(1 To 1, 1 To lCnt)
aOutput(1, lCnt) = vaSplit(i)
Case Else
'everything else gets concatentated in the desc column
aOutput(1, lCOLDESC) = aOutput(1, lCOLDESC) & " " & vaSplit(i)
End Select
Next i
'remove any extraneous spaces
aOutput(1, lCOLDESC) = Trim(aOutput(1, lCOLDESC))
'write the values to the left of the input range
rCell.Offset(0, 1).Resize(1, UBound(aOutput, 2)).Value = aOutput
Next rCell
Else
MsgBox "Select a one column range"
End If
End If
End Sub
Function IsPercent(ByVal sInput As String) As Boolean
IsPercent = Right$(sInput, 1) = "%"
End Function
Function IsInList(ByVal sInput As String) As Boolean
Dim vaList As Variant
Dim vaTest As Variant
'add list items as needed
vaList = Array("Wood", "Glass", "Plastic")
vaTest = Filter(vaList, sInput)
IsInList = UBound(vaTest) > -1
End Function
Function IsMeasure(ByVal sInput As String) As Boolean
Dim vaMeas As Variant
Dim i As Long
'add measurements as needed
vaMeas = Array("mm", "cm", "m")
For i = LBound(vaMeas) To UBound(vaMeas)
'any number of characters that end in a number and a measurement
If sInput Like "*#" & vaMeas(i) Then
IsMeasure = True
Exit For
End If
Next i
End Function
No guarantees that this will be speedy on 10k rows.

Resources