So apparently here this macro fetches specific email addresses from inbox as well as sent items along with email addresses from cc,bcc
the problem is it takes a whole lot of time and i mean if a person has 2k emails he might have to wait for 3 to 4 hours .
Check some sources how to make code faster i got to know about restrict function when applied through DASL filter and limit number of items in a loop. i applied the same but the result is still the same and fetching is still slow .
As new into VBA i dont know all about optimization and still learning.
Any other sources or ways to make the fetching and execution faster ?
code given for reference
Option Explicit
Sub GetInboxItems()
'all vars declared
Dim ol As Outlook.Application
Dim ns As Outlook.Namespace
Dim fol As Outlook.Folder
Dim i As Object
Dim mi As Outlook.MailItem
Dim n As Long
Dim seemail As String
Dim seAddress As String
Dim varSenders As Variant
'for sent mails
Dim a As Integer
Dim b As Integer
Dim objitem As Object
Dim take As Outlook.Folder
Dim xi As Outlook.MailItem
Dim asd As String
Dim arr As Variant
Dim K As Long
Dim j As Long
Dim vcc As Variant
Dim seemail2 As String
Dim seAddress2 As String
Dim varSenders2 As Variant
Dim strFilter As String
Dim strFilter2 As String
'screen wont refresh untill this is turned true
Application.ScreenUpdating = False
'now assigning the variables and objects of outlook into this
Set ol = New Outlook.Application
Set ns = ol.GetNamespace("MAPI")
Set fol = ns.GetDefaultFolder(olFolderInbox)
Set take = ns.GetDefaultFolder(olFolderSentMail)
Range("A3", Range("A3").End(xlDown).End(xlToRight)).Clear
n = 2
strFilter = "#SQL=" & Chr(34) & "urn:schemas:httpmail:fromemail" & Chr(34) & " like '%" & seemail & "'"
strFilter2 = "#SQL=" & Chr(34) & "urn:schemas:httpmail:sentitems" & Chr(34) & " like '%" & seemail2 & "'"
'this one is for sent items folder where it fetches the emails from particular people
For Each objitem In take.Items.Restrict(strFilter2)
If objitem.Class = olMail Then
Set xi = objitem
n = n + 1
seemail2 = Worksheets("Inbox").Range("D1")
varSenders2 = Split(seemail2, ";")
For K = 0 To UBound(varSenders2)
'this is the same logic as the inbox one where if mail is found and if the mail is of similar kind then and only it will return the same
If xi.SenderEmailType = "EX" Then
seAddress2 = xi.Sender.GetExchangeUser.PrimarySmtpAddress
If InStr(1, seAddress2, varSenders2(K), vbTextCompare) Then
Cells(n, 1).Value = xi.Sender.GetExchangeUser().PrimarySmtpAddress
Cells(n, 2).Value = xi.SenderName
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End If
'this is the smpt address (regular address)
ElseIf xi.SenderEmailType = "SMTP" Then
seAddress2 = xi.SenderEmailAddress
If InStr(1, seAddress2, varSenders2(K), vbTextCompare) Then
Cells(n, 1).Value = xi.SenderEmailAddress
Cells(n, 2).Value = xi.SenderName
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End If
'this one fetches the cc part recipient denotes cc
For j = xi.Recipients.Count To 1 Step -1
If (xi.Recipients.Item(j).AddressEntry.Type = "EX") Then
vcc = xi.Recipients.Item(j).Address
If InStr(1, vcc, varSenders2(K), vbTextCompare) Then
Cells(n, 1).Value = xi.Recipients.Item(j).AddressEntry.GetExchangeUser.PrimarySmtpAddress
Cells(n, 2).Value = xi.Recipients.Item(j).Name
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End If
Else
vcc = xi.Recipients.Item(j).Address
If InStr(1, vcc, varSenders2(K), vbTextCompare) Then
Cells(n, 1).Value = xi.Recipients.Item(j).Address
Cells(n, 2).Value = xi.Recipients.Item(j).Name
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End If
End If
Next j
Else: seAddress2 = ""
End If
For a = 1 To take.Items.Count
n = 3
'this also fetches the recipient emails
If TypeName(take.Items(a)) = "MailItem" Then
For b = 1 To take.Items.Item(a).Recipients.Count
asd = take.Items.Item(a).Recipients(b).Address
If InStr(1, asd, varSenders2(K), vbTextCompare) Then
Cells(n, 1).Value = asd
Cells(n, 2).Value = take.Items.Item(a).Recipients(b).Name
n = n + 1
End If
Next b
End If
Next a
Next K
End If
Next objitem
For Each i In fol.Items.Restrict(strFilter)
If i.Class = olMail Then
Set mi = i
'objects have been assigned and can be used to fetch emails
seemail = Worksheets("Inbox").Range("D1")
varSenders = Split(seemail, ";")
n = n + 1
For K = 0 To UBound(varSenders)
'similar logic as above
If mi.SenderEmailType = "EX" Then
seAddress = mi.Sender.GetExchangeUser().PrimarySmtpAddress
If InStr(1, seAddress, varSenders(K), vbTextCompare) Then
Cells(n, 1).Value = mi.Sender.GetExchangeUser().PrimarySmtpAddress
Cells(n, 2).Value = mi.SenderName
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End If
ElseIf mi.SenderEmailType = "SMTP" Then
seAddress = mi.SenderEmailAddress
If InStr(1, seAddress, varSenders(K), vbTextCompare) Then
Cells(n, 1).Value = mi.SenderEmailAddress
Cells(n, 2).Value = mi.SenderName
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End If
For j = mi.Recipients.Count To 1 Step -1
If (mi.Recipients.Item(j).AddressEntry.Type = "EX") Then
vcc = mi.Recipients.Item(j).Address
If InStr(1, vcc, varSenders(K), vbTextCompare) Then
Cells(n, 1).Value = mi.Recipients.Item(j).AddressEntry.GetExchangeUser.PrimarySmtpAddress
Cells(n, 2).Value = mi.Recipients.Item(j).Name
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End If
Else
vcc = mi.Recipients.Item(j).Address
If InStr(1, vcc, varSenders(K), vbTextCompare) Then
Cells(n, 1).Value = mi.Recipients.Item(j).Address
Cells(n, 2).Value = mi.Recipients.Item(j).Name
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End If
End If
Next j
Else: seAddress = ""
End If
Next K
End If
Next i
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Set take = Nothing
Set mi = Nothing
Application.ScreenUpdating = True
End Sub
You have to assign a value to seemail and seemail2 before using in strFilter and strFilter2.
Option Explicit
Sub GetInbox_And_SentItems()
'Early binding - requires reference to Microsoft Outlook XX.X Object Library
Dim ol As Outlook.Application
Dim ns As Outlook.Namespace
Dim fol As Outlook.Folder
Dim folItem As Object
Dim mi As Outlook.mailItem
Dim n As Long
Dim seemail As String
Dim seAddress As String
Dim varSenders As Variant
'for sent mails
Dim b As Integer
Dim objitem As Object
Dim take As Outlook.Folder
Dim xi As Outlook.mailItem
Dim k As Long
Dim seemail2 As String
Dim seAddress2 As String
'Dim varSenders2 As Variant
Dim varReceivers As Variant
Dim strFilter As String
Dim strFilter2 As String
'screen won't refresh until this is turned true
'Application.ScreenUpdating = False
'now assigning the variables and objects of outlook into this
Set ol = New Outlook.Application
Set ns = ol.GetNamespace("MAPI")
Set fol = ns.GetDefaultFolder(olFolderInbox)
Set take = ns.GetDefaultFolder(olFolderSentMail)
'Range("A3", Range("A3").End(xlDown).End(xlToRight)).Clear
Range("A3:A9999").Select
Selection.EntireRow.Delete
n = 2
varReceivers = Split(Worksheets("Inbox").Range("D1"), ";")
For k = LBound(varReceivers) To UBound(varReceivers)
seemail2 = Trim(varReceivers(k))
Debug.Print seemail2
' Note displayto not fromemail
' displayto can be a difficult value
' https://stackoverflow.com/questions/16286694/using-the-restrict-method-in-outlook-vba-to-filter-on-single-recipient-email-ad
' As far as I know there is no working toemail.
strFilter2 = "#SQL=" & Chr(34) & "urn:schemas:httpmail:displayto" & Chr(34) & " like '%" & seemail2 & "'"
Debug.Print strFilter2
Debug.Print "Items in Inbox.........:" & take.Items.Count
Debug.Print "Filtered Items in Inbox:" & take.Items.Restrict(strFilter2).Count
'this one is for sent items folder where it fetches the emails --> to <-- particular people
' there is no point searching a sent folder for sender information
For Each objitem In take.Items.Restrict(strFilter2)
If objitem.Class = olMail Then
Set xi = objitem
n = n + 1
Cells(n, 1).Value = seemail2
Cells(n, 2).Value = xi.Subject
Dim msg As String
msg = ""
For b = 1 To xi.Recipients.Count
msg = msg & xi.Recipients(b).Address & "; "
Next b
Cells(n, 3).Value = msg
End If
Next objitem
Next k
varSenders = Split(Worksheets("Inbox").Range("D1"), ";")
For k = LBound(varSenders) To UBound(varSenders)
seemail = Trim(varSenders(k))
Debug.Print seemail
strFilter = "#SQL=" & Chr(34) & "urn:schemas:httpmail:fromemail" & Chr(34) & " like '%" & seemail & "'"
Debug.Print strFilter
For Each folItem In fol.Items.Restrict(strFilter)
If folItem.Class = olMail Then
Set mi = folItem
'objects have been assigned and can be used to fetch emails
n = n + 1
'similar logic as above
If mi.SenderEmailType = "EX" Then
seAddress = mi.Sender.GetExchangeUser().PrimarySmtpAddress
Cells(n, 1).Value = mi.Sender.GetExchangeUser().PrimarySmtpAddress
Cells(n, 2).Value = mi.SenderName
ElseIf mi.SenderEmailType = "SMTP" Then
seAddress = mi.SenderEmailAddress
Cells(n, 1).Value = mi.SenderEmailAddress
Cells(n, 2).Value = mi.Subject
End If
End If
Next folItem
Next k
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
'Uncomment if needed
'On Error Resume Next
Range("A3:A9999").Select
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
On Error GoTo 0
Application.ScreenUpdating = True
End Sub
All the code touching the email from the outer loop should be taken out of the inner loop. E.g. the line like
seAddress2 = xi.Sender.GetExchangeUser.PrimarySmtpAddress
has no business being in the inner loop.
I also wouldn't call RemoveDuplicates on each step of the loop.
Also, most likely the senders won't be unique - retrieve all the sender addresses (SenderEmailAddress) in a single using MAPIFolder.GetTable and build a dictionary of EX type addresses vs SMTP addresses (GetExchangeUser.PrimarySmtpAddress) to be calculated only once for each unique address instead of retrieving it over and over again.
Related
I am having an issue getting a variable set to pass to a function properly. The frustrating part is that until yesterday this code has been working properly for a good 4 months of weekly use.
Below is the code, removing the dims and parts of the code that will not help resolve this issue.
Sub Combined_15_and_45()
'Dim Iteration Variables
'Dim tracking variables
'Dim range variables
'Dim Invoice Value Variables
'Dim Email body variables
Set wb = ThisWorkbook
Set WithTerms = Sheet4
Set APEmail = Sheet7
With wb
With WithTerms
lrow = .Cells(Rows.Count, 5).End(xlUp).Row
elrow = Sheet7.Cells(Rows.Count, 1).End(xlUp).Row
CalcDate = .Cells(1, 3).Value
i = 1
'loop through looking for times when cell above is different *Store i Instance
'loop through looking for times when cell below is different *Store i as EndInstance
'Specifically searching for changes in account number
For i = 4 To lrow
h = i - 1
j = i + 1
Set rng = .Cells(i, 5)
Set RngUp = .Cells(h, 5)
Set RngDwn = .Cells(j, 5)
'this is where vendor account changes.
If rng.Value <> RngUp.Value Then
instance = i
End If
'Check if invoice for the line is extreme past due *Store i as MaxOvrDue
If .Cells(i, 10).Value <= .Range("C1").Value - 45 Then
MaxOvrDue = i
End If
'check if invoice for line is +15 day overdue, less than 45 * Store i as MidOvrDur
If .Cells(i, 10).Value <= .Range("C1").Value - 15 Then
If .Cells(i, 10).Value >= .Range("C1").Value - 44 Then
If MidOvrdue = 0 Then
MidOvrdue = i
End If
End If
End If
'Check if Invoice for line is 15+ days overdue (Minimum) *Store i as Ovrdue
If .Cells(i, 10).Value < .Range("C1").Value Then
If .Cells(i, 10).Value <= .Range("C1").Value - 14 Then
OvrDue = i
End If
End If
'figure values for the totals of each section
If rng.Value <> RngDwn.Value Then
EndInstance = i
TotalOverdue = Application.WorksheetFunction.SumIfs(.Range("K:K"), .Range("E:E"), .Cells(instance, 5), .Range("J:J"), "<" & (.Range("c1") - 15))
XtrmOverdue = Application.WorksheetFunction.SumIfs(.Range("K:K"), .Range("E:E"), .Cells(instance, 5), .Range("J:J"), "<" & .Range("C1") - 44)
MidTotalOverdue = Application.WorksheetFunction.SumIfs(.Range("K:K"), .Range("E:E"), .Cells(instance, 5), .Range("J:J"), "<" & .Range("C1") - 15, .Range("J:J"), ">=" & .Range("C1") - 45)
If OvrDue = 0 And MaxOvrDue = 0 And MidOvrdue = 0 Then
Else:
'begin building Extremely Overdue Invoice Text
If MaxOvrDue <> 0 And MidOvrdue = 0 Then
**Set XtrmTblRng = .Range(.Cells(instance, 7), .Cells(MaxOvrDue, 11))**
End If
If OvrDue <> 0 And MidOvrdue <> 0 Then
If MaxOvrDue = 0 And OvrDue <= MidOvrdue Then
**Set MidTblRng = .Range(.Cells(MidOvrdue, 7), .Cells(OvrDue, 11))**
'Begin building ONLY overdue email text
Else:
'begin building segments to add to extreme overdue email
**Set XtrmComboTblRng = .Range(.Cells(instance, 7), .Cells(OvrDue, 11))**
End If
End If
If OvrDue <> 0 Then
'Generate the email
With OutMail
.To = eAddy
'Figure out which email to send
If MaxOvrDue <> 0 And MidTotalOverdue <> 0 Then
.HTMLbody = StrBodyXtrm & RangetoHTML(XtrmComboTblRng, CalcDate) & ComboStrBody2 & StrBody4
Else
If MaxOvrDue <> 0 And MidOvrdue = 0 Then
.HTMLbody = StrBodyXtrm & RangetoHTML(XtrmTblRng, CalcDate) & StrBody2 & StrBody4
Else:
.HTMLbody = StrBodyOverdue & RangetoHTML(MidTblRng, CalcDate) & StrBody3
End If
End If
.display
End With
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End If
End If
'clear variables when changing vendor IDs
End If
Set rng = .Cells(j, 5)
Next i
End With
End With
End Sub
Function RangetoHTML(TblRng, CalcDate)
Dim fso As Object
Dim ts As Object
Dim TempFile As String
Dim TempWB As Workbook
Dim tRow As Long
Dim i As Long
Dim CalcDate2 As Double
Dim TempDate As Double
TempFile = Environ$("temp") & "\" & Format(Now, "dd-mm-yy h-mm-ss") & ".htm"
'Copy the range and create a new workbook to paste the data in. This is where I am getting errors all the sudden
**TblRng.Copy**
'manipulate the data from table to fit needs and past into email.
End Function
Essentially this is trying to sort through stack of invoices and determine which email format to use.
Invoices aged 45+
Invoices aged 15-45+
Invoices aged 15-44
I am running into an issue with the email determining a different email format should be used than the TblRng variable that has been built to pass to the function. I just cant seem to find my logic error.
I have been banging my head against a wall for a few days now trying to fix this with no luck. Any help you can give will make you a hero in my eyes!
Thank you
The problem with your logic is with this line
If maxOvrDue = 0 And OvrDue <= midOvrDue Then
If there are no >45 lines (maxOvrDue = 0) then at the first occurance of a >15 line
midOvrDue and OvrDue will be the same. On subsequent >15 lines OvrDue will
be greater than midOvrDue. So the above will be true for 1 and false for 2 or more
lines in the 44-15 range. With 2 or more the default Else option will then Set XtrmComboTblRng not MidTblRng.
Later because maxOvrDue = 0 the email .HTMLbody uses RangetoHTML(MidTblRng, CalcDate).
The remedy would be to just use If maxOvrDue = 0 Then.
You could set an email type within the same logic as used to set the ranges so the mismatch can't occur. Here is an example of how to do that
Option Explicit
Sub Combined_15_and_45()
Dim WithTerms As Worksheet, APEMail As Worksheet
Dim rng As Range, tblRng As Range
Dim lrow As Long, elrow As Long, i As Long
Dim instance As Long, maxOvrDue As Long, midOvrDue As Long
Dim CalcDate As Date, DaysLate As Integer, EmailFormat As Integer
Dim has45 As Boolean, has15 As Boolean
Dim acc As String
Dim TotalOverdue As Currency
Dim XtrmOverdue As Currency, MidTotalOverdue As Currency
Set APEMail = Sheet7
elrow = APEMail.Cells(Rows.Count, 1).End(xlUp).row
' for debugging
Dim fso, ts
Set fso = CreateObject("Scripting.FileSystemObject")
' scan down sheet
Set WithTerms = Sheet4
With WithTerms
lrow = .Cells(Rows.Count, "E").End(xlUp).row 'Row E
CalcDate = .Range("C1").Value
For i = 4 To lrow
'this is where vendor account changes.
Set rng = .Cells(i, 5) ' E Account
If rng.Value <> rng.Offset(-1).Value Then
acc = rng
instance = i
maxOvrDue = 0
midOvrDue = 0
XtrmOverdue = 0
MidTotalOverdue = 0
End If
' check days overdue
DaysLate = DateDiff("d", .Cells(i, "J").Value, CalcDate)
If DaysLate >= 45 Then
maxOvrDue = i
XtrmOverdue = XtrmOverdue + .Cells(i, "K")
ElseIf DaysLate >= 15 Then
midOvrDue = i
MidTotalOverdue = MidTotalOverdue + .Cells(i, "K")
End If
' is this last for account
If rng <> rng.Offset(1) Then
TotalOverdue = XtrmOverdue + MidTotalOverdue
Debug.Print vbCr & acc & " Total", XtrmOverdue, MidTotalOverdue, TotalOverdue
has45 = maxOvrDue > 0
has15 = midOvrDue > 0
If has45 Or has15 Then
If has45 And has15 Then
EmailFormat = 1
Set tblRng = .Range(.Cells(instance, 7), .Cells(midOvrDue, 11))
Debug.Print "+45 and +15", tblRng.Address
' begin building segments to add to extreme overdue email
ElseIf has45 Then
EmailFormat = 2
Set tblRng = .Range(.Cells(instance, 7), .Cells(maxOvrDue, 11))
Debug.Print "+45 only", tblRng.Address
' begin building Extremely Overdue Invoice Text
ElseIf has15 Then
EmailFormat = 3
Set tblRng = .Range(.Cells(instance, 7), .Cells(midOvrDue, 11))
Debug.Print "+15 only", tblRng.Address
' begin building ONLY overdue email text
End If
' select email format
Dim body As String
Select Case EmailFormat
Case 1
body = "strBodyXtrm" & RangetoHTML(tblRng, CalcDate) & "ComboStrBody2 & strBody4"
Case 2
body = "strBodyXtrm" & RangetoHTML(tblRng, CalcDate) & "strBody2 & strBody4"
Case 3
body = "strBodyOverdue" & RangetoHTML(tblRng, CalcDate) & "strBody3"
End Select
' create html file for checking
Set ts = fso.createTextFile(ThisWorkbook.Path & "\" & acc & ".html", 1)
ts.write body
ts.Close
'Generate the email
'With outmail
'.To = eAddy
'.HTMLbody = body
'.display
'End with
End If
End If
Next i
End With
MsgBox "Done"
End Sub
Function RangetoHTML(tblRng, CalcDate) As String
Dim s, rw As Range, cell As Range, pre As String
pre = "<pre>TblRng=" & tblRng.Address(External:=1) & "</pre>"
s = "<tr align=""center"" bgcolor=""#ddddff"">" & _
"<th>Col G</th><th>Col H</th><th>Col I</th>" & _
"<th>Col J</th><th>Col K</th></tr>" & vbCrLf
For Each rw In tblRng.Rows
s = s & "<tr>"
For Each cell In rw.Cells
s = s & "<td>" & cell & "</td>"
Next
s = s & "</tr>" & vbCrLf
Next
RangetoHTML = pre & "<table cellspacing=""0"" cellpadding=""3"" border=""1"">" & _
s & "</table>" & vbCrLf
End Function
I have a VBA script that allows me to calculate the distance in kms between two cities:
This script works correctly, the problem is that the list of cities to calculate that I was given is more than 5000 cities.
When I press the "GO" button, the processing starts and the Excel file freezes and it is impossible to see the progress of the processing until it is finished and it takes almost 1 hour...
Is it possible to improve the processing speed of my script or is it due to the speed of my internet connection?
And from about 3000 cities the script stops because the processing is too long. How can I solve this ?
Option Explicit
Public Const DIST = "http://www.distance2villes.com/recherche?source="
Sub Distance()
Dim lg As Integer, i As Integer
Dim Url As String, Txt As String
With Sheets("Feuil1")
lg = .Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To lg
Url = DIST & .Range("A" & i).Value & "&destination=" & .Range("B" & i).Value
With CreateObject("WINHTTP.WinHTTPRequest.5.1")
.Open "GET", Url, False
.send
Txt = .responseText
End With
' Only set the value if we got a response
If Txt <> vbNullString Then .Range("C" & i).Value = Split(Split(Txt, "id=""distanciaRuta"">")(1), "</strong>")(0)
' Clear our variable before next
Txt = vbNullString
Next i
End With
End Sub
GetElementById (vs Double-Split)
The problem here is that the website is generating huge web pages somehow dependent on the distance between the cities e.g. Paris-London generates a string of about 90k characters, while Paris-Vladivostok 1.4M characters.
Using a different object (MSXML2.XMLHTTP) increased the efficiency by about 10%.
The Code
Option Explicit
Sub Distance()
Const DIST1 As String = "http://www.distance2villes.com/recherche?source="
Const DIST2 As String = "&destination="
Const DIST3 As String = "distanciaRuta"
Const wsName As String = "Feuil1"
'Dim w As Object: Set w = CreateObject("WINHTTP.WinHTTPRequest.5.1")
Dim w As Object: Set w = CreateObject("MSXML2.XMLHTTP")
Dim h As Object: Set h = CreateObject("htmlfile")
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets(wsName)
Dim rg As Range
Set rg = ws.Range("A2", ws.Cells(ws.Rows.Count, "A").End(xlUp).Offset(, 1))
Dim Data As Variant: Data = rg.Value
Dim isFound As Boolean: isFound = True
Dim i As Long
Dim Url As String
Dim S As String
For i = 1 To UBound(Data, 1)
If Len(Data(i, 1)) > 0 And Len(Data(i, 2)) > 0 Then
Url = DIST1 & Data(i, 1) & DIST2 & Data(i, 2)
w.Open "GET", Url, False
w.Send
h.body.innerHTML = w.responseText
On Error GoTo NotFoundError
S = h.getElementById(DIST3).innerText
On Error GoTo 0
If isFound Then
Data(i, 1) = Replace(Left(S, Len(S) - 3), ",", "")
Else
Data(i, 1) = ""
isFound = True
End If
Else
Data(i, 1) = ""
End If
Next
rg.Columns(1).Offset(, 2).Value = Data
Exit Sub
NotFoundError:
isFound = False
Resume Next
End Sub
Try reusing request object (Untested)
Sub Distance()
Dim lg As Integer, i As Integer
Dim Url As String, Txt As String
Dim objReq as WINHTTP.WinHTTPRequest
Set objReq = new WINHTTP.WinHTTPRequest
With Sheets("Feuil1")
lg = .Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To lg
If i Mod 10 = 0 Then Application.Statusbar = i & " of " & lg
Url = DIST & .Range("A" & i).Value & "&destination =" & .Range("B" & i).Value
With objReq
.Open "GET", Url, False
.send
Txt = .responseText
End With
' Only set the value if we got a response
If Txt <> vbNullString Then .Range("C" & i).Value = Split(Split(Txt, "id=""distanciaRuta"">")(1), "</strong>")(0)
' Clear our variable before next
Txt = vbNullString
Next i
End With
End Sub
You can run in asynchronous mode, which allows you to run multiple calls at the same time, which would (in theory) allow you to process the whole list more quickly - see for example
http://dailydoseofexcel.com/archives/2006/10/09/async-xmlhttp-calls/
As a quick example:
Option Explicit
Public Const DIST = "http://www.distance2villes.com/recherche?source="
Dim requests As Collection
Sub Distance()
Dim i As Long, r
r = Rnd() ' "cachebuster" for testing...
Set requests = New Collection
With Sheets("Data")
For i = 2 To 13
.Range("C" & i).Value = "Waiting"
SendRequest i, DIST & .Range("A" & i).Value & _
"&destination=" & .Range("B" & i).Value & "&v=" & r
Next i
End With
End Sub
'create a request object and matching handler,
' add the handler to the "requests" collection,
' send the request
Sub SendRequest(rowNum As Long, URL As String)
Dim req As New MSXML2.XMLHTTP
Dim handler As New asyncHandler
handler.rowNum = rowNum 'store the row number for the request
handler.Initialize req
req.OnReadyStateChange = handler
req.Open "GET", URL, True
requests.Add handler, (CStr("Row" & rowNum))
req.send
End Sub
'called from each instance of `handler` as it completes
Sub SetResult(txt, rowNum)
Sheets("Data").Cells(rowNum, "C").Value = txt
requests.Remove CStr("Row" & rowNum)
Debug.Print "requests queue - " & requests.count
End Sub
"Handler" class asyncHandler (see link for extra step required with this):
Option Explicit
Public rowNum As Long
Dim m_xmlHttp As MSXML2.XMLHTTP
Public Sub Initialize(ByRef xmlHttpRequest As MSXML2.XMLHTTP)
Set m_xmlHttp = xmlHttpRequest
End Sub
Sub OnReadyStateChange()
Dim v
If m_xmlHttp.readyState = 4 Then
If m_xmlHttp.Status = 200 Then
On Error Resume Next
v = Split(Split(m_xmlHttp.responseText, _
"id=""distanciaRuta"">")(1), "</strong>")(0)
On Error GoTo 0
SetResult v, rowNum 'update the sheet
Else
SetResult m_xmlHttp.statusText, rowNum
End If
End If
End Sub
I want to add cell value from one table to another. the first table contains about 110 000 rows (tabCDL) and the other about 37 000 rows (tabEMP). It takes about one hour to do right now and I need to do it faster.
Public Sub MergeColumnEMP()
'Merge
Dim cel, cel2, rngCDL, rngEMP As Range
Dim shtCDL, shtEMP As Worksheet
Dim LastRowCDL, LastRowEMP As Long
Set shtCDL = Sheets("CEDULE")
Set shtEMP = Sheets("EMPRUNT")
LastRowCDL = shtCDL.ListObjects("TabCEDULE").Range.Rows.Count
LastRowEMP = shtEMP.ListObjects("TabEMP").Range.Rows.Count
Set rngCDL = Sheets("CEDULE").Range("H2:H" & LastRowCDL)
Set rngEMP = Sheets("EMPRUNT").Range("C2:C" & LastRowEMP)
For Each cel In rngCDL
For Each cel2 In rngEMP
If cel.Value = cel2.Value Then
'amount
Sheets("CEDULE").Range("I" & cel.Row).Value = Sheets("EMPRUNT").Range("D" & cel2.Row).Value
'Date dstart
Sheets("CEDULE").Range("J" & cel.Row).Value = Sheets("EMPRUNT").Range("H" & cel2.Row).Value
'Date end
Sheets("CEDULE").Range("K" & cel.Row).Value = Sheets("EMPRUNT").Range("I" & cel2.Row).Value
Exit For
End If
Next cel2
Next cel
Debug.Print "DONE merging"
End Sub
Try the next way, please. It uses arrays and should be very fast. Not tested, but it should work, if I did not messed anything up regarding the involved ranges:
Sub MergeColumnEMP() 'unique in EMP, not unique in CEDULE
Dim arrCDL, arrEMP, rngCopy As Range, i As Long, j As Long
Dim shtCDL As Worksheet, shtEMP As Worksheet
Dim LastRowCDL As Long, LastRowEMP As Long
Set shtCDL = Sheets("CEDULE")
Set shtEMP = Sheets("EMPRUNT")
LastRowCDL = shtCDL.ListObjects("TabCEDULE").Range.rows.Count
LastRowEMP = shtEMP.ListObjects("TabEMP").Range.rows.Count
arrCDL = shtCDL.Range("H2:K" & LastRowCDL).value 'h
arrEMP = Sheets("EMPRUNT").Range("C2:I" & LastRowEMP).value 'c
For i = 1 To UBound(arrCDL)
For j = 1 To UBound(arrEMP)
If arrCDL(i, 1) = arrEMP(j, 1) Then
arrCDL(i, 2) = arrEMP(j, 2)
arrCDL(i, 3) = arrEMP(j, 5)
arrCDL(i, 4) = arrEMP(j, 6)
Exit For
End If
Next j
Next i
shtCDL.Range("H2").Resize(UBound(arrCDL), UBound(arrCDL, 2)).value = arrCDL
MsgBox "DONE merging"
End Sub
Edited:
Please, also test the next code, which should be faster:
Sub MergeColumnEMPLast() 'unique in EMP, not unique in CEDULE
Dim arrCDL, arrEMP, rngCopy As Range, i As Long, j As Long
Dim shtCDL As Worksheet, shtEMP As Worksheet
Dim LastRowCDL As Long, LastRowEMP As Long
Dim dict As New Scripting.Dictionary, iMatch As Variant
Set shtCDL = Sheets("CEDULE")
Set shtEMP = Sheets("EMPRUNT")
LastRowCDL = shtCDL.ListObjects("TabCEDULE").Range.rows.Count
LastRowEMP = shtEMP.ListObjects("TabEMP").Range.rows.Count
arrCDL = shtCDL.Range("H2:K" & LastRowCDL).value 'h
arrEMP = shtEMP.Range("C2:I" & LastRowEMP).value 'c
For i = 1 To UBound(arrCDL)
If dict.Count > 0 Then iMatch = Application.match(arrCDL(i, 1), dict.Keys, 0)
If Not IsError(iMatch) Then
If dict.Count > 0 Then
If iMatch <> dict.Count Or (iMatch = dict.Count And arrCDL(i, 1) = dict.Keys(dict.Count - 1)) Then
arrCDL(i, 2) = dict.items(iMatch - 1)(0)
arrCDL(i, 3) = dict.items(iMatch - 1)(1)
arrCDL(i, 4) = dict.items(iMatch - 1)(2)
GoTo OverIteration
End If
End If
End If
For j = 1 To UBound(arrEMP)
If arrCDL(i, 1) = arrEMP(j, 1) Then
arrCDL(i, 2) = arrEMP(j, 2)
arrCDL(i, 3) = arrEMP(j, 6)
arrCDL(i, 4) = arrEMP(j, 7)
dict.Add arrCDL(i, 1), Array(arrEMP(j, 2), arrEMP(j, 6), arrEMP(j, 7))
Exit For
End If
Next j
OverIteration:
Next i
shtCDL.Range("H2").Resize(UBound(arrCDL), UBound(arrCDL, 2)).value = arrCDL
MsgBox "DONE merging"
End Sub
I am just curious how much it takes for your range...
With a Dictionary Object as a look-up
Option Explicit
Public Sub MergeColumnEMP()
'Merge
Dim wb As Workbook
Dim shtCDL As Worksheet, shtEMP As Worksheet
Dim cel As Range, rngCDL, rngEMP As Range
Dim LastRowCDL As Long, LastRowEMP As Long, t0 As Single
t0 = Timer
Set wb = ThisWorkbook
Set shtCDL = wb.Sheets("CEDULE")
LastRowCDL = shtCDL.ListObjects("TabCEDULE").Range.Rows.Count
Set rngCDL = shtCDL.Range("H2:H" & LastRowCDL)
Set shtEMP = wb.Sheets("EMPRUNT")
LastRowEMP = shtEMP.ListObjects("TabEMP").Range.Rows.Count
Set rngEMP = shtEMP.Range("C2:C" & LastRowEMP)
Dim dict As Object, key As String, r As Long
Set dict = CreateObject("Scripting.Dictionary")
' fill dictionary with rowno as value
For Each cel In rngEMP
key = Trim(cel)
If Len(key) > 0 Then dict(key) = cel.Row
Next
' compare using dictionary
For Each cel In rngCDL ' col H
key = Trim(cel)
If Len(key) > 0 And dict.exists(key) Then
r = dict(key)
With shtCDL
' update
cel.Offset(0, 1) = shtEMP.Range("D" & r).Value ' I amount
cel.Offset(0, 2) = shtEMP.Range("H" & r).Value ' J Date dstart
cel.Offset(0, 3) = shtEMP.Range("I" & r).Value ' K Date end
End With
End If
Next
MsgBox "DONE merging", vbInformation, "Duration " & Int(Timer - t0) & " seconds"
End Sub
Sub ExportDataTSV()
Dim BCS As Worksheet
Dim Ctrl As Worksheet
Dim ws As Worksheet
Dim FName As String
Dim insertValues As String
Application.ScreenUpdating = False
Set BCS = ThisWorkbook.Sheets(Sheet2.Name)
Set Ctrl = ThisWorkbook.Sheets(Sheet1.Name)
#If Mac Then
NameFolder = "documents folder"
If Int(Val(Application.Version)) > 14 Then
'You run Mac Excel 2016
folder = _
MacScript("return POSIX path of (path to " & NameFolder & ") as string")
'Replace line needed for the special folders Home and documents
folder = _
Replace(SpecialFolder, "/Library/Containers/com.microsoft.Excel/Data", "")
Else
'You run Mac Excel 2011
folder = MacScript("return (path to " & NameFolder & ") as string")
End If
FName = folder & "bcs_output.tsv"
#Else
folder = Environ$("userprofile")
Debug.Print folder
FName = folder & "Documents\bcs_output.tsv"
#End If
If Ctrl.Range("D9") = "" Or Ctrl.Range("D10") = "" Then
MsgBox "Please enter the Scenario Year and Scenario you wish to save and click again", vbOKOnly
Exit Sub
End If
Ctrl.Range("D9").Copy
BCS.Range("AS2").PasteSpecial Paste:=xlPasteValues
Ctrl.Range("D10").Copy
BCS.Range("AT2").PasteSpecial Paste:=xlPasteValues
With BCS
numrows = .Cells(.Rows.Count, 1).End(xlUp).Row
numcol = .Cells(2, Columns.Count).End(xlToLeft).Column
.Range("AS1").Value = "scenario_year"
.Range("AS2:AS" & numrows).FillDown
.Range("AT1").Value = "scenario"
.Range("AT2:AT" & numrows).FillDown
.Range("AU1").Value = "save_date"
.Range("AU2").Formula = "=NOW()"
.Range("AU2:AU" & numrows).FillDown
.Range("AU2:AU" & numrows).NumberFormat = "yyyy-mm-dd hh:mm"
For x = 2 To numrows
Set rng1 = .Range("A" & x & ":R" & x)
Set rng2 = .Range("AC" & x & ":AF" & x)
Set rng3 = .Range("AH" & x & ":AK" & x)
Set rng4 = .Range("AN" & x & ":AO" & x)
Set rng5 = .Range("AS" & x & ":AU" & x)
Set Data = Union(rng1, rng2, rng3, rng4, rng5)
insertValues = Join2D(ToArray(Data), Chr(9))
Debug.Print insertValues
Call ConvertText(FName, insertValues)
Next x
End With
With BCS
.Activate
.Range("A1").Select
End With
Ctrl.Activate
Application.ScreenUpdating = True
MsgBox "Cluster Data saved to your documents folder, please upload the file here: ", vbOKOnly
End Sub
Function ToArray(rng) As Variant()
Dim arr() As Variant, r As Long, nr As Long
Dim ar As Range, c As Range, cnum As Long, rnum As Long
Dim col As Range
nr = rng.Areas(1).Rows.Count
ReDim arr(1 To nr, 1 To rng.Cells.Count / nr)
cnum = 0
For Each ar In rng.Areas
For Each col In ar.Columns
cnum = cnum + 1
rnum = 1
For Each c In col.Cells
arr(rnum, cnum) = c.Value
rnum = rnum + 1
Next c
Next col
Next ar
ToArray = arr
End Function
Public Function Join2D(ByVal vArray As Variant, Optional ByVal sWordDelim As String = " ", Optional ByVal sLineDelim As String = vbNewLine) As String
Dim i As Long, j As Long
Dim aReturn() As String
Dim aLine() As String
ReDim aReturn(LBound(vArray, 1) To UBound(vArray, 1))
ReDim aLine(LBound(vArray, 2) To UBound(vArray, 2))
For i = LBound(vArray, 1) To UBound(vArray, 1)
For j = LBound(vArray, 2) To UBound(vArray, 2)
'Put the current line into a 1d array
aLine(j) = vArray(i, j)
Next j
'Join the current line into a 1d array
aReturn(i) = Join(aLine, sWordDelim)
Next i
Join2D = Join(aReturn, sLineDelim)
End Function
Function ConvertText(myfile As String, strTxt As String)
Dim objStream
Set objStream = CreateObject("ADODB.Stream")
With objStream
'.Charset = "utf-8"
.Open
.WriteText strTxt
.SaveToFile myfile, 2
'.Close
End With
'Set objStream = Nothing
End Function
I attempted the above to write the non-contiguous ranges to a tab delimited file. I get a 3004 error - Unable to write file from that code. I am not sure why it can't write the file and since I can't even write the file I can't tell if it will write each row of data until there are no more. Can anyone assist with at least helping me get the file to write?
You need to separate folder and "Documents\bcs_output.tsv" with a backslash. In MacOS I believe the path separator is ":" (colon), not "\" (backslash).
I am creating a VBA program that will run in the background of my excel file. This VBA program will read fields in from a folder of text files. I have gotten the fields I need read in, I am just having trouble with the formatting. Every value that is read out is put on the next line in the excel file, but it puts it in the correct row, so I need to figure out how to move a whole column up one row once everything is read in. Below I have added my entire program, which was the easiest to see when entering it under the java header(it is VBA code). I have left out my cLines class where my values get stored. The part in the program that writes to the worksheet is where I believe that we will have to insert the formatting.
'Main Module
Option Explicit
'NOTE: Set reference to Microsoft Scripting Runtime
Sub FindInFile()
Dim sBaseFolder As String, sFindText As String, sFindTracNum As String, sFindTrailNum As String, sFindRemarks As String
Dim FD As FileDialog
Dim FSO As FileSystemObject, FIs As Files, FI As File, FO As Folder
Dim TS As TextStream
Dim colL As Collection, TracNum As Collection, TrailNum As Collection, Remarks As Collection, cL As cLines
Dim S As String, strPath As String
Dim I As Long
Dim R As Range
Dim wsRes As Worksheet, rRes As Range, vRes() As Variant
'Set results worksheet and range
Set wsRes = Worksheets("Sheet1")
Set rRes = wsRes.Cells(1, 1)
sFindText = "Driver Name:"
sFindTracNum = "Tractor #:"
sFindTrailNum = "Trailer #:"
sFindRemarks = "Remarks:"
'Specify the folder
strPath = "C:\test\Excel Test"
'Get the Text files in the folder
Set FSO = New FileSystemObject
Set FO = FSO.GetFolder(strPath)
Set FIs = FO.Files
'Collect the information
Set colL = New Collection
Set TracNum = New Collection
Set TrailNum = New Collection
Set Remarks = New Collection
For Each FI In FIs
With FI
If .Name Like "*.txt" Then
I = 0
Set TS = FSO.OpenTextFile(strPath & "\" & .Name, ForReading)
Do Until TS.AtEndOfStream
S = TS.ReadLine
I = I + 1
Set cL = New cLines
If InStr(1, S, sFindText, vbTextCompare) > 0 Then
With cL
.LineText = S
End With
colL.Add cL
ElseIf InStr(1, S, sFindTrailNum, vbTextCompare) > 0 Then
With cL
.TrailNum = S
End With
colL.Add cL
End If
Loop
End If
End With
Next FI
'Write the collection to a VBA array
ReDim vRes(0 To colL.Count, 1 To 6)
'Column Headers
vRes(0, 1) = "Driver Name"
vRes(0, 2) = "Tractor#"
vRes(0, 3) = "Trailer#"
vRes(0, 4) = "Remarks"
vRes(0, 5) = "Next" & vbLf & "Plan"
vRes(0, 6) = "Status" & vbLf & "of" & vbLf & "Repairs"
For I = 1 To colL.Count
With colL(I)
vRes(I, 1) = .LineText
vRes(I, 2) = .TracNum
vRes(I, 3) = .TrailNum
vRes(I, 4) = .Remarks
End With
Next I
'Write to the worksheet
Application.ScreenUpdating = False
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
With .Rows(1)
.Font.Bold = True
.HorizontalAlignment = xlCenter
End With
With .Columns(3)
'.EntireRow.Cut
'.Offset(-1, 0).EntireRow.Insert shift:=xlDown
End With
.EntireColumn.ColumnWidth = 45
With .EntireRow
.WrapText = True
.VerticalAlignment = xlCenter
.AutoFit
End With
.EntireColumn.AutoFit
'Remove the FindWord
For Each R In rRes.Offset(1).Resize(rRes.Rows.Count - 1).Columns(1).Cells
I = 1
Do
I = InStr(I, R.Text, sFindText, vbTextCompare)
With R.Characters(I, Len(sFindText))
.Delete
End With
I = InStr(I + 1, R.Text, sFindText, vbTextCompare)
Loop Until I = 0
Next R
For Each R In rRes.Offset(1).Resize(rRes.Rows.Count - 1).Columns(3).Cells
I = 1
Do
I = InStr(I, R.Text, sFindTrailNum, vbTextCompare)
With R.Characters(I, Len(sFindTrailNum))
.Delete
End With
I = InStr(I + 1, R.Text, sFindTrailNum, vbTextCompare)
Loop Until I = 0
Next R
End With
Application.ScreenUpdating = True
End Sub'
I figured it out. Here is the updated VBA code:
Option Explicit
'Private Sub Workbook_Open()
'Call FindInFile
'End Sub
'NOTE: Set reference to Microsoft Scripting Runtime
Sub FindInFile()
' Application.OnTime Now + TimeValue("00:01"), "FindInFile"
Dim sBaseFolder As String, sFindText As String, sFindTracNum As String
Dim sFindTrailNum As String, sFindRemarks As String, sFindDefect As String
Dim FD As FileDialog
Dim FSO As FileSystemObject, FIs As Files, FI As File, FO As Folder
Dim TS As TextStream
Dim colL As Collection, TracNum As Collection, TrailNum As Collection
Dim Remarks As Collection, Defect As Collection, cL As cLines
Dim S As String, C As String, strPath As String
Dim I As Long, T As Long, G As Long, H As Long
Dim R As Range
Dim wsRes As Worksheet, rRes As Range, vRes() As Variant
'Set results worksheet and range
Set wsRes = Worksheets("Sheet1")
Set rRes = wsRes.Cells(1, 1)
'Set text you will search for in files
sFindText = "Driver Name:"
sFindTracNum = "Tractor #:"
sFindTrailNum = "Trailer #:"
sFindRemarks = "Remarks:"
sFindDefect = "Defect Found?: No"
'Specify the folder
strPath = "C:\test\Excel Test"
'Get the Text files in the folder
Set FSO = New FileSystemObject
Set FO = FSO.GetFolder(strPath)
Set FIs = FO.Files
'Collect the information
Set colL = New Collection
Set TracNum = New Collection
Set TrailNum = New Collection
Set Remarks = New Collection
Set Defect = New Collection
'Get each field out of the text files
For Each FI In FIs
With FI
If .Name Like "*.txt" Then
I = 0
Set TS = FSO.OpenTextFile(strPath & "\" & .Name, ForReading)
Do Until TS.AtEndOfStream
S = TS.ReadLine
I = I + 1
Set cL = New cLines
If InStr(1, S, sFindDefect, vbTextCompare) > 0 Then
'If (S = "Defect Found?: Yes") Then
'End If
End If
If InStr(1, S, sFindText, vbTextCompare) > 0 Then
With cL
.LineText = S
End With
colL.Add cL
ElseIf InStr(1, S, sFindTrailNum, vbTextCompare) > 0 Then
With cL
.TrailNum = S
End With
TrailNum.Add cL
ElseIf InStr(1, S, sFindRemarks, vbTextCompare) > 0 Then
With cL
.Remarks = S
End With
Remarks.Add cL
End If
Loop
End If
End With
Next FI
'Write the collection to a VBA array
ReDim vRes(0 To colL.Count, 1 To 5)
'Column Headers
vRes(0, 1) = "Driver Name"
vRes(0, 2) = "Tractor#"
vRes(0, 3) = "Trailer#"
vRes(0, 4) = "Remarks"
vRes(0, 5) = "Defect?"
'vRes(0, 6) = "Status" & vbLf & "of" & vbLf & "Repairs"
'Get all of the information on the correct line
For I = 1 To colL.Count
With colL(I)
vRes(I, 1) = .LineText
End With
Next I
For T = 1 To TrailNum.Count
With TrailNum(T)
vRes(T, 3) = .TrailNum
End With
Next T
For G = 1 To Remarks.Count
With Remarks(G)
vRes(G, 4) = .Remarks
End With
Next G
For H = 1 To Defect.Count
With Defect(H)
vRes(H, 5) = .Defect
End With
Next H
'Write to the worksheet
Application.ScreenUpdating = False
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
With .Rows(1)
.Font.Bold = True
.HorizontalAlignment = xlCenter
.RowHeight = 36
End With
.EntireColumn.ColumnWidth = 45
With .EntireRow
.WrapText = True
.VerticalAlignment = xlCenter
'.AutoFit
End With
.EntireColumn.AutoFit
'Remove the word that is found
For Each R In rRes.Offset(1).Resize(rRes.Rows.Count - 1).Columns(1).Cells
I = 1
Do
I = InStr(I, R.Text, sFindText, vbTextCompare)
With R.Characters(I, Len(sFindText))
.Delete
End With
I = InStr(I + 1, R.Text, sFindText, vbTextCompare)
Loop Until I = 0
Next R
For Each R In rRes.Offset(1).Resize(rRes.Rows.Count - 1).Columns(3).Cells
I = 1
Do
I = InStr(I, R.Text, sFindTrailNum, vbTextCompare)
With R.Characters(I, Len(sFindTrailNum))
.Delete
End With
I = InStr(I + 1, R.Text, sFindTrailNum, vbTextCompare)
Loop Until I = 0
Next R
For Each R In rRes.Offset(1).Resize(rRes.Rows.Count - 1).Columns(4).Cells
I = 1
Do
I = InStr(I, R.Text, sFindRemarks, vbTextCompare)
With R.Characters(I, Len(sFindRemarks))
.Delete
End With
I = InStr(I + 1, R.Text, sFindRemarks, vbTextCompare)
Loop Until I = 0
Next R
End With
Application.ScreenUpdating = True
End Sub