superscript letters in vba string variable - excel

I am looking for how to super/subscript a letter/digit in a VBA string variable. I am working in excel with charts that have axes, titles and chart titles that require s-scripting. Additionally, there is a formula to go in a textbox:
Cpt = Cp0 * e^(-ket) where all the p's, t's and 0 are subscripts. The entire expression, (-ket) is superscripted with embedded subscripting for the e (the e between the k & t). Finally, all the specially formatted string variables will be copied to PowerPoint variables via clipboard/gettext.
Any help / guidance is greatly appreciated.
Pat K.

It is workaround Idea only and the code may not be useful for your purpose depending on source and destination of the data and may be treated as demo only. i have only used excel cells and Text Boxes on a sheet as destination and used PowerPoint Text Boxes as target.
The simple approach is that while picking up String from formatted cells/Text Boxes from excel to a variable, Font Subscript, Superscript information is also to be picked up in a parallel variable (here in a 2D Array). The same font information may be used while writing in PowerPoint. The demo idea have to be Modified/Converted to suit your need.
Demo Screen shot
The demo code
Sub Sscript()
Dim CellStr() As Variant
Dim Rng As Range, Cell As Range
Dim shp As Shape
Dim VarNo As Long, i As Long, j As Long, Txt As String, FntInfo As String
Set Rng = Range("C3:C7") 'Range used for collecting input data and font information for the variable
VarNo = 0
'loop used for Trial may be directly assigned to CellStr by increasing Varno by one for each cell
For Each Cell In Rng.Cells
VarNo = VarNo + 1
ReDim Preserve CellStr(1 To 2, 1 To VarNo)
Txt = Cell.Value
CellStr(1, VarNo) = Txt
FntInfo = ""
For i = 1 To Len(Txt)
If Cell.Characters(i, 1).Font.Subscript = True Then
FntInfo = FntInfo & "A"
ElseIf Cell.Characters(i, 1).Font.Superscript = True Then
FntInfo = FntInfo & "B"
Else
FntInfo = FntInfo & "C"
End If
Next i
CellStr(2, VarNo) = FntInfo
Next Cell
'again loop used for Trial may be directly assigned to CellStr from Textboxes in the sheet
For Each shp In ActiveSheet.Shapes
If shp.Type = msoTextBox Then
VarNo = VarNo + 1
ReDim Preserve CellStr(1 To 2, 1 To VarNo)
Txt = shp.TextFrame2.TextRange.Text
CellStr(1, VarNo) = Txt
FntInfo = ""
For i = 1 To Len(Txt)
If shp.TextFrame2.TextRange.Characters(i, 1).Font.Subscript = msoTrue Then
FntInfo = FntInfo & "A"
ElseIf shp.TextFrame2.TextRange.Characters(i, 1).Font.Superscript = msoTrue Then
FntInfo = FntInfo & "B"
Else
FntInfo = FntInfo & "C"
End If
Next i
CellStr(2, VarNo) = FntInfo
End If
Next
'Start of Trial code in excel to be deleted
For i = 1 To UBound(CellStr, 2)
ActiveSheet.Cells(i, 10).Value = CellStr(1, i)
ActiveSheet.Cells(i, 11).Value = CellStr(2, i)
FntInfo = CellStr(2, i)
For j = 1 To Len(FntInfo)
ActiveSheet.Cells(i, 10).Characters(j, 1).Font.Subscript = False
ActiveSheet.Cells(i, 10).Characters(j, 1).Font.Superscript = False
If Mid(FntInfo, j, 1) = "A" Then ActiveSheet.Cells(i, 10).Characters(j, 1).Font.Subscript = True
If Mid(FntInfo, j, 1) = "B" Then ActiveSheet.Cells(i, 10).Characters(j, 1).Font.Superscript = True
Next j
Next
'End of Trial code in excel to be deleted
'Powerpoint placement of data in powerpoint
Dim Pp As PowerPoint.Application
Dim Prs As Presentation
Dim Sld As Slide
Dim Pshp As Shape
Set Pp = CreateObject("Powerpoint.application")
Pp.Visible = True
Set Prs = Pp.Presentations.Open("C:\users\user\desktop\test.pptx")
Set Sld = Prs.Slides(1)
For i = 1 To UBound(CellStr, 2)
Set Pshp = Sld.Shapes(i)
Pshp.TextFrame.TextRange.Text = CellStr(1, i)
FntInfo = CellStr(2, i)
For j = 1 To Len(FntInfo)
Pshp.TextFrame.TextRange.Characters(j, 1).Font.Subscript = False
Pshp.TextFrame.TextRange.Characters(j, 1).Font.Superscript = False
If Mid(FntInfo, j, 1) = "A" Then Pshp.TextFrame.TextRange.Characters(j, 1).Font.Subscript = True
If Mid(FntInfo, j, 1) = "B" Then Pshp.TextFrame.TextRange.Characters(j, 1).Font.Superscript = True
Next j
Next
End Sub
It is suggested to Add reference of Microsoft PowerPoint Object Library and thanks for asking a good question/challenge to achieve something seemingly not possible but logically possible.
Edit: another more simplistic approach (the 1st half of the String variable contains actual string and 2nd half of the variable contains Font Info) with generalized functions is also added below
Sub Sscript2()
Dim Txt As String, Var1 As String, Var2 As String
Dim Addr As String
Var1 = GetVarFont("C6") ' 1st half of the var contains actual string and 2nd half contain font Info
Var2 = GetVarFont("C7") ' 1st half of the var contains actual string and 2nd half contain font Info
'Powerpoint placement of data in powerpoint
Dim Pp As PowerPoint.Application
Dim Prs As Presentation
Dim Sld As Slide
Dim Pshp As Object
Set Pp = CreateObject("Powerpoint.application")
Pp.Visible = True
Set Prs = Pp.Presentations.Open("C:\users\user\desktop\test.pptx")
Set Sld = Prs.Slides(1)
WriteShp Sld.Shapes(8).TextFrame.TextRange, Var1
WriteShp Sld.Shapes(9).TextFrame.TextRange, Var2
End Sub
Sub WriteShp(Ptxt As TextRange, VarX As String)
Dim i As Long
Ptxt.Text = Left(VarX, Len(VarX) / 2)
For i = 1 To Len(VarX) / 2
Ptxt.Characters(i, 1).Font.Subscript = False
Ptxt.Characters(i, 1).Font.Superscript = False
If Mid(VarX, Len(VarX) / 2 + i, 1) = "A" Then Ptxt.Characters(i, 1).Font.Subscript = True
If Mid(VarX, Len(VarX) / 2 + i, 1) = "B" Then Ptxt.Characters(i, 1).Font.Superscript = True
Next
End Sub
Function GetVarFont(Addr As String) As String
Dim Txt As String, i As Long
Txt = Range(Addr).Value
GetVarFont = Txt
For i = 1 To Len(Txt)
If Range(Addr).Characters(i, 1).Font.Subscript = True Then
GetVarFont = GetVarFont & "A"
ElseIf Range(Addr).Characters(i, 1).Font.Superscript = True Then
GetVarFont = GetVarFont & "B"
Else
GetVarFont = GetVarFont & "C"
End If
Next i
End Function

Related

Finding nested duplicates

I am trying to make a Bill of Material list from my CAD software(Creo), this is exported out as a text file and its almost working perferct. The problem is that the text file spits out all parts in model, and I would like to count up duplicates instead of list them out after each other i.e quanties larger then 1.
My code does this nicely if the duplicate is listed in the row above, however if its nested(sub assy) this does not work. In picture below 00151564.asm(level 2) should be listed as quantity=3 but the parts on level 3 should be listed as quantity=1
Below is the original textfile creo spitts out, groups and patterns makes extra unwanteded indents, and material is just sometimes given..
Here is my code:
Sub simen2(Optional myFile As String = "Z:\Prosjekt\33907\Equipment and
materials\Structure\treetool2.txt")
Dim text As String
Dim textline As String
Dim textlineTemp As String
Dim foo As String
Dim output As String
Dim parent As String
Dim grandma As String
Dim greatgrandma As String
Dim greatgreatgrandma As String
Dim partNumber As String
Dim quantity As Integer
Dim material As String
Dim wsOut As Worksheet
Dim i, k As Long
Dim level, levelOld, levelTemp, levelTempOld, subtractLevel As Integer
Dim duplicate As Boolean
Dim levelDictionary As Object
'Init variables
Set wsOut = ThisWorkbook.Worksheets("Output")
subtractLevel = 0
quantity = 1
duplicate = True
partNumberOld = ""
commonNameOld = ""
levelOld = 0
levelTemp = 0
levelTempOld = 0
materialOld = ""
textlineOld = ""
material = "NA"
materialOld = "NA"
text = wsOut.Cells(1, 1).Value
wsOut.Cells.ClearContents
wsOut.Cells(1, 1).Value = text
wsOut.Cells(1, 2).Value = Now
wsOut.Cells(1, 3).Value = myFile
Call write2ExcelHeader(wsOut)
Set levelDictionary = CreateObject("Scripting.Dictionary")
i = 0
k = 1
FileNum = FreeFile()
Open myFile For Input As #FileNum
Line Input #FileNum, foo
Line Input #FileNum, foo
Do Until EOF(FileNum)
k = k + 1
' read in
Line Input #FileNum, textline
' Get level, however group and pattern fuck things up
If InStr(10, textline, "<HTML>") > 0 Or InStr(textline, "Pattern") > 0 Or InStr(textline, "Group") > 0 Then
levelTemp = getLevel(textline)
If levelTemp < levelTempOld Then
subtractLevel = 0
End If
If InStr(textline, "Pattern") > 0 Or InStr(textline, "Group") > 0 Then
subtractLevel = subtractLevel + 1
k = 1
End If
End If
' Grab material
If InStr(textline, "Materials") > 0 Then
Line Input #FileNum, textline
material = Trim(Replace(textline, "<curr>", ""))
End If
'we need to find out if the line has number as first item, i.e trim away spaces, it prints out previous item here...
If InStr(10, textline, "<HTML>") > 0 Then
'textlineTemp = RemoveHTML(textline)
'textlineTemp = Replace(textlineTemp, "Ã", "Ø")
partNumber = getPartNumber(textline)
commonName = getCommonName(textline)
partType = getType(partNumber)
material = "NA"
' add part to dictionary, this is unique parts
If levelDictionary.exists(partNumber) Then
levelDictionary(partNumber) = levelDictionary(partNumber) + 1
Else
levelDictionary.Add partNumber, 1
End If
' Remove duplicates ......
If partNumberOld = partNumber And levelTempOld = levelTemp Then
duplicate = True
quantity = quantity + 1
Else
quantity = 1
duplicate = False
End If
' get family history
level = levelTemp - subtractLevel
LevelArray(level) = partNumber
'lets present result
If Not duplicate Then
If level > 1 Then
parent = LevelArray(level - 1)
Else
parent = "NA"
End If
If level > 2 Then
grandma = LevelArray(level - 2)
Else
grandma = "NA"
End If
If level > 3 Then
greatgrandma = LevelArray(level - 3)
Else
greatgrandma = "NA"
End If
If level > 4 Then
greatgreatgrandma = LevelArray(level - 4)
Else
greatgreatgrandma = "NA"
End If
If i > 0 Then
Call write2Excel(wsOut, i + 2, partNumberOld, commonNameOld, quantityOld, materialOld, levelOld, partTypeOld, parentOld, grandmaOld, greatgrandmaOld, greatgreatgrandmaOld)
End If
i = i + 1
End If
End If
'we always uses previous values for print out
partNumberOld = partNumber
commonNameOld = commonName
levelOld = level
levelTempOld = levelTemp
partTypeOld = partType
quantityOld = quantity
materialOld = material
textlineOld = textline
parentOld = parent
grandmaOld = grandma
greatgrandmaOld = greatgrandma
greatgreatgrandmaOld = greatgreatgrandma
Loop
Close #FileNum
Debug.Print "How many parts " & i
Call DeList(wsOut)
Call CreateList(wsOut, "FilterOutput")
Call totalBOM(levelDictionary)
End Sub
An alternative OO approach using a class module. Output is to a sheet named "Output2".
Update 1 - Added debugging log, creo.log in same folder as workbook.
Option Explicit
Sub ProcessTextFile()
Const TXTFILE = "treetool_Rextroth.txt" '"treetool20210503.txt"
Const MAX_LEVEL = 10
Dim tree() As clsItem, item As clsItem
Dim ruler() As Integer, level As Integer, rs
Dim FileNum As Integer, textline As String, text As String
Dim start_name As Integer, width_name As Integer, n As Long
Dim t0 As Single: t0 = Timer
ReDim tree(MAX_LEVEL)
ReDim ruler(MAX_LEVEL)
FileNum = FreeFile()
Open ThisWorkbook.Path & "\" & TXTFILE For Input As #FileNum
' use first header line to get common name column position
Line Input #FileNum, textline
start_name = InStr(1, textline, "PTC_COMMON_NAME")
width_name = InStr(1, textline, "PRO_MP_") - start_name
' skip
Line Input #FileNum, textline
' set start level and indent
Set tree(0) = New clsItem
tree(0).level = 0
tree(0).id = "NA"
level = 1
ruler(1) = 1
' open log file
Dim fso, ts
Set fso = CreateObject("Scripting.Filesystemobject")
Set ts = fso.createtextfile("creo.log")
' scan text file
n = 2
Do Until EOF(FileNum)
n = n + 1
Line Input #FileNum, textline
rs = ParseLine(textline, n, ts)
If rs(0) = "ASM" Or rs(0) = "PRT" Then
' determine level from indent
level = GetLevel(ruler, rs(2), level, ts)
' create new item
Set item = New clsItem
With item
.itemtype = rs(0)
.id = rs(1)
.name = Mid(textline, start_name, width_name)
.qu = 1
.level = level
.parent = tree(level - 1).id
.creo = n
End With
' build tree
Set tree(level) = item
tree(level - 1).addItem item
' groups or patterns
ElseIf rs(0) = "GRP" Or rs(0) = "PTN" Then
' increase ruler for current level by 2
ruler(level) = ruler(level) + 2
ts.writeline n & " " & rs(0) & " change ruler(" & level & ")=" & ruler(level)
' materials
ElseIf rs(0) = "MTL" Then
If item.itemtype = "PRT" Then
' get material from next line
n = n + 1
Line Input #FileNum, textline
item.material = Trim(Replace(textline, "<curr>", ""))
End If
End If
Loop
' output tree
Application.ScreenUpdating = False
With Sheets("Output2")
text = .Range("A1")
.Cells.ClearContents
.Cells.Clear
.Range("A1") = text
.Range("B1") = Now
.Range("C1") = TXTFILE
With .Range("A2:H2")
.Value2 = Array("Part No", "Common Name", _
"Qu.", "Material", "Level", "Type", "Parent", "Creo Lines")
.Interior.Color = RGB(255, 200, 0)
.Font.Bold = True
End With
' save objects to sheet
tree(0).SaveToWorksheet .Range("A3")
' prettify
.Range("C:C,E:E").HorizontalAlignment = xlCenter
.Columns("H:H").HorizontalAlignment = xlRight
.Columns("A:H").AutoFit
.ListObjects.add(xlSrcRange, .UsedRange.Offset(1), , xlYes).name = "Table2"
.ListObjects("Table2").TableStyle = "TableStyleLight1"
.Activate
.Range("A1").Select
End With
Application.ScreenUpdating = True
MsgBox Format(n, "#,###") & " lines parsed in " & _
Format(Timer - t0, "0.00") & " seconds"
End Sub
' determine level from indent using ruler
Function GetLevel(ByRef ruler, indent, level, ts) As Integer
Dim n As Integer
n = level ' current level
' is this an increase on previous
If indent > ruler(n) Then
ts.writeline "GetLevel before ruler(" & n & ")=" & ruler(n)
n = n + 1
Else
' find previous level
n = 0
Do
n = n + 1
Loop While indent > ruler(n)
End If
ruler(n) = indent ' update
ts.writeline "Level now " & n & " ruler(" & n & ")=" & indent
GetLevel = n
End Function
' determine linetype, partno, indent
Function ParseLine(s As String, n, ts) As Variant
Dim indent As Integer, partno As String
Dim tmp As String, linetype As String
indent = Len(s) - Len(LTrim(s)) ' no of spaces
If InStr(1, s, "<HTML>") Then
tmp = WorksheetFunction.Trim(s)
partno = Split(tmp, " ")(0)
linetype = Right(partno, 3)
ts.writeline vbCrLf & n & " INDENT=" & indent & " '" & s
ElseIf InStr(1, s, "Materials") Then
linetype = "MTL"
ElseIf InStr(1, s, "Group") Then
linetype = "GRP"
ElseIf InStr(1, s, "Pattern") Then
linetype = "PTN"
End If
ParseLine = Array(linetype, partno, indent)
End Function
class module clsItem
========================
Option Explicit
' this class represent a part or assembly
Public id As String ' partno
Public name As String ' common name
Public itemtype As String ' PRT or ASM
Public parent As String
Public level As Integer
Public qu As Integer
Public material As String
Public creo As String ' source line nos in Creo file
Public items As New Collection
' add items
Public Function addItem(obj As clsItem)
' check if exists, if so increment quantity
Dim item As clsItem, bExists As Boolean
For Each item In items
If item.id = obj.id Then
item.qu = item.qu + obj.qu
item.creo = item.creo & " " & obj.creo
bExists = True
Exit For
End If
Next
' does not exist so add new
If Not bExists Then items.add obj, obj.id
End Function
' save object and all children
Public Sub SaveToWorksheet(rng As Range)
Const SP = 5 ' no of spaces to indent at each level
Dim item As clsItem
If level > 0 Then
rng = Space(level * SP) & id
rng.Offset(0, 1) = name
rng.Offset(0, 2) = qu
rng.Offset(0, 3) = material
rng.Offset(0, 4) = level
rng.Offset(0, 5) = itemtype
rng.Offset(0, 6) = parent
rng.Offset(0, 7) = creo
Set rng = rng.Offset(1)
End If
' recurse
For Each item In Me.items
item.SaveToWorksheet rng
Next
End Sub
This scans the Output sheet and creates a new sheet called "No Duplicates". However, with the bugs in your data I suspect this won't work for more complex cases e.g. where an assembly appears in other assemblies at different levels.
Update 1 - dictionary at each level
Sub RemoveDuplicates()
Const START_ROW = 3 ' skip headers
Const COL_PARTNO = 1 ' A
Const COL_QU = 3 ' C
Const COL_LEVEL = 5 ' E
Const COL_TYPE = 6 ' F
Dim wb As Workbook, ws As Worksheet, wsNew As Worksheet
Dim iLastRow As Long, i As Long, r As Long
Dim PartNo As String, isASM As Boolean
Dim dictLevel As Integer, level As Integer
' dictionary at each level
Dim dictASM(10) As Object, arQu As Variant
Set wb = ThisWorkbook
Set ws = wb.Sheets("Output")
iLastRow = ws.Cells(Rows.Count, "A").End(xlUp).Row
ReDim arQu(iLastRow)
For i = 0 To 10
Set dictASM(i) = CreateObject("Scripting.Dictionary")
Next
r = START_ROW
dictLevel = 0
Do
PartNo = Trim(ws.Cells(r, COL_PARTNO))
isASM = Trim(ws.Cells(r, COL_TYPE) = "ASM")
arQu(r) = ws.Cells(r, COL_QU)
level = ws.Cells(r, COL_LEVEL)
If isASM Then
' is this a new assembly clear lower level dictionaries
If level < dictLevel Then
For i = dictLevel To UBound(dictASM)
Set dictASM(i) = CreateObject("Scripting.Dictionary")
Next
dictLevel = level
Else
If dictASM(dictLevel).exists(PartNo) Then
' duplicate assembly
' increment first occurence
i = dictASM(dictLevel)(PartNo)
arQu(i) = arQu(i) + arQu(r)
arQu(r) = 0
level = ws.Cells(r, COL_LEVEL)
' delete lower level items
Do While ws.Cells(r + 1, COL_LEVEL) > level
r = r + 1
arQu(r) = 0 ' delete later
Loop
Else
' first occurence
dictASM(dictLevel).add PartNo, r
End If
End If
End If
r = r + 1
Loop While r < iLastRow
' create new sheet without duplicates
Set wsNew = Sheets.add
wsNew.Name = "No Duplicates"
ws.Range("A2:G2").Copy wsNew.Range("A2") ' header
i = START_ROW
For r = START_ROW To iLastRow
If arQu(r) > 0 Then
ws.Cells(r, 1).Resize(1, 8).Copy wsNew.Cells(i, 1)
wsNew.Cells(i, COL_QU) = arQu(r)
i = i + 1
End If
Next
wsNew.Columns("A:G").AutoFit
MsgBox Format(iLastRow - 1, "#,###") & " rows scanned", vbInformation
End Sub

use VBA to color specific words in a list

SO I have a list of words ( they are 250ish medications in my Settings sheet ) , and I want to use vba to find those specific words in Column D of another sheet and color them magenta. Column D has 105 cells that are full of text.
text I want to search:
list of meds:
what I want it to look like:
below is what iv gathered from other resources but I just cant get it to work! please let me know if you have any suggestions!
also it kinda has to work with mac and windows excel
Sub ColorWords3()
Dim Position As Long, Cell As Range, W As Variant, Words As Variant, Txt As String, druglastcol As Variant, drugs As Variant
druglastcol = Sheets("Settings").Range("A" & Rows.Count).End(xlUp).Row
'Words = Array("TEXT", "WORD", "THEN")
Words = Application.Transpose(Sheets("Settings").Range("A4:A" & druglastcol).Text)
For Each Cell In Columns("D").SpecialCells(xlConstants)
Txt = " " & UCase(Cell.Value) & " "
For Each W In Words
Position = InStr(Txt, W)
Do While Position > 0
If Mid(Txt, Position - 1, Len(W) + 2) Like "[!A-Z0-9]" & W & "[!A-Z0-9]" Then
With Cell.Characters(Position - 1, Len(W)).Font
.Bold = True
.Color = vbRed
End With
End If
Position = InStr(Position + 1, Txt, W)
Loop
Next
Next
End Sub
Like is case-sensitive, so you need to upper-case your drug names to match your upper-cased blocks of text.
If Mid(Txt, Position - 1, Len(W) + 2) Like "[!A-Z0-9]" & UCase(W) & "[!A-Z0-9]" Then
Using Like gets a bit clunky so here's a RegExp-based approach:
EDIT - added a working Like/InStr version...
Sub ColorWords()
Dim Cell As Range, W, Words, matches As Collection, m
With Sheets("Settings")
Words = Application.Transpose(.Range(.Range("A4"), _
.Cells(.Rows.Count, 1).End(xlUp)))
End With
For Each Cell In ActiveSheet.Columns("D").SpecialCells(xlConstants)
For Each W In Words
'Set matches = AllMatchesRegEx(Cell.Text, W) 'windows only
Set matches = AllMatchesInStr(Cell.Text, W) 'windows+mac
For Each m In matches
Debug.Print Cell.Address, W, m
With Cell.Characters(m, Len(W)).Font
.Bold = True
.Color = vbMagenta
End With
Next m
Next
Next
End Sub
Function AllMatchesInStr(ByVal textToSearch As String, searchTerm)
Const OUT As String = "[!A-Z0-9]"
Dim rv As New Collection, pos As Long, start As Long
Dim next2 As String, next1 As String
textToSearch = UCase(" " & textToSearch & " ")
start = 1
pos = InStr(start, textToSearch, searchTerm, vbTextCompare)
Do While pos > 0
If Mid(textToSearch, pos - 1, 1) Like OUT Then
next2 = Mid(textToSearch, pos + Len(searchTerm), 2)
next1 = Left(next2, 1)
'Handle possible s at end of search term
If next1 Like OUT Or (next2 Like "S" & OUT) Then
rv.Add pos - 1
End If
End If
start = pos + 1
pos = InStr(start, textToSearch, searchTerm, vbTextCompare)
Loop
Set AllMatchesInStr = rv
End Function
Function AllMatchesRegEx(textToSearch As String, searchTerm)
Dim rv As New Collection, matches, m
Static reg As Object
If reg Is Nothing Then
Set reg = CreateObject("VBScript.RegExp")
reg.Global = True
reg.IgnoreCase = True
End If
reg.Pattern = "\b" & searchTerm & "s?\b" 'Allow for simple plural form,
'flank with word boundaries
Set matches = reg.Execute(textToSearch)
For Each m In matches
rv.Add m.firstindex + 1 'firstindex is zero-based
Next m
Set AllMatchesRegEx = rv
End Function
There is a mistake in your code:
Words = Application.Transpose(Sheets("Settings").Range("A4:A" & Dr).Text)
what is Dr?
Also don't do this:
druglastcol = Sheets("Settings").Range("A4:A" & Rows.Count).End(xlDown).Row
Do this instead:
druglastcol = Sheets("Settings").Range("A" & Rows.Count).End(xlUp).Row
The reason we do it this way is the method you have used will stop if there is a blank row in the data, the method i have posted comes from the bottom up so will always grab the true last row.
Try
Sub test()
Dim Ws As Worksheet
Dim s As String
Dim vDB
Dim i As Long
'Application.ScreenUpdating = False
Set Ws = Sheets("Settings")
With Ws
vDB = .Range("a1", .Range("a" & Rows.Count).End(xlUp))
End With
For i = 1 To UBound(vDB, 1)
s = vDB(i, 1)
setCharacterColor s
Next i
'Application.ScreenUpdating = True
End Sub
Sub setCharacterColor(strPattern As String)
Dim mCol As Object 'MatchCollection
Dim Ws As Worksheet
Dim rngDB As Range, rng As Range
Dim s As String
Dim i As Integer, Ln As Integer
Set Ws = Sheets("Facts")
Set rngDB = Ws.Range("d1", Ws.Range("d" & Rows.Count).End(xlUp))
For Each rng In rngDB
s = rng.Value
Set mCol = GetRegEx(s, strPattern)
If Not mCol Is Nothing Then
For i = 0 To mCol.Count - 1
c = mCol.Item(i).FirstIndex + 1
Ln = mCol.Item(i).Length
With rng.Characters(c, Ln).Font
.Bold = True
.Color = vbMagenta
End With
Next i
End If
Next
End Sub
Function GetRegEx(StrInput As String, strPattern As String) As Object
Dim RegEx As Object 'New RegExp
Set RegEx = CreateObject("VBScript.RegExp") 'New RegExp
With RegEx
.Global = True
.IgnoreCase = False
.MultiLine = True
.Pattern = strPattern
End With
If RegEx.test(StrInput) Then
Set GetRegEx = RegEx.Execute(StrInput)
End If
End Function
If your use Mac then try below.
Sub test()
Dim Ws As Worksheet, WsColor As Worksheet
Dim rngDB As Range, rng As Range
Dim s As String
Dim vDB, vR
Dim i As Long, Ln As Integer
Dim j As Index
Dim st, et
Application.ScreenUpdating = False
st = Timer
Set Ws = Sheets("Settings")
Set WsColor = Sheets("Facts")
With Ws
vDB = .Range("a1", .Range("a" & Rows.Count).End(xlUp))
End With
With WsColor
Set rngDB = .Range("d1", .Range("d" & Rows.Count).End(xlUp))
End With
For Each rng In rngDB
For i = 1 To UBound(vDB, 1)
Ln = Len(vDB(i, 1)) 'String Length
vR = getItem(rng, vDB(i, 1)) 'string startedIndex
If IsArray(vR) And Not IsEmpty(vR) Then
For j = 1 To UBound(vR)
With rng.Characters(vR(j), Ln).Font
.Bold = True
.Color = vbMagenta
End With
Next j
End If
Next i
Next rng
Application.ScreenUpdating = True
et = Timer
Debug.Print et - st
End Sub
Function getItem(rng As Range, v As Variant) As Variant
Dim vR()
Dim k As Integer, s As Integer, n As Index
Dim str As String
str = rng.Text
s = 1
Do
n = InStr(s, str, v)
If n > 0 Then
k = k + 1
ReDim Preserve vR(1 To k)
vR(k) = n
End If
s = n + Len(v)
DoEvents
Loop While n > 0
If k Then
getItem = vR
Else
getItem = Empty
End If
End Function

Set up a variable with multiple IF statements linked to a FOR loop with a counter

I have 3 sheets. In the sheet "Manager", there are 7 dropdown lists for criteria: H5, H7, H9, H11, H13, H15, H17. Once the criteria are selected and the user clicks on the button "COPY", the macro searches in the sheet "Data" columns A:G the rows matching the selected criteria. Then it copies the range P:W for the matching rows and pastes it in sheet "Quote" starting from row 11. It is important to note when the user does not select a criterion for any of the dropdown list, then that criterion is just ignored (see VbNullString in the code)
By now, the macro runs fine with multiple criteria selection for the Company dropdown list (H5) and single criterion selection for the others (H7, H9, H11, H13, H15, H17).
Sub Quote()
Dim Source As Worksheet
Dim Target As Worksheet
Dim Manager As Worksheet
Dim Multiple () As String 'Here
Dim InfoA As String
Dim InfoB As String
Dim InfoC As String
Dim ProductType As String
Dim SalesStatus As String
Dim finalrow As Integer
Dim counter As Integer
Dim I As Integer
Set Source = Worksheets("Data")
Set Target = Worksheets("Quote")
Set Manager = Worksheets("Manager")
If Worksheets("Manager").Range("H5").Value <> vbNullString Then 'Here
Multiple = Split(Worksheets("Manager").Range("H5").Value, ",") 'Here
If Worksheets("Manager").Range("H13").Value <> vbNullString Then 'Modified
Multiple = Split(Worksheets("Manager").Range("H13").Value, ",") 'Here
Else 'Here
Multiple = Split("", "") 'Here
End If 'Here
End If 'Here
InfoA = Worksheets("Manager").Range("H7").Value
InfoB = Worksheets("Manager").Range("H9").Value
InfoC = Worksheets("Manager").Range("H11").Value
ProductType = Worksheets("Manager").Range("H15").Value
SalesStatus = Worksheets("Manager").Range("H17").Value
finalrow = Source.Cells(Rows.Count, 1).End(xlUp).Row
For counter = 0 To UBound(Multiple) 'Here
lookupMult = Trim(Multiple(counter)) 'Here
For I = 2 To finalrow
thisComp = Source.Cells(I, 1)
thisInfA = Source.Cells(I, 2)
thisInfB = Source.Cells(I, 3)
thisInfC = Source.Cells(I, 4)
thisProd = Source.Cells(I, 5)
thisType = Source.Cells(I, 6)
thisSale = Source.Cells(I, 7)
If (thisComp = lookupMult Or lookupMult = vbNullString) Then 'Here
If (thisInfA = InfoA Or InfoA = vbNullString) Then
If (thisInfB = InfoB Or InfoB = vbNullString) Then
If (thisInfC = InfoC Or InfoC = vbNullString) Then
If (thisProd = lookupMult Or lookupMult = vbNullString) Then 'Here
If (thisType = ProductType Or ProductType = vbNullString) Then
If (thisSale = SalesStatus Or SalesStatus = vbNullString) Then
Source.Range(Source.Cells(I, 16), Source.Cells(I, 23)).Copy Target.Range("A200").End(xlUp).Offset(1, 0).Resize(1, 8)
End If
End If
End If
End If
End If
End If
End If
Next I
Next counter
End Sub
In addition to the multiple criteria selection for H5, I need also to enable it for the Product (H13). To do so, I tried to modify the variable Company using a more elaborated IF statement. In the picture, the sheet "Quote" is the result I should get. But in fact nothing is copy-pasted and I cannot figure out what I'm doing wrong. I added some comments 'Here to show what part of the code I modified. By advance thanks for any guidance.
I found a way to solve my issue. It is not a silver bullet, but at least it works as it should. After, if anyone knows some way to optimize the code, outside of SQL queries and structured tables, feel free to share and I will try. Note I believe SQL queries is probably a better option, but it means I have to rework almost all my code and use methods I do not know (yet). I will study it later for a future update.
The problem is the word "counter" might be a reserved variable. So, I was not authorized to add another FOR in my loop sharing similar features. Since I changed the "counter" variable by letters, I'm now able to do multiple criteria selection for other dropdown lists. In the example below, I just made it for H5 and H13 in order to keep it clear.
Sub Quote()
Dim Source As Worksheet
Dim Target As Worksheet
Dim Manager As Worksheet
Dim Company () As String
Dim InfoA As String
Dim InfoB As String
Dim InfoC As String
Dim Product () As String
Dim ProductType As String
Dim SalesStatus As String
Dim finalrow As Integer
Dim I As Integer
Dim J As Integer
Dim K As Integer
Set Source = Worksheets("Data")
Set Target = Worksheets("Quote")
Set Manager = Worksheets("Manager")
If Worksheets("Manager").Range("H5").Value <> vbNullString Then
Company= Split(Worksheets("Manager").Range("H5").Value, ",")
Else
Company = Split("", "")
End If
InfoA = Worksheets("Manager").Range("H7").Value
InfoB = Worksheets("Manager").Range("H9").Value
InfoC = Worksheets("Manager").Range("H11").Value
If Worksheets("Manager").Range("H13").Value <> vbNullString Then
Product = Split(Worksheets("Manager").Range("H13").Value, ",")
Else
Product = Split("", "")
End If
ProductType = Worksheets("Manager").Range("H15").Value
SalesStatus = Worksheets("Manager").Range("H17").Value
finalrow = Source.Cells(Rows.Count, 1).End(xlUp).Row
For K = 0 To UBound(Company)
lookupComp = Trim(Company(K))
For J = 0 To UBound(Product)
lookupProd = Trim(Product(J))
For I = 2 To finalrow
thisComp = Source.Cells(I, 1)
thisInfA = Source.Cells(I, 2)
thisInfB = Source.Cells(I, 3)
thisInfC = Source.Cells(I, 4)
thisProd = Source.Cells(I, 5)
thisType = Source.Cells(I, 6)
thisSale = Source.Cells(I, 7)
If (thisComp = lookupComp Or lookupComp = vbNullString) Then
If (thisInfA = InfoA Or InfoA = vbNullString) Then
If (thisInfB = InfoB Or InfoB = vbNullString) Then
If (thisInfC = InfoC Or InfoC = vbNullString) Then
If (thisProd = lookupProd Or lookupProd = vbNullString) Then
If (thisType = ProductType Or ProductType = vbNullString) Then
If (thisSale = SalesStatus Or SalesStatus = vbNullString) Then
Source.Range(Source.Cells(I, 16), Source.Cells(I, 23)).Copy Target.Range("A200").End(xlUp).Offset(1, 0).Resize(1, 8)
End If
End If
End If
End If
End If
End If
End If
Next I
Next J
Next K
End Sub

VBA Splitting a String into multiple cells when it has variable Delimiters

If I have the below info all contained in a single cell and I want to split it into separate cells. I understand how to use the space as a delimiter but in this case, the name also has spaces and I want the name to stay together in a single cell. To further complicate the matter, the name is not always just first and last, it can also include middle so is not always a standard two names.
2172571122 Jane Doe 3143332222 John Doe
2172242237 Mary Mixer 2223334444 Mike M Martin
Want it to end up looking like this:
Cell 1 = 2172242237
Cell 2 = Mary Mixer
Cell 3 = 2223334444
Cell 4 = Mike M Martin
Any suggestions?
This regex based function alternates each split between numbers and text (words).
Option Explicit
Function customSplit(str As String, _
Optional ndx As Integer = 1) As Variant
Static rgx As Object, cmat As Object
Set rgx = CreateObject("VBScript.RegExp")
With rgx
.Global = True
.MultiLine = True
.IgnoreCase = True
If CBool(ndx Mod 2) Then
.Pattern = "[0-9]{10}"
ndx = (ndx + 1) \ 2
Else
.Pattern = "[A-Z]{1,9}\s[A-Z]{1,9}[\s[A-Z]{1,9}]?"
ndx = ndx \ 2
End If
If .test(str) Then
Set cmat = .Execute(str)
If ndx <= cmat.Count Then
customSplit = cmat.Item(ndx - 1)
End If
End If
End With
End Function
You could try:
Option Explicit
Sub test()
Dim strToSplit As String, strImport As String
Dim arrwords As Variant
Dim i As Long, counter As Long
With ThisWorkbook.Worksheets("Sheet1")
strToSplit = .Range("A1").Value
arrwords = Split(strToSplit, " ")
counter = 1
For i = LBound(arrwords) To UBound(arrwords)
If IsNumeric(arrwords(i)) = True Then
strImport = arrwords(i)
.Cells(3, counter).Value = strImport
counter = counter + 1
ElseIf Not IsNumeric(arrwords(i)) = True Then
If Not IsNumeric(.Cells(3, counter - 1).Value) Then
strImport = .Cells(3, counter - 1) & " " & arrwords(i)
.Cells(3, counter - 1).Value = strImport
counter = counter
Else
strImport = arrwords(i)
.Cells(3, counter).Value = strImport
counter = counter + 1
End If
End If
Next
End With
End Sub
Results look like this:
I have a few ideas on what you could do.
1) Read a Line
Do a split(line, " ") and loop through the indecies while performing a isNumeric() on each split value. If not, then add to a string Array() and set a flag to true.
Then, if isnumeric then, expect another name and set flag to true.
2) Read a line.
Then, loop through each character performing an isnumeric and if not then add that character to a string Array() and set flag until isnumeric again, etc....
I hope that helps or at least gets you in the right direction.
Additional variant to posted already:
Sub ZZZ()
Dim dic As Object: Set dic = CreateObject("Scripting.Dictionary")
Dim num$, cl As Range, data As Range, key, x
Dim Result As Worksheet
Set data = Range([A1], Cells(Rows.Count, "A").End(xlUp))
For Each cl In data
x = "": num = "":
For Each x In Split(cl, " ")
If IsNumeric(x) Then
num = x
dic.Add x, ""
ElseIf x <> "" And num <> "" Then
dic(num) = Trim(dic(num) & " " & x)
End If
Next x
Next cl
Set Result = Worksheets.Add
With Result
.Name = "Result " & Replace(Now, ":", "-")
x = 1
For Each key In dic
.Cells(x, "A").Value2 = key
.Cells(x, "B").Value2 = dic(key)
x = x + 1
Next key
.Columns("A:B").AutoFit
End With
End Sub
test:

extract specific set of digits from random strings in EXCEL VBA

Disclaimer- my case is specific, and in my case my code works because I know the pattern.
I was looking for an answer everywhere, and the codes I tried were not quite what I was looking for, this is my solution if you are looking for a set of numbers.
In my case, I was looking for 7 digits, starting with digit 1 in a a column with random strings, some string had the number some others didn't.
The number will appear in these three scenarios "1XXXXXX", "PXXXXXXXX", "PXXXXXXXXX"(this has more digits because there is a slash).
Here are the examples of strings:
9797 P/O1743061 465347 Hermann Schatte Earl Lowe
9797 Po 1743071 404440 Claude Gaudette Jose Luis Lopez
9817 1822037 463889 Jean Caron Mickelly Blaise
My Code
Sub getnum()
'i don't use explicit so i didn't declare everything
Dim stlen As String
Dim i As Integer
Dim arra() As String
Dim arran() As String
Orig.AutoFilterMode = False
Call BeginMacro
LastRow = Orig.Cells(Rows.Count, 1).End(xlUp).Row
Orig.Range("J2:J" & LastRow).Clear
'loop though column
For n = 2 To LastRow
celref = Orig.Cells(n, 4).Value
'split string on white spaces
arra() = Split(celref, " ")
'turn string to multiple strings
For counter = LBound(arra) To UBound(arra)
strin = arra(counter)
'remove white spaces from string
storage = Trim(strin)
lenof = Len(storage)
'if string has 9 characthers, check for conditions
If lenof = 9 Then
'position of first and last charachter
somstr = Mid(storage, 1, 1)
somot = Mid(storage, 9, 1)
If somstr = "P" Or somstr = "p" And IsNumeric(somot) = True Then
'removes Po or PO and keeps only 7 digits
storage = Right(storage, 7)
'stores in column J
Orig.Cells(n, 10).Value = storage
End If
ElseIf lenof = 10 Then
somstr = Mid(storage, 1, 1)
somot = Mid(storage, 10, 1)
'other conditions
If somstr = "P" Or somstr = "p" And IsNumeric(somot) = True Then
'removes Po or PO and keeps only 7 digits
storage = Right(storage, 7)
'stores in column J
Orig.Cells(n, 10).Value = storage
End If
End If
'eliminate comma within
arran() = Split(storage, ",")
If Orig.Cells(n, 10).Value <> storage Then
For counter2 = LBound(arran) To UBound(arran)
strin2 = arran(counter2)
storage2 = Trim(strin2)
'final condition if is 7 digits and starts with 1
If IsNumeric(storage2) = True And Len(storage2) = 7 Then
car = Mid(storage2, 1, 1)
If car = 1 Then
'stores in columns J at specific position
Orig.Cells(n, 10).Value = storage2
End If
Else
If isnumeric(orig.cells(n,10).value) =true and _
len(orig.cells(n,10).value = 7 then
orig.cells(n,10).value = orig.cells(n,10).value
else
Orig.Cells(n, 10).Value = "no po# in D"
End If
Next counter2
End If
Next counter
Next n
Call EndMacro
End Sub
you may try this
Option Explicit
Sub getnum()
Dim position As Variant
Dim cell As Range
With Worksheets("Orig") ' change it to your actual sheet name
With Intersect(.UsedRange, Columns("J"))
.Replace what:="P/O", replacement:="P/O ", lookat:=xlPart
For Each cell In .Cells
position = InStr(cell.Text, " 1")
If position > 0 Then cell.Value = Mid(cell.Value, position + 1, 7)
Next
End With
End With
End Sub
This code paste two formulas one in column G and one in column J). The first formula checks for a "P" in the first character of the cell in column 2 and if there is a "P" it extracts the last 7 characters in the string and puts them in column G. The second formula checks if there is not a "P" and if not extracts the last 7 characters in the string and puts them in column J.
Sub Extract()
Dim ws As Worksheet
Dim lRow As Long
Set ws = ThisWorkbook.Sheets("Sheet3")
lRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
ws.Range("G2:G" & lRow).Formula = "=IF(LEFT(B2)=""P"",(RIGHT(B2,7)),"""")"
ws.Range("J2:J" & lRow).Formula = "=IF(LEFT(B2)<>""P"",(RIGHT(B2, 7)),"""")"
End Sub
You may use the RegEx to extract the number in desired format.
Please give this a try...
Function Get10DigitNumber(ByVal Str As String) As String
Dim RE As Object
Set RE = CreateObject("VBScript.RegExp")
With RE
.Global = False
.Pattern = "1\d{6}"
End With
If RE.test(Str) Then
Get10DigitNumber = RE.Execute(Str)(0)
End If
End Function
Then if you want to use this function on the worksheet itself, assuming your string is in A2, try this...
=Get10DigitNumber(A2)
OR
You may use this function in another sub routine/macro like this...
Debug.Print Get10DigitNumber(<pass your string variable here>)
Edited Function:
Function Get10DigitNumber(ByVal Str As String) As String
Dim RE As Object, Matches As Object
Set RE = CreateObject("VBScript.RegExp")
With RE
.Global = False
.Pattern = "[Pp]?\/?[Oo]?(1\d{6})\b"
End With
If RE.test(Str) Then
Set Matches = RE.Execute(Str)
Get10DigitNumber = Matches(0).SubMatches(0)
End If
End Function
And use if as already described above.
After understanding what you were doing, I think this will work. Any feedback would be appreciated.
Dim cell As Range, LRow As Long
LRow = ActiveSheet.Cells(Rows.Count, 4).End(xlUp).Row
For Each cell In Range("D2:D" & LRow)
If cell.Value Like "*Po *" Then
cell.Offset(0, 6).Value = Split(cell.Value, " ")(2)
Else: cell.Offset(0, 6).Value = Split(cell.Value, " ")(1)
End If
Next cell
For Each cell In Range("J2:J" & LRow)
If Len(cell.Value) > 7 Then
cell.Value = Right(cell.Value, 7)
End If
Next

Resources