I have a VBA code that copies data from MS Word documents in a folder and pastes them into an MS Excel file. The folder contains about over 2000 MS word files. The code opens each word file in the folder and looks for two key words, lets call them "FindWord1" and "FindWord2", then copies all the data (including text) that is located between these two keywords from this word file and pastes it into a Excel worksheet. Then moves on to the next Word file in the folder.
Some of these 2000 word documents are missing the two keywords. If the code does not find the key words (either "Findword1" or "Findword2") it returns an error. So only the word documents opened before this error are copied and pasted. Is there a way to log the files names of the word documents that are missing the keywords, skip them and move on to the next file in the folder.
The code runs fine as is, but I have to manually go and remove the file from the folder for it to go to the next file which is taking a lot of time. I would appreciate any help here.
Thanks,
N
'Note: this code requires a reference to the Word object model. See under the VBE's Tools|References.
Application.ScreenUpdating = False
'Objects
Dim wdApp As New Word.Application, wdDoc As Word.Document
Dim strFolder As String, strFile As String, lRow As Long
Dim WkSht As Worksheet: Set WkSht = ActiveSheet
'Folder Location
strFolder = "C:\Users\Folder\"
strFile = Dir(strFolder & "*.docx", vbNormal)
'Loop Start
While strFile <> ""
Set wdDoc = wdApp.Documents.Open(Filename:=strFolder & strFile, AddToRecentFiles:=False, ReadOnly:=True, Visible:=False)
lRow = WkSht.UsedRange.SpecialCells(xlCellTypeLastCell).Row + 1
With wdDoc
' Text you want to search
Dim FindWord1, FindWord2 As String
Dim result As String
FindWord1 = "Keyword1"
FindWord2 = "Keyword2"
'Style
mystyle = ""
'Defines selection for Word's find function
wdDoc.SelectAllEditableRanges
' Move your cursor to the start of the document
wdDoc.ActiveWindow.Selection.HomeKey unit:=wdStory
'Find Functionality in MS Word
With wdDoc.ActiveWindow.Selection.Find
.Text = FindWord1
.Replacement.Text = ""
.Forward = True
.Wrap = 1
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
If mystyle <> "" Then
.Style = mystyle
End If
If .Execute = False Then
MsgBox "'Text' not found.", vbExclamation
Exit Sub
End If
' Locate after the ending paragraph mark (beginning of the next paragraph)
' wdDoc.ActiveWindow.Selection.Collapse Direction:=wdCollapseEnd
' Starting character position of a selection
lngStart = wdDoc.ActiveWindow.Selection.End 'Set Selection.Start to include searched word
.Text = FindWord2
.Replacement.Text = ""
.Forward = True
.Wrap = 1
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
'.Style = mystyle
If .Execute = False Then
MsgBox "'Text2' not found.", vbExclamation
Exit Sub
End If
lngEnd = wdDoc.ActiveWindow.Selection.Start 'Set Selection.End to include searched word
End With
'Copy Selection
wdDoc.Range(lngStart, lngEnd).Copy
WkSht.Paste WkSht.Range("C" & lRow)
.Close SaveChanges:=False
End With
strFile = Dir()
Wend
wdApp.Quit
Set wdDoc = Nothing: Set wdApp = Nothing: Set WkSht = Nothing
Application.ScreenUpdating = True
End Sub
Please remember to declare all variables, add Option Explicit at the top of your module to help you enforce this.
You might know this already but Dim FindWord1, FindWord2 As String will declare FindWord1 as Variant, you have to declare the variable type for each variable one by one i.e. Dim FindWord1 As String, FindWord2 As String.
What is mysetyle for? It's not being used but I have left it there anyway, please delete if there is no use for it.
Try below code, if the Word document does not contain both keywords then it will prompt a MsgBox and Debug.Print to the immediate window, modify to your needs:
Private Sub Test()
'Note: this code requires a reference to the Word object model. See under the VBE's Tools|References.
Application.ScreenUpdating = False
'Objects
Dim wdApp As Word.Application
Dim wdDoc As Word.Document
Dim lRow As Long
Dim WkSht As Worksheet
Set WkSht = ActiveSheet
Const colPaste As Long = 3 'Column C
'Search String
Const FindWord1 As String = "Keyword1"
Const FindWord2 As String = "Keyword2"
'Folder Location
'Const strFolder As String = "C:\Users\Folder\"
Dim strFile As String
strFile = Dir(strFolder & "*.docx", vbNormal)
'Loop Start
While strFile <> vbNullString
If wdApp Is Nothing Then Set wdApp = New Word.Application
Set wdDoc = wdApp.Documents.Open(Filename:=strFolder & strFile, AddToRecentFiles:=False, ReadOnly:=True, Visible:=False)
lRow = WkSht.UsedRange.SpecialCells(xlCellTypeLastCell).Row + 1
'Style
mystyle = vbNullString
Dim firstRng As Word.Range
Set firstRng = wdDoc.Range.Duplicate
'Find Functionality in MS Word
With firstRng.Find
.Text = FindWord1
.Replacement.Text = ""
.Forward = True
.Wrap = 1
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
End With
If firstRng.Find.Found Then
Dim secondRng As Word.Range
Set secondRng = wdDoc.Range(firstRng.End, wdDoc.Range.End).Duplicate
With secondRng.Find
.Text = FindWord2
.Replacement.Text = ""
.Forward = True
.Wrap = 1
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
End With
If secondRng.Find.Found Then
'Found both keywords, copy to worksheet
Dim copyRng As Word.Range
Set copyRng = wdDoc.Range(firstRng.Start, secondRng.End).Duplicate
copyRng.Copy
'WkSht.Cells(lRow, colPaste).Paste
WkSht.Paste WkSht.Range("C" & lRow)
Else
'Error - second word not found~ abort and move on to next file
MsgBox "Second word not found" & vbNewLine & _
strFolder & strFile
Debug.Print "Second word not found: " & strFolder & strFile
End If
Else
'Error - first word not found~ abort and move on to next file
MsgBox "First word not found" & vbNewLine & _
strFolder & strFile
Debug.Print "First word not found: " & strFolder & strFile
End If
Set firstRng = Nothing
Set secondRng = Nothing
Set copyRng = Nothing
wdDoc.Close 0
strFile = Dir()
Wend
wdApp.Quit
Set wdDoc = Nothing
Set wdApp = Nothing
Set WkSht = Nothing
Application.ScreenUpdating = True
End Sub
Do you have lngStart and lngEnd defined somewhere? Maybe Dim them and assign 0 to both right after opening the next word doc, and then check if they are not equal to
0 before the copy to excel part. Don't have any considerable experience for Word VBA, sorry if not applicable.
Related
I need to use Word Macro for automatically proofreading the documents. I have an excel file, filled in with all the wrong spelling words, and after I installed the macro to Microsoft Word, it took several minutes to finish the spelling checking for just 1 page of the Word Document.
Can I use .txt to replace the excel in order to make it faster? Or what should I improve? Below please find the code for the Macro:
Attribute VB_Name = "PR"
Option Explicit
Sub PR()
Dim Path As String
Dim objExcel As Object
Dim iCount As Integer
Dim VChar As String
Dim OChar As String
Options.AutoFormatAsYouTypeReplaceQuotes = True
Path = "D:\Macro\rplPR.xlsx"
'Highlight variant characters
With ActiveDocument
.TrackRevisions = False
.ShowRevisions = False
End With
Set objExcel = CreateObject("Excel.Application")
objExcel.Workbooks.Open Path
For iCount = 2 To 2500
Selection.HomeKey Unit:=wdStory
VChar = objExcel.ActiveWorkbook.Sheets(1).Cells(iCount, 1)
If Len(VChar) = 0 Then Exit For
Selection.HomeKey Unit:=wdStory, Extend:=wdMove
With Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Replacement.Highlight = True
.Text = VChar
.Replacement.Text = "^&"
.Execute Replace:=wdReplaceAll
End With
Next
objExcel.ActiveWorkbook.Close
objExcel.Quit
End Sub
Move these lines up to above the For statement. You are setting them 2,499 times and you only need to do it once.
.ClearFormatting
.Replacement.ClearFormatting
.Replacement.Highlight = True
.Text = VChar
.Replacement.Text = "^&"
So each dot is a function call. There are 5 needless ones done 2498 times which is 12,490 function calls.
Function calls, while essential, are slow compared to other operations as there is a lot of setup.
If you didn't use with that would be an extra 12,490 function calls as well for a total of 24,980 sloww needless function calls.
Try the following. Do note that there is necessarily some overhead involved in starting Excel (if not already running), as well as processing the workbook. Hence, even a single-page document will encounter the same overhead there as a 100-page document.
Sub BulkFindReplace()
Application.ScreenUpdating = False
Dim xlApp As Object, xlWkBk As Object, StrWkBkNm As String, StrWkSht As String
Dim iDataRow As Long, xlFList As String, xlRList As String, i As Long
StrWkBkNm = "D:\Macro\rplPR.xlsx": StrWkSht = "Sheet1"
'Start Excel
Set xlApp = CreateObject("Excel.Application")
If xlApp Is Nothing Then
MsgBox "Can't start Excel.", vbExclamation
Exit Sub
End If
On Error GoTo 0
With xlApp
'Hide our Excel session
.Visible = False
' The file is available, so open it.
Set xlWkBk = .Workbooks.Open(StrWkBkNm, False, True)
If xlWkBk Is Nothing Then
MsgBox "Cannot open:" & vbCr & StrWkBkNm, vbExclamation
.Quit: Set xlApp = Nothing: Exit Sub
End If
' Process the workbook.
With xlWkBk
With .Worksheets(StrWkSht)
' Find the last-used row in column A.
iDataRow = .Cells(.Rows.Count, 1).End(-4162).Row ' -4162 = xlUp
' Capture the F/R data.
For i = 1 To iDataRow
' Skip over empty fields to preserve the underlying cell contents.
If Trim(.Range("A" & i)) <> vbNullString Then
xlFList = xlFList & "|" & Trim(.Range("A" & i))
xlRList = xlRList & "|" & Trim(.Range("B" & i))
End If
Next
End With
.Close False
End With
.Quit
End With
' Release Excel object memory
Set xlWkBk = Nothing: Set xlApp = Nothing
'Exit if there are no data
If xlFList = "" Then Exit Sub
'Process each word from the F/R List
With ActiveDocument.Range.Find
.ClearFormatting
.Replacement.ClearFormatting
.MatchWholeWord = True
.MatchCase = False
.Wrap = wdFindContinue
For i = 1 To UBound(Split(xlFList, "|"))
.Text = Split(xlFList, "|")(i)
.Replacement.Text = Split(xlRList, "|")(i)
.Execute Replace:=wdReplaceAll
Next
End With
Application.ScreenUpdating = True
End Sub
I am trying to excecute a VBA macro from excel to remove a row in a word document if a string is present.
For i = startItem To endItem
Dim msWord As Object
Set msWord = CreateObject("Word.Application")
With msWord
.Visible = TRUE
.Documents.Open getSetting("PTC TEMPLATE") 'path of the template in msword format
.Activate
'Remove TEST ROW
'LOOP TEST TO REMOVE
Dim DirArray As Variant
DirArray = ThisWorkbook.Sheets("valveList").ListObjects("valveList").HeaderRowRange.value
For Each element In DirArray
If element Like "*TEST*" Then
Debug.Print element & "--> " & Range("valveList[" & element & "]")(i).value
If Range("valveList[" & element & "]")(i).value = "NO" Then
.ActiveDocument.Select
With .Selection.Range.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = Range("valveList[" & element & "]")(i).value 'Find all strings in col A
.Forward = TRUE
.Wrap = wdFindStop
.MatchCase = FALSE
.MatchWholeWord = FALSE
.Execute
If .Found = TRUE Then
.Selection.Rows.Delete
End If
End With
End If
End If
Next element
'End REMOVE TEST ROW
Here I have the problem that I dont know how to refer to the found string and delete the row of the table the string belongs to.
I'm not very familiar with VBA, if someone can revise my code and explain how to solve this problem I'll be thankful
There are numerous problems with your code, including repeatedly starting Word and opening a new copy of the document you're modifying, employing Word constants with late binding, and the use of unqualified Range references. Try something along the lines of:
Sub Demo()
Dim msWord As Object, wdDoc As Object, xlSht As Worksheet, DirArray As Variant
Set xlSht = ThisWorkbook.Sheets("valveList")
DirArray = xlSht.ListObjects("valveList").HeaderRowRange.Value
Set msWord = CreateObject("Word.Application")
With msWord
.Visible = True
.ScreenUpdating = False
Set wdDoc = .Documents.Open(GetSetting("PTC TEMPLATE")) 'path of the template in msword format
For i = startItem To endItem
For Each element In DirArray
If element Like "*TEST*" Then
If xlSht.Range("valveList[" & element & "]")(i).Value = "NO" Then
With wdDoc.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = xlSht.Range("valveList[" & element & "]")(i).Text 'Find all strings in col A
.Forward = True
.Wrap = 0 'wdFindStop
.MatchCase = False
.MatchWholeWord = False
End With
Do While .Find.Execute
If .Information(12) = True Then 'wdWithInTable
.Rows(1).Delete
End If
.Collapse 0 'wdCollapseEnd
Loop
End With
End If
End If
Next element
Next i
.ScreenUpdating = True
End With
End Sub
I have a macro I am using to import the contents of tables in Word files into an Excel worksheet. The current script is working well, but there are times and error occurs in this line while processing a file where the contents of one of the columns becomes very lengthy:
.Paste Destination:=.Range("A" & r)
I don't need the full contents of the text in each column. I would like to modify this to either copy only the first line of text or a defined number of characters in each row/column of the table.
Is there a way to do this?
Here is the script I am currently using:
Sub GetFirstTableData()
'Note: this code requires a reference to the Word object model.
'See under the VBE's Tools|References.
Application.ScreenUpdating = False
Dim wdApp As New Word.Application, wdDoc As Word.Document
Dim strFolder As String, strFile As String, r As Long
strFolder = GetFolder: If strFolder = "" Then GoTo ErrExit
strFile = Dir(strFolder & "\*.doc", vbNormal)
While strFile <> ""
Set wdDoc = wdApp.Documents.Open(Filename:=strFolder & "\" & strFile, AddToRecentFiles:=False, Visible:=False)
With wdDoc
With .Tables(1)
With .Range.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "[^13^l]"
.Replacement.Text = Chr(182)
.Forward = True
.Format = False
.MatchWildcards = True
.Execute Replace:=wdReplaceAll
End With
.Columns.Add
.Cell(1, 8).Range.Text = Split(strFile, ".doc")(0)
.Range.Copy
End With
With ActiveSheet
r = .Cells(.Rows.Count, 1).End(xlUp).Row
If r > 1 Then r = r + 2
.Paste Destination:=.Range("A" & r)
End With
.Close SaveChanges:=False
End With
strFile = Dir()
Wend
ActiveSheet.UsedRange.Replace What:=Chr(182), Replacement:=Chr(10), LookAt:=xlPart, SearchOrder:=xlByRows
ErrExit:
wdApp.Quit
Set wdDoc = Nothing: Set wdApp = Nothing
Application.ScreenUpdating = True
End Sub
Function GetFolder() As String
Dim oFolder As Object
GetFolder = ""
Set oFolder = CreateObject("Shell.Application").BrowseForFolder(0, "Choose a folder", 0)
If (Not oFolder Is Nothing) Then GetFolder = oFolder.Items.Item.Path
Set oFolder = Nothing
End Function
Turns out that the error was occurring because there were tables embedded in the tables being parsed, not due to the length of the body of text in the cell.
The hangup would occur on this line during the processing of the .docx file with the embedded tables:
.Paste Destination:=.Range("A" & r)
Once the tables were deleted from the file, the macro works like a champ!
As mentioned in title I have a folder with word files and I want to copy from these files all tables to seperate sheets. I have tried to adapt the solutions from the internet but none of them works (or maybe I did not apply them correctly).
The error I encounter is
1004 "Method 'Paste' of object '_Worksheet' failed"
or sometimes it cannot copy/paste due to merged cells.
It shows on line code:
WkSht.Paste Destination:=WkSht.Range("A" & r)
What is strange when I rerun the macro it does go properly trough all files in folder. But I have to kill the word sometimes in task managers due to OLE error
"Microsoft Excel is waiting for another application to complete an OLE action."
choosing File, Options, Advanced, and under General Ignore other applications that use DDE does not help.
Public wdApp As New Word.Application, wdDoc As Word.Document, wdTbl As Word.Table
Public strFolder As String, strFile As String, WkBk As Workbook, WkSht As Worksheet, r As Long
Sub GetTableData()
Application.ScreenUpdating = False
Dim x As Integer
strFolder = GetFolder: If strFolder = "" Then GoTo ErrExit
Set WkBk = ActiveWorkbook
wdApp.WordBasic.DisableAutoMacros
strFile = Dir(strFolder & "\*.doc*", vbNormal)
While strFile <> ""
Set wdDoc = wdApp.Documents.Open(FileName:=strFolder & "\" & strFile, AddToRecentFiles:=False, Visible:=False)
Set WkSht = WkBk.Sheets.Add
x = x + 1
WkSht.Name = Mid(strFile, 20, 29) & x
With wdDoc
For Each wdTbl In .Tables
With wdTbl.Range.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "[^13^l]"
.Replacement.Text = "¶"
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = True
.Execute Replace:=wdReplaceAll
End With
r = WkSht.Cells(WkSht.Rows.Count, 1).End(xlUp).Row
If r > 1 Then r = r + 2
wdTbl.Range.Copy
WkSht.Paste Destination:=WkSht.Range("A" & r)
Next
WkSht.UsedRange.Replace What:="¶", Replacement:=Chr(10), LookAt:=xlPart, SearchOrder:=xlByRows
.Close SaveChanges:=False
End With
strFile = Dir()
Wend
ErrExit:
wdApp.Quit
Set wdDoc = Nothing: Set wdApp = Nothing: Set WkSht = Nothing: Set WkBk = Nothing
Application.ScreenUpdating = True
End Sub
Function GetFolder() As String
Dim oFolder As Object
GetFolder = ""
Set oFolder = CreateObject("Shell.Application").BrowseForFolder(0, "Choose a folder", 0)
If (Not oFolder Is Nothing) Then GetFolder = oFolder.Items.Item.Path
Set oFolder = Nothing
End Function
Function sheet_exists(sheet_to_find As String) As Boolean
Dim work_sheet As Worksheet
sheet_exists = False
For Each work_sheet In ThisWorkbook.Worksheets
If sheet_to_find = work_sheet.Name Then
sheet_exists = True
Exit Function
End If
Next work_sheet
End Function
I'm trying to create a code that reads a dynamic Excel table into an existing Word document and changes some variables in the document (for example %Username%)
The code below gives me an "Locked for editing" error by myself, but that isn't the case.
Can someone see what I have to change in the code?
The code is:
Sub Export_Table_Word()
'Word objects.
Dim wdApp As Word.Application
Dim wdDoc As Word.Document
Dim wdbmRange As Word.Range
'Excel objects.
Dim wbBook As Workbook
Dim wsSheet As Worksheet
Dim rnReport As Range
'Path of Word Template
Dim stPathTemplate As String
Dim stPathSave As String
'Dynamic Replace variables
Dim UserName As String
Dim StrFind
Dim StrRepl As String
'Loop variable
Dim i As Long
Dim msWord As Object
Set msWord = CreateObject("Word.Application")
'Define replacement variables
UserName = Application.UserName
sFirst = Split(UserName, " ")(0) 'Firstname 'sFirst = Split(UserName, ",")(1) 'Firstname
sLast = Split(UserName, " ")(1) 'Lastname
sUserName = Left(sFirst, 1) & sLast 'First letter of firstname and lastname
sFullName = sFirst & " " & sLast 'Full name
StrFind = "%User_Name%,%Full_name%, %Date%" 'Strings to be replaced in the word document
StrRepl = sUserName & "*" & sFullName & "*" & " " & Date 'Replaced by
'Initialize Path word template
stPathTemplate = "C:\Users\xxx\Desktop\VBA_TEST\VBA_Automation.docx"
stPathSave = "C:\Users\xxx\Desktop\VBA_TEST\Finished.docx"
'Initialize the Excel objects.
Set wbBook = ThisWorkbook
Set wsSheet = wbBook.Worksheets(1)
Set rnReport = wsSheet.Range("D2:D7")
'Initialize the Word objets.
Set wdApp = New Word.Application
Set wdDoc = wdApp.Documents.Open(stPathTemplate)
Set wdbmRange = wdDoc.Bookmarks("Report").Range
'If the macro has been run before, clean up any artifacts before trying to paste the table in again.
On Error Resume Next
With wdDoc.InlineShapes(1)
.Select
.Delete
End With
On Error GoTo 0
'Turn off screen updating.
Application.ScreenUpdating = True
With wdDoc
.Visible = True
.Documents.Open (stPathTemplate)
.Activate
With .ActiveDocument.Content.Find
.ClearFormatting
.Replacement.ClearFormatting
For i = 0 To UBound(Split(StrFind, ",")) 'Loop to replace all the defined dynamic strings
.Text = Split(StrFind, ",")(i)
.Replacement.Text = Split(StrRepl, "*")(i)
.Execute Replace:=wdReplaceAll
Next i
.Forward = True
.Wrap = 1 'FindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute Replace:=2 'wdReplaceAll (WdReplace Enumeration)
End With
End With
'Copy the report to the clipboard.
rnReport.Copy
'Select the range defined by the "Report" bookmark and paste in the report from clipboard.
With wdbmRange
.Select
.Paste
End With
'Save and close the Word doc.
With wdDoc
'.Save
.SaveAs2 Filename:=stPathSave, _
FileFormat:=wdFormatXMLDocument, AddToRecentFiles:=False
.Close
End With
'Quit Word.
wdApp.Quit
'Null out your variables.
Set wdbmRange = Nothing
Set wdDoc = Nothing
Set wdApp = Nothing
'Clear out the clipboard, and turn screen updating back on.
With Application
.CutCopyMode = False
.ScreenUpdating = True
End With
MsgBox ("Done")
End Sub