This question already has answers here:
Excel UDF for capturing numbers within characters
(4 answers)
Closed 4 years ago.
I need to extract the numbers from a string of text and I'm not quite sure how to do it. The code I've attached below is very preliminary and most likely can be done more elegantly. A sample of the string I'm trying to parse is as follows:
"ID CSys ID Set ID Set Value Set Title 7026..Plate Top MajorPrn Stress 7027..Plate Top MinorPrn Stress 7033..Plate Top VonMises Stress"
I need to pull the numbers 7026, 7027, and 7033. The string will vary in length and the number of values that I'll need to pull will also vary. Any help would be much appreciated. Thanks!
Dim WrdArray() As String
Dim txtstrm As TextStream
Dim line As String
Dim clm As Long
Dim Rw As Long
'------------------------------------------------------------
Dim i As Long
Dim strPath As String
Dim strLine As String
Dim count, count1 As Integer
Dim holder As String
Dim smallSample As String
count = 0
count1 = 1
holder = ""
'Remove Filters and Add Custom Filters
Call Application.FileDialog(msoFileDialogOpen).Filters.Clear
Call Application.FileDialog(msoFileDialogOpen).Filters.Add("Text Files", "*.txt")
Call Application.FileDialog(msoFileDialogOpen).Filters.Add("Dat Files", "*.dat")
'only allow the user to select one file
Application.FileDialog(msoFileDialogOpen).AllowMultiSelect = False
'make the file dialog visible to the user
intChoice = Application.FileDialog(msoFileDialogOpen).Show
'determine what choice the user made
If intChoice <> 0 Then
'get the file path selected by the user
strPath = Application.FileDialog(msoFileDialogOpen).SelectedItems(1)
End If
'------------------------------------------------------------
If strPath <> "" Then
Set txtstrm = FSO.OpenTextFile(strPath)
Else
MsgBox "No file selected.", vbExclamation
Exit Sub
End If
Rw = 1
Do Until txtstrm.AtEndOfStream
line = txtstrm.ReadLine
clm = 1
WrdArray() = Split(line, " ") 'Change with ; if required
For Each wrd In WrdArray()
If Rw = 1 Then
Do While count <> Len(wrd)
smallSample = Left(wrd, 1)
If smallSample = "0" Or smallSample = "1" Or smallSample = "2" Or smallSample = "3" Or smallSample = "4" _
Or smallSample = "5" Or smallSample = "6" Or smallSample = "7" Or smallSample = "8" _
Or smallSample = "9" Then
holder = holder & smallSample
Else
If holder <> "" Then
Cells(count1, 1) = holder
count1 = count1 + 1
End If
holder = ""
End If
wrd = Right(wrd, Len(wrd) - 1)
clm = clm + 4
ActiveSheet.Cells(Rw, clm) = holder
Loop
Else
ActiveSheet.Cells(Rw, clm) = wrd
clm = clm + 1
End If
Next wrd
Rw = Rw + 1
Loop
txtstrm.Close
End Sub
You can use Regular Expressions.
Sub ExtractNumbers()
Dim str As String, regex As regExp, matches As MatchCollection, match As match
str = "ID CSys ID Set ID Set Value Set Title 7026..Plate Top MajorPrn Stress 7027..Plate Top MinorPrn Stress 7033..Plate Top VonMises Stress"
Set regex = New regExp
regex.Pattern = "\d+" '~~~> Look for variable length numbers only
regex.Global = True
If (regex.Test(str) = True) Then
Set matches = regex.Execute(str) '~~~> Execute search
For Each match In matches
Debug.Print match.Value '~~~> Prints: 7026, 7027, 7033
Next
End If
End Sub
Make sure you reference the VBA regex library:
Open VBA editor
Tools > References...
Check Microsoft VBScript Regular Expression 5.5
To exact numbers in the form you want, try something like:
Sub dural()
Dim s As String, i As Long, L As Long, c As String, temp As String
s = [A1]
L = Len(s)
temp = ""
For i = 1 To L
c = Mid(s, i, 1)
If c Like "[0-9]" Then
temp = temp & c
Else
temp = temp & " "
End If
Next i
temp = "'" & Application.WorksheetFunction.Trim(temp)
temp = Replace(temp, " ", ",")
[B1] = temp
End Sub
You can use this function that splits the "words and test for numeric:
Function numfromstring(str As String) As String
Dim strarr() As String
str = Replace(str, ".", " ")
strarr = Split(str)
Dim i As Long
For i = 0 To UBound(strarr)
If IsNumeric(strarr(i)) Then
numfromstring = numfromstring & "," & strarr(i)
End If
Next i
numfromstring = Mid(numfromstring, 2)
End Function
You would call it from the worksheet with a formula:
=numfromstring(A1)
Or from vba like this:
Sub try()
Dim str As String
str = "ID CSys ID Set ID Set Value Set Title 7026..Plate Top MajorPrn Stress 7027..Plate Top MinorPrn Stress 7033..Plate Top VonMises Stress"
Dim out As String
out = numfromstring(str)
Debug.Print out
End Sub
If you have Office 365 Excel you can use this array formula:
=TEXTJOIN(",",TRUE,IF(ISNUMBER(--TRIM(MID(SUBSTITUTE(SUBSTITUTE(A1,"."," ")," ",REPT(" ",99)),(ROW($1:$100)-1)*99+1,99))),TRIM(MID(SUBSTITUTE(SUBSTITUTE(A1,"."," ")," ",REPT(" ",99)),(ROW($1:$100)-1)*99+1,99)),""))
Being an array formula it needs to be confirmed with Ctrl-Shift-Enter instead of Enter when exiting edit mode:
Related
So... I have this form where people select different controls (We call safety measures controls, these are not content controls) from a listbox and add them to a list. This is in a repeating table. Each control has a heading label (either "engineering" "administrative" or "PPE" that I want to make bold and underlined but I want the options selected in the listboxes to be in normal formatting.
the portion of code that is printing this to the document looks like this:
Set tableSequence = ActiveDocument.Tables(1)
Set NewRow = tableSequence.Rows.Add
NewRow.Cells(5).Range.Text = "Engineering: " & MyString3 _
& vbCrLf & "Administrative: " _
& MyString4 & vbCrLf _
& "PPE: " & MyString5
I want the words Engineering, Administrative, and PPE to be bold and underlined, and the items represented by the MyString objects to appear in standard formatting. Thank you.
The string portion is as follows:
Private Sub CommandButton6_Click()
Dim tableSequence As Table
Dim NewRow As Row
Dim MyString5 As String
Dim v As Variant
Dim var3
Dim p As String
Dim M As Long
For var3 = 0 To ListBox7.ListCount - 1
If ListBox7.Selected(var3) = True Then
MyString5 = MyString5 & ListBox7.List(var3)
v = Split(MyString5, ",")
p = ""
For M = LBound(v) To UBound(v)
p = p + v(M)
If M Mod 3 = 2 Then
p = p + vbCr
Else
p = p + ","
End If
Next M
p = Left(p, Len(p) - 1)
Debug.Print p
End If
sorry for leaving that out
How to format a part (or multiple parts) of a Cell's Value in a Word table:
I have to admit i am not very fond of Word VBA, but i stitched this Sub together for you and it works in my test document. Adjust it to your needing.
Option Explicit
Sub asd()
Dim tableSequence As Table
Set tableSequence = ActiveDocument.Tables(1)
Dim NewRow As Row
Set NewRow = tableSequence.Rows.Add
NewRow.Cells(5).Range.Text = "Engineering: asd" & vbCrLf & "Administrative: vvv" & vbCrLf & "test" & vbCrLf & "PPE: blabla"
NewRow.Cells(5).Range.Bold = False
NewRow.Cells(5).Range.Underline = False
Dim keywordArr As Variant
keywordArr = Array("Engineering:", "Administrative:", "PPE:")
Dim keyword As Variant
Dim myRange As Variant
Dim startPos As Integer
Dim endPos As Integer
Dim length As Integer
Dim i As Integer
i = 1
For Each keyword In keywordArr
Do While InStr(1, myRange, keyword) = 0
Set myRange = NewRow.Cells(5).Range.Paragraphs(i).Range
i = i + 1
Loop
startPos = InStr(1, myRange, keyword)
startPos = myRange.Characters(startPos).Start
length = Len(keyword)
endPos = startPos + length
Set myRange = ActiveDocument.Range(startPos, endPos)
With myRange.Font
.Bold = True
.Underline = True
End With
Next keyword
End Sub
Below is a solution for the same thing in Excel:
First off you would have to write the text into the cell just like you already do.
Next would be to find the position of your keywords in the cell's value + the length of your keywords like so
startPos = Instr(1, NewRow.Cells(5), "Engineering:")
length = len("Engineering:")
Then you can set up the Font of the found substring via Range.Characters.Font
NewRow.Cells(5).Characters(startPos, Length).Font.Bold = True
NewRow.Cells(5).Characters(startPos, Length).Font.Underline = True
Now the elegant way would be to have an array of keywords and iterate through them to change the font for all them
Dim keywordArr As Variant
keywordArr = Array("Engineering:", "Administrative:", "PPE:")
Dim keyword As Variant
Dim startPos as Integer
Dim length as Integer
For Each keyword In keywordArr
startPos = InStr(1, NewRow.Cells(5), keyword)
length = Len(keyword)
With NewRow.Cells(5).Characters(startPos, Length).Font
.Bold = True
.Underline = True
End With
Next keyword
I have multiple rows, I need to join with "##" characters at end of every cell value,
I am able add this characters but at the end it is printing extra characters(##)
My excel file: From this excel file, I need to join the values by ## for each cell and feed into notepad
Excel File for Input
My output should be:(Actual and Expected)
Actual and Expected Output
Here Is my code:
sub join()
dim LRow as long
dim LCol as long
Dim str1 as string
Dim str2 as string
Dim ws1 As Worksheet
Set ws1 = ActiveWorkbook.Worksheets(1)
plik = ThisWorkbook.Path & "\" & "BL2ASIS" & ws1.Name & ".txt"
Open plik For Output As 2
With ws1
LRow = .Cells(.Rows.Count, 1).End(xlUp).Row
LCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
LCol = LCol - 2
slast = vbNullString
str2 = Join(Application.Transpose(Application.Transpose(.Cells(n, "A").Resize(1, 2).Value)), "")
str1 = str2 & Join(Application.Transpose(Application.Transpose(.Cells(n, "C").Resize(1, LCol).Value)), "##") & "##"
str1 = Replace(str1, "=", vbNullString)
str1 = Replace(str1, "####", "##")
Print #2, str1
End with
end sub
You could replace your line:
str1 = Replace(str1, "####", "##")
with:
Do Until Len(str1) = Len(Replace(str1, "####", "##"))
str1 = Replace(str1, "####", "##")
Loop
which will keep applying the replace until there is no point in doing so (i.e. the length doesn't change)
EDIT
Sorry to alter an accepted answer, but I've noticed that you might want to keep instances of #### if they occur somewhere other than at the end of the row. If you do then the following would be better, as it only trims the right-most characters:
Do Until Right(str1, 4) <> "####"
str1 = Left(str1, Len(str1) - 2)
Loop
The reason you are getting repeated characters is because you are joining empty array elements. An alternative to removing repeated delimiters is to use a UDF to only join non null values. Please see below for such a function.
Sub TestJoin()
Dim r As Range: Set r = Worksheets("Sheet1").Range("B1:B12")
Dim arr() As Variant
arr = Application.Transpose(r)
Debug.Print NonNullJoin(arr, "#") & "#"
End Sub
Function NonNullJoin(SourceArray() As Variant, Optional Delimiter As String = " ") As String
On Error Resume Next
Dim i As Long: For i = 0 To UBound(SourceArray)
If CStr(SourceArray(i)) <> "" Then NonNullJoin = _
IIf(NonNullJoin <> "", NonNullJoin & Delimiter & CStr(SourceArray(i)), CStr(SourceArray(i)))
Next i
End Function
Use regex to replace more than one instance.
Note:
If you want to replace repeats only at the end of the string then change regex pattern to (##){2,}$ . This will deal with 2 or more occurrences.
If only worried about two occurrences at end, use (##)\1$
Code:
Option Explicit
Sub TEST()
Dim testString As String, pattern As String
testString = "xxxxx####"
testString = RemoveChars(testString)
Debug.Print testString
End Sub
Public Function RemoveChars(ByVal inputString As String) As String
Dim regex As Object, tempString As String
Set regex = CreateObject("VBScript.RegExp")
With regex
.Global = True
.MultiLine = True
.IgnoreCase = False
.pattern = "(##){2,}"
End With
If regex.TEST(inputString) Then
RemoveChars = regex.Replace(inputString, "##")
Else
RemoveChars = inputString
End If
End Function
I was wondering how to remove duplicate names/text's in a cell. For example
Jean Donea Jean Doneasee
R.L. Foye R.L. Foyesee
J.E. Zimmer J.E. Zimmersee
R.P. Reed R.P. Reedsee D.E. Munson D.E. Munsonsee
While googling, I stumbled upon a macro/code, it's like:
Function RemoveDupes1(pWorkRng As Range) As String
'Updateby20140924
Dim xValue As String
Dim xChar As String
Dim xOutValue As String
Set xDic = CreateObject("Scripting.Dictionary")
xValue = pWorkRng.Value
For i = 1 To VBA.Len(xValue)
xChar = VBA.Mid(xValue, i, 1)
If xDic.exists(xChar) Then
Else
xDic(xChar) = ""
xOutValue = xOutValue & xChar
End If
Next
RemoveDupes1 = xOutValue
End Function
The macro is working, but it is comparing every letter, and if it finds any repeated letters, it's removing that.
When I use the code over those names, the result is somewhat like this:
Jean Dos
R.L Foyes
J.E Zimers
R.P edsDEMuno
By looking at the result I can make out it is not what I want, yet I got no clue how to correct the code.
The desired output should look like:
Jean Donea
R.L. Foye
J.E. Zimmer
R.P. Reed
Any suggestions?
Thanks in Advance.
Input
With the input on the image:
Result
The Debug.Print output
Regex
A regex can be used dynamically iterating on the cell, to work as a Find tool. So it will extract only the shortest match. \w*( OUTPUT_OF_EXTRACTELEMENT )\w*, e.g.: \w*(Jean)\w*
The Regex's reference must be enabled.
Code
Function EXTRACTELEMENT(Txt As String, n, Separator As String) As String
On Error GoTo ErrHandler:
EXTRACTELEMENT = Split(Application.Trim(Mid(Txt, 1)), Separator)(n - 1)
Exit Function
ErrHandler:
' error handling code
EXTRACTELEMENT = 0
On Error GoTo 0
End Function
Sub test()
Dim str As String
Dim objMatches As Object
Set objRegExp = CreateObject("VBScript.RegExp") 'New regexp
lastrow = ActiveSheet.Cells(ActiveSheet.Rows.Count, "A").End(xlUp).Row
For Row = 1 To lastrow
str = Range("A" & Row)
F_str = ""
N_Elements = UBound(Split(str, " "))
If N_Elements > 0 Then
For k = 1 To N_Elements + 1
strPattern = "\w*(" & EXTRACTELEMENT(CStr(str), k, " ") & ")\w*"
With objRegExp
.Pattern = strPattern
.Global = True
End With
If objRegExp.test(strPattern) Then
Set objMatches = objRegExp.Execute(str)
If objMatches.Count > 1 Then
If objRegExp.test(F_str) = False Then
F_str = F_str & " " & objMatches(0).Submatches(0)
End If
ElseIf k <= 2 And objMatches.Count = 1 Then
F_str = F_str & " " & objMatches(0).Submatches(0)
End If
End If
Next k
Else
F_str = str
End If
Debug.Print Trim(F_str)
Next Row
End Sub
Note that you can Replace the Debug.Print to write on the target
cell, if it is column B to Cells(Row,2)=Trim(F_str)
Explanation
Function
You can use this UDF, that uses the Split Function to obtain the element separated by spaces (" "). So it can get every element to compare on the cell.
Loops
It will loop from 1 to the number of elements k in each cell and from row 1 to lastrow.
Regex
The Regex is used to find the matches on the cell and Join a new string with the shortest element of each match.
This solution operates on the assumption that 'see' (or some other three-letter string) will always be on the end of the cell value. If that isn't the case then this won't work.
Function RemoveDupeInCell(dString As String) As String
Dim x As Long, ct As Long
Dim str As String
'define str as half the length of the cell, minus the right three characters
str = Trim(Left(dString, WorksheetFunction.RoundUp((Len(dString) - 3) / 2, 0)))
'loop through the entire cell and count the number of instances of str
For x = 1 To Len(dString)
If Mid(dString, x, Len(str)) = str Then ct = ct + 1
Next x
'if it's more than one, set to str, otherwise error
If ct > 1 Then
RemoveDupeInCell = str
Else
RemoveDupeInCell = "#N/A"
End If
End Function
My input file (flat text file) is as follows:
tom:ss1:ts1
dick:ss1:ts1
tom:ss2:ts2
dick:ss2:ts2
harry:ss1:ts1
tom:ss3:
harry::ts2
First col is employee name. Second col is softskill training and third is techskill training.
I want to read this file and create following structure "in memory" for being used in the later part of the code.
{
'dick': {
'soft_skill': ['ss1', 'ss2'],
'tech_skill': ['ts1', 'ts2']
},
'harry': {
'soft_skill': ['ss1'],
'tech_skill': ['ts1', 'ts2']
},
'tom': {
'soft_skill': ['ss1', 'ss2', 'ss3'],
'tech_skill': ['ts1', 'ts2']
}
}
Against the key 'tom' the value stored is a dictionary which is as below:
{
'soft_skill': ['ss1', 'ss2', 'ss3'],
'tech_skill': ['ts1', 'ts2']
}
Inside this dictionary, against the key 'soft_skill', the value is an array which is shown as ['ss1', 'ss2', 'ss3'].
Similar to 'soft_skill', the key 'tech_skill' holds the value as an array shown as ['ts1', 'ts2'].
How to create above structure in VBA?
I have used FSO to read the text to excel and define a named range for col1 as "name_rng" which is continued with following:
Set traininglist = CreateObject("Scripting.Dictionary")
For Each cell In Range("name_rng")
If Not traininglist.Exists(cell.Value) Then
traininglist.Add cell.Value, Cells(cell.Row, 2).Value & ";" & _
Cells(cell.Row, 3).Value
Else
traininglist(cell.Value) = traininglist(cell.Value) & "|" & _
Cells(cell.Row, 2).Value & ";" & Cells(cell.Row, 3).Value
End If
Next
x = traininglist.keys
y = traininglist.items
For i = 0 To UBound(x)
ActiveCell.Value = x(i)
ActiveCell.Offset(0, 1).Value = y(i)
ActiveCell.Offset(1, 0).Select
Next
Set traininglist = Nothing
end sub
This is how I have stored the values as (key,value) pair
tom => ss1;ts1|ss2;ts2|ss3;
dick => ss1;ts1|ss2;ts2
harry => ss1;ts1|;ts2
For instance, taking the values of 'tom', 'ss1;ts1' is the first set of softskill and techskill which is then further delimited by | to segregate between the further sets of training for respective emp...
The above method is sufficing the need but I have to further split the values basis the delimiters and use loops to access the values... I Think this is a workaround but not a authenticate solution...
Thus need to advise on how to create dictionary of dictionary of arrays.
It is possible to achieve that task using Data Types and arrays, please see my comments inside the code.
but, if in spite of it you wish to use a Dictionary, you can use collection (or nested collections) as the value of the dictionary: Create dictionary of lists in vba
Type Employee
soft_skill() As Variant
tech_skill() As Variant
name As String
End Type
Function GetEmployee(ByVal name As String, ByRef soft_skill As Variant, ByRef tech_skill As Variant) As Employee
GetEmployee.name = name
GetEmployee.soft_skill = soft_skill
GetEmployee.tech_skill = tech_skill
End Function
Sub Main()
' declare an array of 2 Employee for the example
Dim ar(1) As Employee
' add "TOM"
Dim soft_skill As Variant
soft_skill = Array("ss1", "ss2", "ss3")
Dim tech_skill As Variant
tech_skill = Array("ts1", "ts2")
ar(0) = GetEmployee("TOM", soft_skill, tech_skill)
' add "JOHN"
Dim soft_skill2 As Variant
soft_skill2 = Array("vb.net", "c++", "java")
Dim tech_skill2 As Variant
tech_skill2 = Array("c#", "vba")
ar(1) = GetEmployee("JOHN", soft_skill2, tech_skill2)
' loop trough the array
For i = 0 To UBound(ar)
MsgBox (ar(i).name & " ")
' show soft_skill
For j = 0 To UBound(ar(i).soft_skill)
MsgBox (ar(i).soft_skill(j))
Next j
' show tech_skill
For Z = 0 To UBound(ar(i).tech_skill)
MsgBox (ar(i).tech_skill(Z))
Next Z
Next i
' use like a dictionary (get TOM for example)
Dim p As Employee
p = pickEmp("TOM", ar)
' show tom name
MsgBox (p.name)
' show tom soft_skills
For i = 0 To UBound(p.soft_skill)
MsgBox (p.soft_skill(i))
Next
' show tom tech_skill
For i = 0 To UBound(p.tech_skill)
MsgBox (p.tech_skill(i))
Next
End Sub
' return employee by name parameter from employee array
Private Function pickEmp(ByVal name As String, ByRef empArray() As Employee) As Employee
Dim index As Integer
index = -1
For i = 0 To UBound(empArray)
If empArray(i).name = name Then
index = i
Exit For
End If
Next i
If index = -1 Then
MsgBox ("there is no employee called " & name)
End If
pickEmp = empArray(index)
End Function
Try the following macro...
Sub test()
Dim dicNames As Object
Dim dicSkills As Object
Dim strPathAndFilename As String
Dim strTextLine As String
Dim intFileNum As Integer
Dim arrData() As String
Dim strName As String
Dim strSoftSkill As String
Dim strTechSkill As String
Dim intField As Integer
Dim arr() As String
Dim i As Long
strPathAndFilename = "c:\users\domenic\desktop\sample.txt"
If Len(Dir(strPathAndFilename, vbNormal)) = 0 Then
MsgBox "File not found.", vbExclamation
Exit Sub
End If
Set dicNames = CreateObject("Scripting.Dictionary")
dicNames.CompareMode = 1 'TextCompare
intFileNum = FreeFile()
Open strPathAndFilename For Input As intFileNum
Do Until EOF(intFileNum)
Line Input #intFileNum, strTextLine
If Len(strTextLine) > 0 Then
strName = ""
strSoftSkill = ""
strTechSkill = ""
arrData() = Split(strTextLine, ":")
For intField = LBound(arrData) To UBound(arrData)
Select Case intField
Case 0: strName = Trim(Split(strTextLine, ":")(intField))
Case 1: strSoftSkill = Trim(Split(strTextLine, ":")(intField))
Case 2: strTechSkill = Trim(Split(strTextLine, ":")(intField))
End Select
Next intField
If Not dicNames.Exists(strName) Then
Set dicSkills = CreateObject("Scripting.Dictionary")
dicSkills.CompareMode = 1 'TextCompare
If Len(strSoftSkill) > 0 Then
dicSkills.Add "Soft_Skills", strSoftSkill
End If
If Len(strTechSkill) > 0 Then
dicSkills.Add "Tech_Skills", strTechSkill
End If
dicNames.Add strName, dicSkills
Else
If Len(strSoftSkill) > 0 Then
dicNames(strName).Item("Soft_Skills") = dicNames(strName).Item("Soft_Skills") & "|" & strSoftSkill
End If
If Len(strTechSkill) > 0 Then
dicNames(strName).Item("Tech_Skills") = dicNames(strName).Item("Tech_Skills") & "|" & strTechSkill
End If
End If
End If
Loop
Close intFileNum
'List soft skills for Tom
arr() = Split(dicNames("tom").Item("Soft_Skills"), "|")
If UBound(arr) <> -1 Then
For i = LBound(arr) To UBound(arr)
Debug.Print Trim(arr(i))
Next i
Else
MsgBox "No soft skills listed for Tom.", vbInformation
End If
Set dicNames = Nothing
Set dicSkills = Nothing
End Sub
I need to sync up the values in the curly braces {} found in column C and put them against the user id in column F as seen below.
E.g. on the Emails sheet
becomes this on a new sheet
Sub CopyConditional()
Dim wshS As Worksheet
Dim WhichName As String
Set wshS = ActiveWorkbook.Sheets("Emails")
WhichName = "NewSheet"
Const NameCol = "C"
Const FirstRow = 1
Dim LastRow As Long
Dim SrcRow As Long
Dim TrgRow As Long
Dim wshT As Worksheet
Dim cpt As String
Dim user As String
Dim computers() As String
Dim computer As String
On Error Resume Next
Set wshT = Worksheets(WhichName)
If wshT Is Nothing Then
Set wshT = Worksheets.Add(After:=wshS)
wshT.Name = WhichName
End If
On Error GoTo 0
If wshT.Cells(1, NameCol).value = "" Then
TrgRow = 1
Else
TrgRow = wshT.Cells(wshT.Rows.Count, NameCol).End(xlUp).Row + 1
End If
LastRow = wshS.Cells(wshS.Rows.Count, NameCol).End(xlUp).Row
For SrcRow = FirstRow To LastRow
cpt = wshS.Range("C" & SrcRow).value
user = wshS.Range("F" & SrcRow).value
If InStr(cpt, ":") Then
cpt = Mid(cpt, InStr(1, cpt, ":") + 1, Len(cpt))
End If
If InStr(cpt, ";") Then
computers = Split(cpt, ";")
For i = 0 To UBound(computers)
If computers(i) <> "" Then
wshT.Range("A" & TrgRow).value = user
wshT.Range("B" & TrgRow).value = Mid(Left(computers(i), Len(computers(i)) - 1), 2)
TrgRow = TrgRow + 1
End If
Next
Else
computer = cpt
If computer <> "" Then
wshT.Range("A" & TrgRow).value = user
wshT.Range("B" & TrgRow).value = Mid(Left(computer, Len(computer) - 1), 2)
TrgRow = TrgRow + 1
End If
End If
Next SrcRow
End Sub
I managed to resolve it with the above code but there are 3 niggling issues:
1) The first curly brace is always copied, how do I omit this so something like {Computer1 looks like Computer 1
2) Where there are two computers in a row, then the output looks something like this:
when it should really be split into two different rows i.e.
User 1 | Computer 1
User 1 | Computer 2
3) If there is text after the last curly brace with text in it e.g. {Computer1};{Computer2};Request submitted then that text is added as a new row, I don't want this, I want it to be omitted e.g.
should just be:
User 1 | Computer 1
User 1 | Computer 2
How do I go about rectifying these issues?
Try this:
Sub Collapse()
Dim uRng As Range, cel As Range
Dim comps As Variant, comp As Variant, r As Variant, v As Variant
Dim d As Dictionary '~~> Early bind, for Late bind use commented line
'Dim d As Object
Dim a As String
With Sheet1 '~~> Sheet that contains your data
Set uRng = .Range("F1", .Range("F" & .Rows.Count).End(xlUp))
End With
Set d = CreateObject("Scripting.Dictionary")
With d
For Each cel In uRng
a = Replace(cel.Offset(0, -3), "{", "}")
comps = Split(a, "}")
Debug.Print UBound(comps)
For Each comp In comps
If InStr(comp, "Computer") <> 0 _
And Len(Trim(comp)) <= 10 Then '~~> I assumed max Comp# is 99
If Not .Exists(cel) Then
.Add cel, comp
Else
If IsArray(.Item(cel)) Then
r = .Item(cel)
ReDim Preserve r(UBound(r) + 1)
r(UBound(r)) = comp
.Item(cel) = r
Else
r = Array(.Item(cel), comp)
.Item(cel) = r
End If
End If
End If
Next
Next
End With
For Each v In d.Keys
With Sheet2 '~~> sheet you want to write your data to
If IsArray(d.Item(v)) Then
.Range("A" & .Rows.Count).End(xlUp).Offset(1, 0) _
.Resize(UBound(d.Item(v)) + 1) = v
.Range("B" & .Rows.Count).End(xlUp).Offset(1, 0) _
.Resize(UBound(d.Item(v)) + 1) = Application.Transpose(d.Item(v))
Else
.Range("A" & .Rows.Count).End(xlUp).Offset(1, 0) = v
.Range("B" & .Rows.Count).End(xlUp).Offset(1, 0) = d.Item(v)
End If
End With
Next
Set d = Nothing
End Sub
Above code uses Replace and Split Function to pass your string to array.
a = Replace(cel.Offset(0, -3), "{", "}") '~~> standardize delimiter
comps = Split(a, "}") '~~> split using standard delimiter
Then information are passed to dictionary object using User as key and computers as items.
We filter the items passed to dictionary using Instr and Len Function
If InStr(comp, "Computer") <> 0 _
And Len(Trim(comp)) <= 10 Then
As I've commented, I assumed your max computer number is 99.
Else change 10 to whatever length you need to check.
Finally we return the dictionary information to the target worksheet.
Note: You need to add reference to Microsoft Scripting Runtime if you prefer early bind
Result: I tried it on a small sample data patterned on how I see it in you SS.
So assuming you have this data in Sheet1:
Will output data in Sheet2 like this:
I use a custom parse function for this type of operation:
Sub CopyConditional()
' some detail left out
Dim iRow&, Usern$, Computer$, Computers$
For iRow = ' firstrow To lastrow
Usern = Sheets("Emails").Cells(iRow, "F")
Computers = Sheets("Emails").Cells(iRow, "C")
Do
Computer = zParse(Computers) ' gets one computer
If Computer = "" Then Exit Do
' Store Computer and Usern
Loop
Next iRow
End Sub
Function zParse$(Haystack$) ' find all {..}
Static iPosL& '
Dim iPosR&
If iPosL = 0 Then iPosL = 1
iPosL = InStr(iPosL, Haystack, "{") ' Left
If iPosL = 0 Then Exit Function ' no more
iPosR = InStr(iPosL, Haystack, "}") ' Right
If iPosR = 0 Then MsgBox "No matching }": Stop
zParse = Mid$(Haystack, iPosL + 1, iPosR - iPosL - 1)
iPosL = iPosR
End Function
1) Use the Mid function to drop the first character:
str = "{Computer1"
str = Mid(str,2)
now str = "Computer1"
2) You can use the Split function to separate these out and combine with the Mid function above
str = "{Computer1}{Computer2}"
splt = Split(str,"}")
for a = 0 to Ubound(splt)
result = Mid(splt(a),2)
next a
3) Add a conditional statement to the above loop
str = "{Computer1}{Computer2}"
splt = Split(str,"}")
for a = 0 to Ubound(splt)
if Left(splt(a),1) = "{" then result = Mid(splt(a),2)
next a
Use this loop and send each result to the desired cell (in the for-next loop) and you should be good to go.