Extract numbers from comment and add them - excel

As the title suggests I am looking for a way to retrieve all the numbers from a cell comment and add them up. The only way I can think to do this would be to retrieve the comment as a string, assign each set of numbers to a variable, then add up the variables?
I am having a hard time with the logic, I don't know a way to retrieve the numbers out of a comment.
So far I have:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim varComment As String
For i = 19 To 30
If Not Intersect(Target, Range("N19:N30")) Is Nothing Then
On Error Resume Next
varComment = Cells(Ni).Comment.Text
Next i
End If
End Sub
The use is that I have a comment in cells N19:N30 that contains dollar values, "Food - $20, Gas - $40, etc..." I want the cell value to be updated anytime a new listing is made to reflect the total cost. Make sense?

Without making any assumptions on the numbers I would extract the numbers with a regex expression and then sum them up. I used a function found here and modified it slightly.
Function CleanString(strIn As String) As String
Dim objRegex
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Global = True
'.Pattern = "[^\d]+"
.Pattern = "[^0-9" & Application.DecimalSeparator & "]"
CleanString = .Replace(strIn, vbCrLf)
End With
End Function
With this function you can then add up the numbers in a comment
Function commentSum(cmt As Comment) As Double
Dim vDat As Variant
Dim i As Long
Dim res As Double
vDat = Split(CleanString(cmt.Text), vbCrLf)
For i = LBound(vDat) To UBound(vDat)
If Len(vDat(i)) > 0 Then
res = res + CDbl(vDat(i))
End If
Next i
commentSum = res
End Function
For testing purposes
Sub TestCmtAdd()
Dim rg As Range
Dim sngCell As Range
Set rg = Range("A1:A10")
For Each sngCell In rg
If Not (sngCell.Comment Is Nothing) Then
MsgBox "Sum of numbers in comment of cell: " & sngCell.Address & " is " & commentSum(sngCell.Comment)
End If
Next
End Sub

My below code is working under the following assumption:-
-
Each Number MUST be start with "$" (spaces between $ and the Number will be trimed)
Each Number MUST end with "," (spaces between "," and the Number will be trimed)
Your "varComment" is already populated
Note: Split the comment with "vbCrLf" did not work with me
Dim SplitedComment() As String
Dim tmpStr As Variant
Dim DolarSignLoc, yourSum As Integer
' For Each Comment, Do the following
SplitedComment() = Split(varComment, ",") ' Split the Comment by ",", we'll need ONLY the output that Contain "$" ( some of the output may NOT contain that char)
yourSum = 0 ' initialize your Sum Variable
For Each tmpStr In SplitedComment ' for each Text in the SplittedComment
DolarSignLoc = InStr(tmpStr, "$") ' Get the Location of the "$" ( ZERO if not exist)
If DolarSignLoc > 0 Then ' ONLY Process the Text if contains "$"
tmpStr = Right(tmpStr, Len(tmpStr) - DolarSignLoc) ' Excetract your Number
yourSum = yourSum + CInt(Trim(tmpStr)) ' Add to your Summation
End If
Next

Related

How to color a specific line (condition is present) of a comment in Excel?

Dear experts in Excel and VBA!
Could you tell me how you can color a certain line (condition - the presence of a certain word) in a Comments?
Comment consists of several lines, separated by Chr (10).
Example in picture1:
the comment has 4 lines, the second line contains the word "VBA", so this line should be highlighted in red.
The main problem is that the test word "VBA" can be in any line, there can be from 1 to 10+ lines.
I assumed that:
can move data from comment to cell
replace Chr (10) with some character, for example, "_"
distribute the text of the cell into columns through the "column distribution wizard"
search for the desired word "VBA" in the received cells
determine the cell number and understand that this is the number of the required line in the comment
based on the cell number, paint over the line number in the comment
Can you please tell me if my action logic is correct? Am I heading in the right direction?
If so, what is the correct way to carry out points 4-6?
enter image description here
would this help?
"test" is the codename for the sheet I have set, change it according to your situation.
"i" will give you the line number, starting from 0. So in your example it would be 1.
Edit: Added Exit For in the if check.
Option Explicit
Sub test_note()
Dim strNote As String
Dim arrNote As Variant
Dim number_of_lines As Integer
strNote = test.Range("A5").NoteText
number_of_lines = Len(strNote) - Len(Replace(strNote, Chr(10), "")) + 1
ReDim arrNote(1 To number_of_lines) As String
arrNote = Split(strNote, Chr(10))
Dim i As Long
For i = LBound(arrNote) To UBound(arrNote)
If InStr(arrNote(i), "VBA") > 0 Then
Debug.Print i, arrNote(i)
Exit For 'If you are sure there won't be any other occurrence of VBA in there, why check the rest of the lines? Speeds code depending on circumstance.
End If
Next i
End Sub
Edit 2: Revised code to change the color of the comment line.
Sub test_note()
Dim strNote As String
Dim arrNote As Variant
Dim number_of_lines As Integer
strNote = test.Range("B5").NoteText
number_of_lines = Len(strNote) - Len(Replace(strNote, Chr(10), "")) + 1
ReDim arrNote(1 To number_of_lines) As String
arrNote = Split(strNote, Chr(10))
Dim i As Long
Dim startPos As Integer
Dim number_of_chars As Integer
startPos = 1
' Reset comment font color
test.Range("B5").Comment.Shape.TextFrame.Characters.Font.Color = 0
For i = LBound(arrNote) To UBound(arrNote)
If InStr(arrNote(i), "VBA") > 0 Then
number_of_chars = Len(arrNote(i))
test.Range("B5").Comment.Shape.TextFrame.Characters(startPos, number_of_chars).Font.Color = vbRed
Debug.Print i, arrNote(i), "startPos: " & startPos, "numChars: " & number_of_chars
Else
startPos = startPos + Len(arrNote(i)) + 1
End If
Next i
End Sub
Check this. Just running this VBA copies your comments to the cells
and highlights the lines containing "VBA", however, it does this for
all comments on all sheets
credit: https://martinbosanacvba.blogspot.com/2021/08/copying-comments-to-cells-and.html
Sub Demo()
Dim tnahqb1 As Range
Dim tnahqb2 As Range
Dim tnahqb3 As Workbook
Dim tnahqb4 As Worksheet
Dim tnahqb5 As Variant
Dim tnahqb6 As Integer
Dim tnahqb7 As Integer
Dim tnahqb8 As Integer
Dim tnahqb9 As Integer
For Each tnahqb10 In ActiveWorkbook.Worksheets
Set tnahqb1 = tnahqb10.Cells.SpecialCells(xlCellTypeComments)
If tnahqb1 Is Nothing Then
MsgBox "No comments in the sheet"
Else
For Each cell In tnahqb1
cell.Value = cell.Comment.Text
tnahqb5 = Split(cell.Comment.Text, Chr(10))
tnahqb6 = UBound(tnahqb5) - LBound(tnahqb5) + 1
For I = LBound(tnahqb5) To UBound(tnahqb5)
If InStr(tnahqb5(I), "VBA") > 0 Then
tnahqb8 = Len(tnahqb5(I))
With cell
tnahqb7 = InStr(cell.Comment.Text, tnahqb5(I))
tnahqb9 = tnahqb7 + tnahqb8
.Characters(tnahqb7, tnahqb8).Font.Color = vbRed
End With
End If
Next I
Next cell
End If
Next tnahqb10
End Sub

Excel Macro to force upper case and remove special characters with button click

I have 2 codes but only one is working in VBA. I have
Private Sub FINALIZEBTN_Click()
Dim response As VbMsgBoxResult
response = MsgBox("HAVE YOU COMPLETED THE FORM IN FULL?", vbYesNo)
If response = vbYes Then
MsgBox "DO NOT FORGET TO SAVE AND SUBMIT THIS FORM"
Else
If response = vbNo Then
MsgBox "PLEASE REVIEW AND COMPLETE THE FORM IN FULL"
Exit Sub
End If
End If
Dim cell As Range
For Each cell In Range("$A$1:" & Range("$A$1").SpecialCells(xlLastCell).Address)
If Len(cell) > 0 Then cell = UCase(cell)
Next cell
Application.ScreenUpdating = True
End Sub
So on the click you get a yes/no prompt then it forces uppercase throughout the whole sheet.
The only symbols we are allowing are '&' and '-'
I would either like another box to pop up when a special character is entered telling them something like "hey you can't do this" or when a special character is found to remove it and just remove it with nothing. If we could get it to remove and replace the latin letters with the acutes (like for spanish) that would also great. Currently I don't see any changes when I save or run the macros with the code in module 1.
I have the following code in Module 1
Function removeSpecial(sInput As String) As String
Dim sSpecialChars As String
Dim i As Long
sSpecialChars = "\/:*?""<>|$,.`"
For i = 1 To Len(sSpecialChars)
sInput = Replace$(sInput, Mid$(sSpecialChars, i, 1), "")
Next
removeSpecial = sInput
End Function
As others have said, you need to call the removeSpecial.
That said, I would rewrite removeSpecial to specify characters you want to keep, as there are many more special characters than what you have listed in removeSpecial
Other changes
Use SpecialCells xlCellTypeConstants to only loop over cells containing values (removes need to test Len and excludes formulas).
Account for possibility sheet has no constant values
Added substitution of accented characters: you will need to extend the ReplaceFrom and ReplaceWith strings to include all replacements you want (make sure these two strings are the same length)
You may (or may not) want to include other characters in the inclusion, eg space, or other punctuation? If so, add them to the sKeepChars Like pattern (leave the - as the first character inside then []
ALL CAPS messages are ugly!
Function removeSpecial(sInput As String) As String
Dim sKeepChars As String
Dim sClean As String
Dim c As String
Dim i As Long, j As Long
Const ReplaceFrom As String = "AE"
Const ReplaceWith As String = "ÀÊ"
sKeepChars = "[-&A-Z" & ReplaceWith & "]"
For i = 1 To Len(sInput)
c = Mid$(sInput, i, 1)
If c Like sKeepChars Then
j = InStr(ReplaceFrom, c)
If j Then
c = Mid$(ReplaceWith, j, 1)
End If
sClean = sClean & c
End If
Next
removeSpecial = sClean
End Function
Private Sub FINALIZEBTN_Click()
Dim response As VbMsgBoxResult
response = MsgBox("Have you completed the form in full?", vbYesNo)
If response = vbYes Then
MsgBox "Do not forget to save and submit this form"
ElseIf response = vbNo Then
MsgBox "Please review and complete the form in full"
Exit Sub
End If
Application.ScreenUpdating = False
Dim cell As Range
Dim rng As Range
With ActiveSheet
On Error Resume Next
Set rng = .Cells.SpecialCells(xlCellTypeConstants)
On Error GoTo 0
If Not rng Is Nothing Then
For Each cell In rng
cell = removeSpecial(UCase(cell))
Next cell
End If
End With
Application.ScreenUpdating = True
End Sub
This should work fine:
Dim MyStr As String
For Each cell In Range("$A$1:" & Range("$A$1").SpecialCells(xlLastCell).Address)
If Len(cell) > 0 Then
MyStr = cell
cell = UCase(removeSpecial(MyStr))
End If
Next cell

Search and replace with wildcards in Excel VBA

I use comma as decimal separator, but sometimes I receive files where values are below a set limit, and then the file uses point as "<2.5". Sometimes there is one digit before the decimal separator, and sometimes there are two digits.
I need to be able to replace the point with a comma in cells with begin with the "less than" symbol, but retain the actual numbers, so that "<2.5" is replaced with "<2,5" and "<10.0" is replaced with "<10,0". This needs to be done in Excel VBA.
I can't do a general search for "." and replace with ",", since there are places where I need to keep the point as it is.
Anyone have an idea of how to achieve this?
Approach via Replace function
You could read in data to a datafield array, replace the mentioned "<" data via Replace function and write them back in one statement by the following code. - Of course it's possible to use RegEx, too as mentioned in above comment.
Notes
a) I assume you are using data in column A:A via Set rng = ws.Range("A1:A" & n); this can easily changed to any other range.
b) Assigning values to a variant datafield array automatically creates a one based 2-dim array, which you address in case of one column only e.g. via v(1,1), v(2,1), v(3,1) etc. to v(n,1).
Example Code
Option Explicit
Sub replaceLowerThan()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("MySheet") ' << Change to your sheet name
Dim n As Long, i As Long
Dim rng As Range
Dim v
' get last row number and define data range
n = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Set rng = ws.Range("A1:A" & n)
' write data to 1-based 2-dim datafield array
v = rng.Value2
' replace "<..." values
For i = 1 To n
If Not IsError(v(i, 1)) Then ' omit cells with errors like #DIV/0!
If v(i, 1) Like "<*" Then v(i, 1) = Replace(v(i, 1), ".", ",")
End If
Next i
' write values back
rng.Value2 = v
End Sub
This worked:
Dim strPattern As String: strPattern = "(<[0-9]+)[\.]"
Dim strReplace As String: strReplace = "$1,"
Dim myreplace As Long
Dim strInput As String
Dim Myrange As Range
Set RegEx = CreateObject("VBScript.RegExp")
Set Myrange = ActiveSheet.UsedRange
For Each cell In Myrange
If strPattern <> "" Then
strInput = cell.Value
With RegEx
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = strPattern
End With
If RegEx.Test(strInput) Then
cell.Value = (RegEx.Replace(strInput, strReplace))
End If
End If
Next

How to extract only cells which contain a letter AND a number in Excel?

I have a series of addresses from which I need to extract postal codes.The data is very sloppily formatted (not separated, some with spacings some without etc..) meaning the only way I can think to extract the postcodes is to create a column to which is added only the values which contain Text and a Number as these are the only cells to contain the postal code.
The data is too messy to isolate exactly where the postcode lies but I would like something to return a result like above.
How could I return only cell O2 & P2 from the range K2:R2?
*Address here is made up
Though I believe that #DarrenBartrup-Cook has a better answer. This quick dirty little UDF will do it bassed on the mix of numbers and text like asked.
Function pcode(rng As Range)
Dim rngt As Range
Dim chr As String
Dim i As Integer
For Each rngt In rng
If Not IsNumeric(rngt) Then
For i = 1 To Len(rngt)
If IsNumeric(Mid(rngt, i, 1)) Then
pcode = Trim(pcode & " " & rngt.Value)
Exit For
End If
Next i
End If
Next rngt
End Function
Put this in a module attached to the workbook, NOT the worksheet code or ThisWorkbook code.
You would call it from the sheet with this formula:
=pcode(I5:P5)
For a VBA result you could use the code below.
In cell T2 enter =GetPostCode(K2:R2),
or in VBA you can use Debug.Print GetPostCode(Sheet1.Range("K2:N2"))
I can't remember where I got the pattern from, but can probably be improved.
Public Function GetPostCode(AddressRange As Range) As Variant
Dim rCell As Range
Dim sAddressString As String
For Each rCell In AddressRange
sAddressString = sAddressString & " " & rCell.Value
Next rCell
sAddressString = Trim(sAddressString)
GetPostCode = ValidatePostCode(sAddressString)
End Function
Public Function ValidatePostCode(strData As String) As Variant
Dim RE As Object, REMatches As Object
Dim UKPostCode As String
'Pattern could probably be improved.
UKPostCode = "(?:(?:A[BL]|B[ABDHLNRST]?|C[ABFHMORTVW]|D[ADEGHLNTY]|E[CHNX]?|F[KY]|G[LUY]?|" _
& "H[ADGPRSUX]|I[GMPV]|JE|K[ATWY]|L[ADELNSU]?|M[EKL]?|N[EGNPRW]?|O[LX]|P[AEHLOR]|R[GHM]|S[AEGKLMNOPRSTWY]?|" _
& "T[ADFNQRSW]|UB|W[ACDFNRSV]?|YO|ZE)\d(?:\d|[A-Z])? \d[A-Z]{2})"
Set RE = CreateObject("VBScript.RegExp")
With RE
.MultiLine = False
.Global = False
.IgnoreCase = True
.Pattern = UKPostCode
End With
Set REMatches = RE.Execute(strData)
If REMatches.Count = 0 Then
ValidatePostCode = CVErr(xlErrValue)
Else
ValidatePostCode = REMatches(0)
End If
End Function
Edit: I thought it wasn't working as it only return E17 3RU which is in Walthamstow, but HE17 3RU isn't a valid postcode (http://www.royalmail.com/find-a-postcode) so it found the valid one.

Changing the date format to yyyy-mm-dd

I've got a date column that contains dates in mixed format. For example:
A
21.03.1990
03/21/1990
So, basically there are two different formats in one column: dd.mm.yyyy and mm/dd/yyyy. I'm trying to write a VBA script to change format of all dates in the column to be yyyy-mm-dd. That's what I've got so far:
Sub changeFormat()
Dim rLastCell As Range
Dim cell As Range, i As Long
Dim LValue As String
i = 1
With ActiveWorkbook.Worksheets("Sheet1")
Set rLastCell = .Range("A65536").End(xlUp)
On Error Resume Next
For Each cell In .Range("A1:A" & rLastCell.Row)
LValue = Format(cell.Value, "yyyy-mm-dd")
.Range("B" & i).Value = LValue
i = i + 1
Next cell
On Error GoTo 0
End With
End Sub
I know that it's not elegant piece of code, but I'm beginner with VBA so please forgive me.
The problem with that code is that it just rewrite unchanged A column into column B, when I change argument in Format function from yyyy-mm-dd to dd/mm/yyyy it works, but only for dates in format mm/dd/yyyy, and leaves dd.mm.yyyy untouched. I would appreciate any advice.
UPDATED: NEW ANSWER
Here is a solution that will do the job! The sub routine includes a function that does the replacement (the function itself is really useful!). Run the sub and all occurances in column A will be fixed.
Sub FixDates()
Dim cell As range
Dim lastRow As Long
lastRow = range("A" & Rows.count).End(xlUp).Row
For Each cell In range("A1:A" & lastRow)
If InStr(cell.Value, ".") <> 0 Then
cell.Value = RegexReplace(cell.Value, _
"(\d{2})\.(\d{2})\.(\d{4})", "$3-$2-$1")
End If
If InStr(cell.Value, "/") <> 0 Then
cell.Value = RegexReplace(cell.Value, _
"(\d{2})/(\d{2})/(\d{4})", "$3-$1-$2")
End If
cell.NumberFormat = "yyyy-mm-d;#"
Next
End Sub
Place this function in the same module:
Function RegexReplace(ByVal text As String, _
ByVal replace_what As String, _
ByVal replace_with As String) As String
Dim RE As Object
Set RE = CreateObject("vbscript.regexp")
RE.pattern = replace_what
RE.Global = True
RegexReplace = RE.Replace(text, replace_with)
End Function
How it works: I have a nifty RegexReplace function that allows you to do replace using regular expressions. The sub mearly loops through your A column and does a regex replace for those 2 cases you mentioned. The reason I use an Instr() first is to determain if it needs the replacement, and which kind. You could technically skip this but doing replace on cells that don't need it is really costly. At the end I format the cell to your custom date format regardless of what's inside for safe measure.
In case you aren't familiar with Regex (for ref: http://www.regular-expressions.info/), the expression I am using is:
Each item in () are capture groups - aka, the stuff you want to mess with
\d stands for a number [0-9].
{2} means 2 of, and {4} mean 4 of. I have been explicit here for safety.
The \ before the . in the first replace is needed since "." has special meaning.
In VBA regex, you refer to capture groups by using $ + no. of group. This is how I flip the order of the 3 items.
You don't really need VBA for this. This one-liner worksheet formula will do the trick:
=IF(ISERROR(FIND(".",A1)),IF(ISERROR(FIND("/",A1)),"invalid format",
DATE(RIGHT(A1,4),LEFT(A1,2),MID(A1,4,2))),
DATE(RIGHT(A1,4),MID(A1,4,2),LEFT(A1,2)))
This assumes the day and month are always given as two-digit numbers (e.g. always 03 and never just 3) and the year has four digits (i.e. "restricted" to years 1000-9999). But if this is not the case for you, then the formula can easily be adjusted to suit your purpose.
See if this does what you want. You may have to tailor it a bit for your own application.
Hope this helps!
Sub convertDates()
Dim rRng As Range
Dim rCell As Range
Dim sDest As String
Dim sYear, sMonth, sDay, aDate
'Range where the dates are stored, excluding header
Set rRng = Sheet1.Range("A2:A11")
'Column name of destination
sDest = "B"
'You could also use the following, and just select the range.
'Set rRng = Application.selection
For Each rCell In rRng.Cells
sYear = 99999
If InStr(rCell.Value, ".") > 0 Then
aDate = Split(rCell.Value, ".")
If UBound(aDate) = 2 Then
sDay = aDate(0)
sMonth = aDate(1)
sYear = aDate(2)
End If
ElseIf InStr(rCell.Value, "/") > 0 Then
aDate = Split(rCell.Value, "/")
If UBound(aDate) = 2 Then
sDay = aDate(1)
sMonth = aDate(0)
sYear = aDate(2)
End If
End If
With rCell.Range(sDest & "1")
If sYear <> 99999 Then
On Error Resume Next
.Value = "'" & Format(CDate(sMonth & "/" & sDay & "/" & sYear), "YYYY-MM-DD")
'If it can't convert the date, just put the original value in the dest
'cell. You can tailor this to your own preference.
If Err.Number <> 0 Then .Value = rCell.Value
On Error GoTo 0
Else
.Value = rCell.Value
End If
End With
Next
End Sub
#Jean-François Corbett has a formula solution that works but that might be shortened by more than half by foregoing the error message (on the basis that #VALUE! is as informative) and another IF, both DATEs, applying IFERROR rather than ISERROR, and SUBSTITUTE in place of one LEFT, MID, RIGHT set:
=IFERROR(1*(MID(A1,4,3)&LEFT(A1,3)&RIGHT(A1,4)),1*SUBSTITUTE(A1,".","/"))
This applies the interpretation mentioned by #Issun in the comment to the OP and assumes the output will be to cells formatted yyyy-mm-dd.
May be written with a subroutine like so:
Sub Macro1()
Range("B1:B10").Formula = "=IFERROR(1*(MID(A1,4,3)&LEFT(A1,3)&RIGHT(A1,4)),1*SUBSTITUTE(A1,""."",""/""))"
End Sub
Here is a simple solution to this:
Sub ChangeFormat1()
Dim ws as Worksheet
Set ws = Thisworkbook.Sheets("Sheet1")
LR = ws.cells(rows.count,1).End(Xlup).Row
with ws
For I = 1 to LR
.cells(I,2).value = .cells(I,1).value
Next
End with
ws.range("B1:B" & LR).numberformat = "yyyy-mm-dd"
End Sub
Month(Date) & "-" & Day(Date) & "-" & Year(Date)

Resources