Search string in a range (text template) and replace from dynamic rows - excel

Currently I have a template which is in range called rngP1.
And this contains a text below:
"This is to confirm that strTitle has been enacted on strDate for strCompany."
Basically, I have a data in another sheet that will be used to replace these 3 strings from my template:
So what I would like to happen is that in every row data it will search strings strTitle, strDate, and strCompany and replace them according to the data of each row.
I have a code already, however, it doesn't work as I expected:
Sub example()
Dim wsMain As Worksheet
Set wsMain = Sheets("Main")
Dim wsTemplate As Worksheet
Set wsTemplate = Sheets("Template")
Dim textToReplace As Variant
Dim array_example()
Dim Find_Text As Variant
Dim str As String
last_row = wsMain.Range("A1").End(xlDown).Row 'Last row of the data set
ReDim array_example(last_row - 1, 2)
Find_Text = Array("strTitle", "strDate", "strCompany")
str = wsTemplate.Range("rngP1").Value
'Storing values in the array
For i = 0 To last_row - 1
array_example(i, 0) = wsMain.Range("A" & i + 2)
array_example(i, 1) = wsMain.Range("C" & i + 2)
array_example(i, 2) = wsMain.Range("D" & i + 2)
Next
For i = LBound(array_example, 1) To UBound(array_example, 1)
For j = LBound(array_example, 2) To UBound(array_example, 2)
For a = 0 To UBound(Find_Text)
str = Replace(str, Find_Text(a), array_example(i, j))
Next a
Next j
MsgBox str
Next i
End Sub
Wrong Output:
It should be:
This is to confirm that Title1 has been enacted on 13-October-18 for Company X.
And next one would be the next row which is title 2. So on and so fort.
If you have an alternative way to do it, I appreciate it.

Here is a working example:
You can push the data range from a worksheet into an array with one line without looping
DataArr = wsMain.Range("A2:D" & LastRow).Value
You need only 2 loops for the replacing:
one to loop through the data rows
one to loop through the variables to replace
Your template str was not initialized within the loop, but you need a fresh template for every data row.
Note that the array loaded from the range starts counting from 1 but the variables array starts counting from 0.
Option Explicit
Sub Example()
Dim Template As String
Template = "This is to confirm that strTitle has been enacted on strDate for strCompany."
'load your template string from worksheet here!
Dim Variables As Variant 'variables to be replaced
Variables = Array("strTitle", "strDate", "strCompany")
Dim wsMain As Worksheet
Set wsMain = ThisWorkbook.Worksheets("Main")
Dim LastRow As Long 'this method is more reliable to find the last used row
LastRow = wsMain.Cells(wsMain.Rows.Count, "A").End(xlUp).Row
Dim DataArr As Variant 'load the complete data range into an array
DataArr = wsMain.Range("A2:D" & LastRow).Value
Dim Output As String
Dim iRow As Long, iVar As Long
For iRow = LBound(DataArr, 1) To UBound(DataArr, 1) '1 to LastRow
Output = Template 'initialize with the template!
For iVar = LBound(Variables) To UBound(Variables) ' 0 to 2
Output = Replace(Output, Variables(iVar), DataArr(iRow, iVar + 1))
Next iVar
Debug.Print Output
Next iRow
End Sub

Related

VBA Split() function not working when ":" is the delimiter

I'm trying to use the split() function to loop through a specified range and split all strings when a ":" is encountered, and replace the existing value with the split value.
Dim k As Integer
Dim lRow as Long
Dim startZip_col As Long
Dim startZip_str As String
Dim startZip_result() As String
Dim startZip_decomposed As Variant
For k = 2 To lRow
startZip_str = Cells(k, startZip_col).Value
startZip_result = Split(startZip_str, ":")
For Each startZip_decomposed In startZip_result
Cells(k, startZip_col) = startZip_result(1)
Next
Next k
a example of the values i want to split are:
abc:1234
abc:5678
def:3456
tried debug.print to pinpoint where the errors are, but column value is correctly identified, loop looks fine, not sure where went wrong
Logic:
Where is lRow. startZip_col inititalized? Define and initialize your variables/Objects correctly.
Fully qualify the cells else it may refer to active sheet which may not be the sheet you think it is. For example ws.Cells(k, startZip_col).Value where ws is the relevant worksheet.
Before splitting, check for the existence of :
Code:
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long
Dim i As Long, j As Long
Dim ZipCol As Long
Dim ZipString As String
Dim ZipResult As Variant
'~~> Change this to the relevant sheet
Set ws = Sheet1
'~~> Change this to the releavant column
ZipCol = 1
With ws
'~~> Get the last row in Col A. Change to relevant column
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = 2 To lRow
ZipString = .Cells(i, ZipCol).Value
'~~> Check if the string contains ":"
If InStr(1, ZipString, ":") Then
ZipResult = Split(ZipString, ":")
'.Cells(1, ZipCol) = ZipResult(1)
'~~> For testing
For j = LBound(ZipResult) To UBound(ZipResult)
Debug.Print ZipResult(j)
Next j
End If
Next i
End With
End Sub

Convert Excel Array formula into VBA code

I have two set of range named as LIST_KEY and LIST_CAT. In Column A, user will add some data which will contain one of the one of the text from LIST_KEY. I would like to get corresponding Category list from LIST_CAT depends upon the Key value
I am using below VBA code to achieve this. This include a Array formula.
Sub match()
Dim ss As Workbook
Dim test As Worksheet
Set ss = Excel.Workbooks("test.xlsm")
Set test = ss.Worksheets("Sheet1")
For i = 2 To test.Cells(Rows.Count, "A").End(xlUp).Row
Cells(i, "B").FormulaArray = "=INDEX(LIST_CAT,MATCH(TRUE,ISNUMBER(SEARCH(LIST_KEY,RC[-1])),0))"
Cells(i, "B").Formula = Cells(i, "B").Value
Next i
End Sub
This code works perfect if there is less data to fetch. But in my original use case, I will have around 8000 rows. Due to this large number of columns excel will go to not responding state after 2-3 minutes.
Instead of adding Array formula to column B, Is there anyway to convert that into VBA to run this faster. Sorry, I am new to this VBA stuff and dont have much experience
Try the following code, which uses arrays instead of worksheet formulas...
Option Explicit
Sub GetCategories()
Dim sourceWorkbook As Workbook
Set sourceWorkbook = Workbooks("test.xlsm")
Dim sourceWorksheet As Worksheet
Set sourceWorksheet = sourceWorkbook.Worksheets("Sheet1")
Dim lookupArray As Variant
lookupArray = sourceWorkbook.Names("LIST_KEY").RefersToRange.Value
Dim returnArray As Variant
returnArray = sourceWorkbook.Names("LIST_CAT").RefersToRange.Value
Dim tableArray As Variant
Dim lastRow As Long
With sourceWorksheet
lastRow = .Cells(.Rows.Count, "a").End(xlUp).Row
tableArray = .Range("A2:B" & lastRow).Value
End With
Dim desc As String
Dim i As Long
Dim j As Long
For i = LBound(tableArray, 1) To UBound(tableArray, 1)
desc = tableArray(i, 1)
For j = LBound(lookupArray, 1) To UBound(lookupArray, 1)
If InStr(1, desc, lookupArray(j, 1), vbTextCompare) > 0 Then
tableArray(i, 2) = returnArray(j, 1)
Exit For
End If
Next j
Next i
sourceWorksheet.Range("B2").Resize(UBound(tableArray, 1), 1).Value = Application.Index(tableArray, 0, 2)
End Sub

Unable to populate unique values in third sheet comparing the values of the second sheet to the first one

I've got three sheets - main,specimen and output in an excel workbook. The sheet main and speciment contain some information. Some of the information in two sheets are identical but few of them are not. My intention is to paste those information in output which are available in speciment but not in main.
I've tried like [currently it fills in lots of cells producing duplicates]:
Sub getData()
Dim cel As Range, celOne As Range, celTwo As Range
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("main")
Dim ws1 As Worksheet: Set ws1 = ThisWorkbook.Worksheets("specimen")
Dim ws2 As Worksheet: Set ws2 = ThisWorkbook.Worksheets("output")
For Each cel In ws.Range("A2:A" & ws.Cells(Rows.Count, 1).End(xlUp).row)
For Each celOne In ws1.Range("A2:A" & ws1.Cells(Rows.Count, 1).End(xlUp).row)
If cel(1, 1) <> celOne(1, 1) Then ws2.Range("A" & Rows.Count).End(xlUp).Offset(1, 0).value = celOne(1, 1)
Next celOne
Next cel
End Sub
main contains:
UNIQUE ID FIRST NAME LAST NAME
A0000477 RICHARD NOEL AARONS
A0001032 DON WILLIAM ABBOTT
A0290191 REINHARDT WESTER CARLSON
A0290284 RICHARD WARREN CARLSON
A0002029 RAYMOND MAX ABEL
A0002864 DARRYL SCOTT ABLING
A0003916 GEORGES YOUSSEF ACCAOUI
specimen contains:
UNIQUE ID FIRST NAME LAST NAME
A0288761 ROBERT HOWARD CARLISLE
A0290284 RICHARD WARREN CARLSON
A0290688 THOMAS A CARLSTROM
A0002029 RAYMOND MAX ABEL
A0002864 DARRYL SCOTT ABLING
output should contain [EXPECTED]:
UNIQUE ID FIRST NAME LAST NAME
A0288761 ROBERT HOWARD CARLISLE
A0290688 THOMAS A CARLSTROM
How can I achieve that?
If you have the latest version of Excel, with the FILTER function and dynamic arrays, you can do this with an Excel formula.
I changed your Main and Specimen data into tables.
On the Output worksheet you can then enter this formula into a single cell:
=FILTER(specTbl,ISNA(MATCH(specTbl[UNIQUE ID],mnTbl[UNIQUE ID],0)))
The remaining fields will autopopulate with the results.
For a VBA solution, I like to use Dictionaries, and VBA arrays for speed.
'set reference to microsoft scripting runtime
' or use late-binding
Option Explicit
Sub findMissing()
Dim wsMain As Worksheet, wsSpec As Worksheet, wsOut As Worksheet
Dim dN As Dictionary, dM As Dictionary
Dim vMain As Variant, vSpec As Variant, vOut As Variant
Dim I As Long, v As Variant
With ThisWorkbook
Set wsMain = .Worksheets("Main")
Set wsSpec = .Worksheets("Specimen")
Set wsOut = .Worksheets("Output")
End With
'Read data into vba arrays for processing speed
With wsMain
vMain = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=3)
End With
With wsSpec
vSpec = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=3)
End With
'add ID to names dictionary
Set dN = New Dictionary
For I = 2 To UBound(vMain, 1)
dN.Add Key:=vMain(I, 1), Item:=I
Next I
'add missing ID's to missing dictionary
Set dM = New Dictionary
For I = 2 To UBound(vSpec, 1)
If Not dN.Exists(vSpec(I, 1)) Then
dM.Add Key:=vSpec(I, 1), Item:=WorksheetFunction.Index(vSpec, I, 0)
End If
Next I
'write results to output array
ReDim vOut(0 To dM.Count, 1 To 3)
vOut(0, 1) = "UNIQUE ID"
vOut(0, 2) = "FIRST NAME"
vOut(0, 3) = "LAST NAME"
I = 0
For Each v In dM.Keys
I = I + 1
vOut(I, 1) = dM(v)(1)
vOut(I, 2) = dM(v)(2)
vOut(I, 3) = dM(v)(3)
Next v
Dim R As Range
With wsOut
Set R = .Cells(1, 1)
Set R = R.Resize(UBound(vOut, 1) + 1, UBound(vOut, 2))
With R
.EntireColumn.Clear
.Value = vOut
.Style = "Output"
.EntireColumn.AutoFit
End With
End With
End Sub
Both show the same result (except the formula solution does not bring over the column headers; but you can do that with a formula =mnTbl[#Headers] in the cell above the original formula above).
Another option is to join the values of each row in each range and store them in arrays.
Then compare arrays and output the unique values.
In this case, your uniques come from evaluating the whole row, and not just the Unique ID.
Please read code's comments and adjust it to fit your needs.
Public Sub OutputUniqueValues()
Dim mainSheet As Worksheet
Dim specimenSheet As Worksheet
Dim outputSheet As Worksheet
Dim mainRange As Range
Dim specimenRange As Range
Dim mainArray As Variant
Dim specimenArray As Variant
Dim mainFirstRow As Long
Dim specimenFirstRow As Long
Dim outputCounter As Long
Set mainSheet = ThisWorkbook.Worksheets("main")
Set specimenSheet = ThisWorkbook.Worksheets("specimen")
Set outputSheet = ThisWorkbook.Worksheets("output")
' Row at which the output range will be printed (not including headers)
outputCounter = 2
' Process main data ------------------------------------
' Row at which the range to be evaluated begins
mainFirstRow = 2
' Turn range rows into array items
mainArray = ProcessRangeData(mainSheet, mainFirstRow)
' Process specimen data ------------------------------------
' Row at which the range to be evaluated begins
specimenFirstRow = 2
' Turn range rows into array items
specimenArray = ProcessRangeData(specimenSheet, specimenFirstRow)
' Look for unique values and output results in sheet
OutputUniquesFromArrays outputSheet, outputCounter, mainArray, specimenArray
End Sub
Private Function ProcessRangeData(ByVal dataSheet As Worksheet, ByVal firstRow As Long) As Variant
Dim dataRange As Range
Dim evalRowRange As Range
Dim lastRow As Long
Dim counter As Long
Dim dataArray As Variant
' Get last row in sheet (column 1 = column A)
lastRow = dataSheet.Cells(dataSheet.Rows.Count, 1).End(xlUp).Row
' Set the range of specimen sheet
Set dataRange = dataSheet.Range("A" & firstRow & ":C" & lastRow)
' Redimension the array to the number of rows in range
ReDim dataArray(dataRange.Rows.Count)
counter = 0
' Join each row values so it's easier to compare them later and add them to an array
For Each evalRowRange In dataRange.Rows
' Use Trim function if you want to omit the first and last characters if they are spaces
dataArray(counter) = Trim(evalRowRange.Cells(1).Value) & "|" & Trim(evalRowRange.Cells(2).Value) & "|" & Trim(evalRowRange.Cells(3).Value)
counter = counter + 1
Next evalRowRange
ProcessRangeData = dataArray
End Function
Private Sub OutputUniquesFromArrays(ByVal outputSheet As Worksheet, ByVal outputCounter As Long, ByVal mainArray As Variant, ByVal specimenArray As Variant)
Dim specimenFound As Boolean
Dim specimenCounter As Long
Dim mainCounter As Long
' Look for unique values ------------------------------------
For specimenCounter = 0 To UBound(specimenArray)
specimenFound = False
' Check if value in specimen array exists in main array
For mainCounter = 0 To UBound(mainArray)
If specimenArray(specimenCounter) = mainArray(mainCounter) Then specimenFound = True
Next mainCounter
If specimenFound = False Then
' Write values to output sheet
outputSheet.Range("A" & outputCounter).Value = Split(specimenArray(specimenCounter), "|")(0)
outputSheet.Range("B" & outputCounter).Value = Split(specimenArray(specimenCounter), "|")(1)
outputSheet.Range("C" & outputCounter).Value = Split(specimenArray(specimenCounter), "|")(2)
outputCounter = outputCounter + 1
End If
Next specimenCounter
End Sub

How do I turn a validation list from a cell into a list with VBA

I have a sheet that I need to paste data to according to the validation lists in those sheets. In the sheet, there are many columns each with their own data validation list - some are written directly as "yes;no" others are references "='$$VALUES$$'!$IJ$1:$IJ$12".
What I need is to find a way to add each item in each list to an array. Using the code below I could find the references above.
Debug.Print Cells(2, 6).Validation.Formula1
Is there any elegant way to store the output as a list containing each valid input. My only idea so far is to first check which type of output I get, and then if it is the list form of "yes;no" then look for the number of ; and then split it item by item. And in case its the sheet range reference split it by sheet and range and convert that range to an array.
Something like this, will do it. I'd set a range rather than using activecell, and also check validation is present to reduce your errors.
Sub get_val_lists()
Dim arrOutput() As Variant
If Left(ActiveCell.Validation.Formula1, 1) <> "=" Then
arrOutput = Split(ActiveCell.Validation.Formula1, ",")
Else
arrOutput = Application.Transpose( _
Range(Mid(ActiveCell.Validation.Formula1, 2)).value)
End If
End Sub
I was a bit pressed for time so I ended up doing an inelegant solution myself. Posting it here in case somebody else runs into the same problem.
Sub ValidList()
Dim strFormula As String
Dim intLastSemi As Integer
Dim intCurSemi As Integer
Dim intSemi As Integer
Dim aryList() As Variant
Dim intLen As Integer
Dim blnCont As Boolean
Dim strSheet As String
Dim strRange As String
Dim intSplit As Integer
Dim ws As Worksheet
Dim rng As Range
Dim e As Variant
Dim Row As Integer
Dim Col As Integer
'This is just an example, turning it into a fucntion based on row and col later
'so now my test validation list is just in A1
Row = 1
Col = 1
strFormula = Cells(Row, Col).Validation.Formula1
intLen = Len(strFormula)
If InStr(1, strFormula, "=") Then 'Sheet reference
intSplit = InStr(1, strFormula, "!")
strSheet = Right(Left(strFormula, intSplit - 1), intLen - intSplit - 3)
strRange = Right(strFormula, intLen - intSplit)
Set ws = Worksheets(strSheet)
Set rng = ws.Range(strRange)
aryList() = rng
ElseIf Not InStr(1, strFormula, ";") Then 'Hardcoded list
intSemi = 0
intLastSemi = 0
blnCont = True
While blnCont
intCurSemi = InStr(intLastSemi + 1, strFormula, ";")
If intCurSemi <> 0 Then
intSemi = intSemi + 1
ReDim Preserve aryList(intSemi)
aryList(intSemi) = Right(Left(strFormula, intCurSemi - 1), intCurSemi - intLastSemi - 1)
intLastSemi = intCurSemi
ElseIf intCurSemi = 0 Then
intSemi = intSemi + 1
ReDim Preserve aryList(intSemi)
aryList(intSemi) = Right((strFormula), intLen - intLastSemi)
blnCont = False
End If
Wend
End If
'For my attempt at passing the array to a function
'For Each e In aryList
' MsgBox e
'Next
'ReDim ValidList(UBound(aryList))
'ValidList = aryList
End Sub

Excel VBA parse column, extract all substrings

I'm trying to parse a column that contains data in the following format in each cell -
pull: test1
or
pull: test2|pull: test3|.....
or
other: blah...
I only want a grab each "Pull: test" and place 1 in each row in a new worksheet like below, and ignore any parts of the cell that don't begin with "pull: " -
pull: test1
pull: test2
pull: test3
...
What I have so far just pulls the entire column and pastes into the same spreadsheet, I'm not sure how to separate the items in each cell into their own rows. I also can't get it to pull to a different worksheet correctly either (commented out my attempt)
Sub InStrDemo()
Dim lastrow As Long
Dim i As Integer, icount As Integer
'Sheets.Add.Name = "TEST"
lastrow = ActiveSheet.Range("A10000").End(xlUp).Row
For i = 1 To lastrow
If InStr(1, LCase(Range("E" & i)), "pull:") <> 0 Then
icount = icount + 1
'Sheets("TEST").Range("A" & icount & ":E" & icount) = Worksheets("SearchResults").Range("A" & i & ":E" & i).Value
Range("L" & icount) = Range("E" & i).Value
End If
Next i
End Sub
Untested, written on mobile.
Option Explicit
Sub testDemo()
Dim sourceSheet as worksheet
Set sourceSheet = ActiveSheet ' would be more reliable to qualify the workbook and worksheet by name'
Dim outputSheet as worksheet
Set outputSheet = thisworkbook.worksheets.add
Dim lastRow As Long
lastrow = sourceSheet.Range("A10000").End(xlUp).Row
' I assume column E needs to be parsed'
Dim arrayOfValues() as variant
arrayOfValues = sourceSheet.range("E1:E" & lastRow)
Dim rowIndex as long
Dim columnIndex as long
Dim splitString() as string
Dim cumulativeOffset as long
Dim toJoin(0 to 1) as string
toJoin(0) = "pull: test" ' Might speed up string concatenation below'
Dim outputArray() as string
With outputsheet.range("A1") ' The first row you want to start stacking from'
For rowIndex = 1 to lastRow
' Single dimensional, 0-based array'
splitString = VBA.strings.split(vba.strings.lcase$(arrayOfValues(rowIndex,1)), "pull: test",-1, vbbinarycompare)
Redim outputArray(1 to (ubound(splitString)+1), 1 to 1)
For columnIndex = lbound(splitString) to ubound(splitString)
toJoin(1) = splitString(columnIndex)
Outputarray(columnIndex+1,1) = VBA.strings.join(toJoin, vbnullstring)
Next columnIndex
'Instead of splitting upon a delimiter, then prepending the delimiter to each array element (as is done above), you could repeatedly call instr(), use mid$() to extract the sub-string, then increase the argument passed to the "Start" parameter in instr() (effectively moving from start to end of the string) -- until instr() returns 0. Then move on to the next string in the outer loop.'
.offset(cumulativeOffset,0).resize(Ubound(outputArray, 1), 1).value2 = outputArray
cumulativeOffset = cumulativeOffset + ubound(splitString)
Next rowIndex
End Sub

Resources