VBA Word Find And Replace From Excel /&- - vba

I am creating a macro to search word documents for exact matches against acronyms in an Excel file. If the acronym is in the Word file, the macro highlights the acronym and inserts the row number into an array for use in a to-be-written macro to generate a Table of Acronyms.
The below macro works, however there are several false positives whenever I run it. This occurs when certain acronyms contain special characters, notably "&", "/" and "-".
For example, if I run the below macro on a file that contains RT&E, the code will insert the row number for "RT and "RT&E" and "T&E" into the array (provided all three are in the first column in the excel file).
This is not a problem on small documents, but for 150 page documents, it's just too much. I also apologize for the bad code. Suggestions to make it better are appreciated.
Dim rng As range
Dim i As Long
Dim acro As String
Dim acrolist As Excel.Application
Dim acrobook As Excel.Workbook
Dim acromatch() As Variant
ReDim acromatch(0 To 1)
Set acrolist = New Excel.Application
Set acrobook = acrolist.Workbooks.Open("P:\AcronymMacro\MasterAcronymList.xlsm")
' Count from first row with acronym to maximum # of rows
' That way, list can be as long or short as needed
For i = 3 To 1048576
Set rng = ActiveDocument.range
acro = acrobook.Sheets(1).Cells(i + 1, 1)
' Loop breaks when it finds an empty cell
' i.e. the last acronym in the document.
If acro = "" Then Exit For
' Find and Replace code
With rng.Find
.Text = acro
.Format = True
.MatchCase = True
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
' Do While loop
Do While .Execute(Forward:=True) = True
rng.HighlightColorIndex = wdPink
Call InsertIntoArray(acromatch(), i + 1)
Loop
End With
Next
MsgBox Join(acromatch(), ",")
'Make sure you close your files, ladies and gentlemen!
acrobook.Close False
Set acrolist = Nothing
Set acrobook = Nothing
' This function resizes array and insert value as last value
Public Function InsertIntoArray(InputArray As Variant, Value As Variant)
ReDim Preserve InputArray(LBound(InputArray) To UBound(InputArray) + 1)
InputArray(UBound(InputArray)) = Value
End Function
One thing I tried was to run another Range.Find method in the Do While Loop, with a slight change to the acronym. For instance the below code makes sure there is a space, period, or close parentheses and not an ampersand and hyphen after the acronym. If it is different, then it doesn't get added.
Do While .Execute(Forward:=True) = True
rng.HighlightColorIndex = wdPink
acro = acro + "[ .)]"
With rng.Find
.Text = acro
.MatchWildCards = True
If rng.Find.Execute(Forward=True) = True Then Call InsertIntoArray(acromatch(), i + 1)
End With
Loop
This code, however, makes sure nothing gets into the array.
How to I present false positives when acronyms have special characters in acronyms?

here is a rewrite of your code
it puts the data from excel into an array, then the array is searched
no correction made for the problem with special characters
Sub acroTest()
Dim acromatch() As Variant
ReDim acromatch(0 To 1)
Dim acrolist As Excel.Application
Set acrolist = New Excel.Application
Dim acrobook As Excel.Workbook
Set acrobook = acrolist.Workbooks.Open("P:\AcronymMacro\MasterAcronymList.xlsm")
Dim rng As Range ' msWord range
Set rng = ActiveDocument.Range
With rng.Find ' set up find command
.Format = True ' these are "remembered" until changed
.MatchCase = True ' same as the "find" dialog box
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
' Count from first row with acronym to maximum # of rows
' That way, list can be as long or short as needed
' could loop excel range this way
' constant xlCellTypeConstants = 2
' Dim acro As Excel.Range
' For Each acro In acrobook.Sheets(1).Range("a3:a1048576").SpecialCells(xlCellTypeConstants) ' all non-blank, non-formula cells
Dim acro As Excel.Range
Set acro = acrolist.Range(acrobook.Sheets(1).Range("A3"), acrobook.Sheets(1).Cells(1048576, "A").End(xlUp)) ' range A3 to last used cell in A column
Dim wordsInExcel As Variant ' column A gets put into an array for faster execution
wordsInExcel = acro.Value ' convert excel range to 2d array (1 x N)
wordsInExcel = acrolist.Transpose(wordsInExcel) ' convert result to 2d array (N x 1)
wordsInExcel = acrolist.Transpose(wordsInExcel) ' convert again to get 1d array
Dim i As Long
For i = 1 To UBound(wordsInExcel)
rng.Find.Text = wordsInExcel(i) ' this is "search text"
Do While rng.Find.Execute(Forward:=True) = True ' do the actual search
rng.HighlightColorIndex = wdPink
Call InsertIntoArray(acromatch(), i + 1)
Loop
Next
MsgBox Join(acromatch(), ",")
' Make sure you close your files, ladies and gentlemen!
acrobook.Close False
Set acrolist = Nothing
Set acrobook = Nothing
End Sub
' This function resizes array and insert value as last value
Public Function InsertIntoArray(InputArray As Variant, Value As Variant)
ReDim Preserve InputArray(LBound(InputArray) To UBound(InputArray) + 1)
InputArray(UBound(InputArray)) = Value
End Function

Related

How do I search and highlight multiple terms in Microsoft Word?

My goal is to be able to run this script and have the document search for and highlight a set number of terms, typically 10+ terms. I figured out how to do this with another script I found here, but every time I use it Word crashes.
Below is a simpler version I have pieced together from different forums/videos I found online. It does exactly what I want it to do except I can't figure out how to make it look for more than one term.
The .Text = "Text" works great but only for one term. If I list multiple then it only looks for the one I listed last. I have tested other chunks of code I found online but I can't figure it out.
I am hoping it is a simple fix, especially since the rest of the code does what I want. TIA!
Sub UsingTheFindObject_Medium()
'Declare Variables.
Dim wrdFind As Find
Dim wrdRng As range
Dim wrdDoc As Document
'Grab the ActiveDocument.
Set wrdDoc = Application.ActiveDocument
'Define the Content in the document
Set wrdRng = wrdDoc.Content
'Define the Find Object based on the Range.
Set wrdFind = wrdRng.Find
'Define the parameters of the Search.
With wrdFind
'Search the text for the following term(s)
.Text="Test"
.Format = True
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While wrdFind.Execute = True
'Change the color to Yellow.
wrdRng.HighlightColorIndex = wdYellow
Loop
End Sub
This will do what you want.
Sub HighlightMultipleWords()
Dim sArr() As String
Dim rTmp As Range
Dim x As Long
sArr = Split("highlight specific words") ' your list
Options.DefaultHighlightColorIndex = wdYellow
For x = 0 To UBound(sArr)
Set rTmp = ActiveDocument.Range
With rTmp.Find
.Text = sArr(x)
.Replacement.Text = sArr(x)
.Replacement.Highlight = True
.Execute Replace:=wdReplaceAll
End With
Next
End Sub
Before:
After:
Use your current routine as a function.
Here is an example.
Function FindAndMark(sText As String) ' UsingTheFindObject_Medium()
' https://stackoverflow.com/questions/69633517/how-do-i-search-and-highlight-multiple-terms-in-microsoft-word
' Charles Kenyon
'Declare Variables.
Dim wrdFind As Find
Dim wrdRng As Range
Dim wrdDoc As Document
'Grab the ActiveDocument.
Set wrdDoc = Application.ActiveDocument
'Define the Content in the document
Set wrdRng = wrdDoc.Content
'Define the Find Object based on the Range.
Set wrdFind = wrdRng.Find
'Define the parameters of the Search.
With wrdFind
'Search the text for the following term(s)
.Text = sText
.Format = True
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
' Mark text
Do While wrdFind.Execute = True
'Change the color to Yellow.
wrdRng.HighlightColorIndex = wdYellow
Loop
Set wrdFind = Nothing
Set wrdRng = Nothing
Set wrdDoc = Nothing
End Function
Sub MultiFindMark()
' https://stackoverflow.com/questions/69633517/how-do-i-search-and-highlight-multiple-terms-in-microsoft-word
' Charles Kenyon
Dim i As Integer
Const n As Integer = 4 ' set number (n) of terms in search
Dim sArray(n) As String ' Create array to hold terms
' Assign values, starting at 0 and going to n-1
Let sArray(0) = "Aenean"
Let sArray(1) = "Pellentesque"
Let sArray(2) = "libero"
Let sArray(3) = "pharetra"
For i = 0 To n - 1
FindAndMark (sArray(i))
Next i
End Sub
Here is a revision using the code from ASH to handle the Array
Sub MultiFindMark2()
' https://stackoverflow.com/questions/69633517/how-do-i-search-and-highlight-multiple-terms-in-microsoft-word
' Charles Kenyon
' modified to use methods proposed by ASH
Dim i As Long
Dim sArray() As String ' Create array to hold terms
' Assign values, starting at 0 and going to n-1
sArray = Split("Aenean Pellentesque libero pharetra") ' your list separated by spaces
For i = 0 To UBound(sArray)
FindAndMark (sArray(i))
Next i
End Sub
With some of the changes showing as comments:
Sub MultiFindMark2()
' https://stackoverflow.com/questions/69633517/how-do-i-search-and-highlight-multiple-terms-in-microsoft-word
' Charles Kenyon
' modified to use methods proposed by ASH
Dim i As Long
' Const n As Integer = 4 ' set number (n) of terms in search
Dim sArray() As String ' Create array to hold terms
' Assign values, starting at 0 and going to n-1
sArray = Split("Aenean Pellentesque libero pharetra") ' your list separated by spaces
' Let sArray(0) = "Aenean"
' Let sArray(1) = "Pellentesque"
' Let sArray(2) = "libero"
' Let sArray(3) = "pharetra"
For i = 0 To UBound(sArray)
FindAndMark (sArray(i))
Next i
End Sub
Note, this still requires the function.

VBA Word macro not working as expected with field results in document

I have a word document (report) and in that document, I'm importing many text files with fields like this:
{INCLUDETEXT "C:\\PATH\\TOXMLFILES\\Request.xml" \*CHARFORMAT}
Also I'm updating all those fields with a macro on opening the document...
Sub AutoOpen()
With Options
.UpdateFieldsAtPrint = True
.UpdateLinksAtPrint = True
End With
ActiveDocument.Fields.Update
End Sub
Now I need to highlight the text of those imported XMLs (in the IncludeText fields) between <faultstring></faultstring> tags
Here is code I got here on stackoverflow for highlighting text (making it bold)
Sub BoldBetweenQuotes()
' base for a quotes finding macro
Dim blnSearchAgain As Boolean
Dim blnFindStart As Boolean
Dim blnFindEnd As Boolean
Dim rngFind As word.Range
Dim rngFindStart As word.Range
Dim rngFindEnd As word.Range
Set rngFind = ActiveDocument.content
Set rngFindStart = rngFind.Duplicate
Do
' set up find of first of quote pair
With rngFindStart.Find
.ClearFormatting
.Text = "<faultstring>"
.Replacement.Text = ""
.Forward = True
.wrap = wdFindStop
blnFindStart = .Execute
End With
If blnFindStart Then
rngFindStart.Collapse wdCollapseEnd
Set rngFindEnd = rngFindStart.Duplicate
rngFindEnd.Find.Text = "</faultstring>"
blnFindEnd = rngFindEnd.Find.Execute
If blnFindEnd Then
rngFindStart.End = rngFindEnd.Start
' make it bold
rngFindStart.Font.Bold = True
rngFindStart.Start = rngFindEnd.End
rngFindStart.End = rngFind.End
blnSearchAgain = True
Else
blnSearchAgain = False
End If
Else
blnSearchAgain = False
End If
Loop While blnSearchAgain = True
End Sub
Problem is, when I run the macro in my Word document (with the IncludeText fields) it keeps cycling and bolding just the first appearance of text between faultstring tags. When I run it in a new Word document with some random text and faultrstring tags it works well...
EDIT: It turns out the problem is due to the faultstring tags being inside the IncludeText fields. I need to turn the fields into static text after opening the document and updating the fields. How can I do that?
In order to convert dynamic field content to static text using Word's object model (such as VBA) the Fields.Unlink method is required. For the entire document:
ActiveDocument.Fields.Unlink
This is also possible for any given Range; to remove the fields in the last paragraph, for example:
ActiveDocument.Paragraphs.Last.Range.Fields.Unlink
In order to unlink only a certain type of field, loop the Fields collection, test the Field.Type and unlink accordingly. For example, for IncludeText:
Sub DeleteIncludeTextFields()
Dim doc As word.Document
Set doc = ActiveDocument
Debug.Print DeleteFieldType(wdFieldIncludeText, doc)
End Sub
Function DeleteFieldType(fldType As word.WdFieldType, doc As word.Document) _
As Long
Dim fld As word.Field
Dim counter As Long
counter = 0
For Each fld In doc.Fields
If fld.Type = wdFieldIncludeText Then
fld.Unlink
counter = counter + 1
End If
Next
DeleteFieldType = counter
End Function
Assuming you want to do this for all the fields in your document, after updating it:
Sub AutoOpen()
With Options
.UpdateFieldsAtPrint = True
.UpdateLinksAtPrint = True
End With
ActiveDocument.Fields.Update
ActiveDocument.Fields.Unlink
End Sub

VBA word add caption

I am trying to add captions to a word document, using VBA. I am using the following code. The data starts off as tables in an Excel spreadsheet, with one per sheet. We are trying to generate a list of tables in the word document.
The following code loads starts editing a word template:
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
Set objDoc = objWord.Documents.Add("Template path")
' Moving to end of word document
objWord.Selection.EndKey END_OF_STORY, MOVE_SELECTION
' Insert title
objWord.Selection.Font.Size = "16"
objWord.Selection.Font.Bold = True
objWord.Selection.TypeText ("Document name")
objWord.Selection.ParagraphFormat.SpaceAfter = 12
objWord.Selection.InsertParagraphAfter
The following code loops through the sheets in the worksheet and adds the tables and headers.
' Declaring variables
Dim Wbk As Workbook
Dim Ws As Worksheet
Dim END_OF_STORY As Integer: END_OF_STORY = 6
Dim MOVE_SELECTION As Integer: MOVE_SELECTION = 0
Dim LastRow As Integer
Dim LastColumn As Integer
Dim TableCount As Integer
Dim sectionTitle As String: sectionTitle = " "
' Loading workbook
Set Wbk = Workbooks.Open(inputFileName)
' Moving to end of word document
objWord.Selection.EndKey END_OF_STORY, MOVE_SELECTION
' Looping through all spreadsheets in workbook
For Each Ws In Wbk.Worksheets
' Empty Clipboard
Application.CutCopyMode = False
objWord.Selection.insertcaption Label:="Table", title:=": " & Ws.Range("B2").Text
In the cell B2, I have the following text: "Table 1: Summary". I am hoping for the word document to have a header which reflects this text. The problem is the table number is repeated twice, and I get output: "Table 1: Table 1: Summary". I tried the following alterations, both of which resulted in errors:
objWord.Selection.insertcaption Label:="", title:="" & Ws.Range("B2").Text
objWord.Selection.insertcaption Label:= Ws.Range("B2").Text
What am I doing wrong, and more generally how does the insertcaption method work?
I have tried reading this, but am confused by the syntax.
https://msdn.microsoft.com/en-us/vba/word-vba/articles/selection-insertcaption-method-word
One of the built-in features of using the Caption style in MS Word is the automatic numbering it applies and dynamically adjust in your document. You are explicitly trying to manage the table numbering yourself - which is fine - but you'll then have to un-do some of Word's automatic helpful numbering in your code.
Working from Excel, I've tested the code below to set up a test document with Captions and then a quick routine to remove the automatic part of the label. This example code works as a stand-alone test to illustrate how I worked it, leaving it to you to adapt to your own code.
The initial test sub simply establishes the Word.Application and Document objects, then creates three tables with following paragraphs. Each of the tables has it's own caption (which shows the doubled up label, due to the automatic labeling from Word). The code throws up a MsgBox to pause so you can take a look at the document before it's modified.
Then the code goes back and searches the entire document for any Caption styles and examines the text within the style to find the double label. I made the assumption that a double label is present if there are two colons ":" detected in the caption text. The first label (up to and past the first colon) is removed and the text replaced. With that, the resulting document looks like this:
The code:
Option Explicit
Sub test()
Dim objWord As Object
Dim objDoc As Object
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
Set objDoc = objWord.documents.Add
Dim newTable As Object
Set newTable = objDoc.Tables.Add(Range:=objDoc.Range, NumRows:=3, NumColumns:=1)
newTable.Borders.Enable = True
newTable.Range.InsertCaption Label:="Table", Title:=": Table 1: summary xx"
objDoc.Range.InsertParagraphAfter
objDoc.Range.InsertAfter "Lorem ipsum"
objDoc.Characters.Last.Select
objWord.Selection.Collapse
Set newTable = objDoc.Tables.Add(Range:=objWord.Selection.Range, NumRows:=3, NumColumns:=2)
newTable.Range.InsertCaption Label:="Table", Title:=": Table 2: summary yy"
newTable.Borders.Enable = True
objDoc.Range.InsertParagraphAfter
objDoc.Range.InsertAfter "Lorem ipsum"
objDoc.Characters.Last.Select
objWord.Selection.Collapse
Set newTable = objDoc.Tables.Add(Range:=objWord.Selection.Range, NumRows:=3, NumColumns:=3)
newTable.Range.InsertCaption Label:="Table", Title:=": Table 3: summary zz"
newTable.Borders.Enable = True
objDoc.Range.InsertParagraphAfter
objDoc.Range.InsertAfter "Lorem ipsum"
MsgBox "document created. hit OK to continue"
RemoveAutoCaptionLabel objWord
Debug.Print "-----------------"
End Sub
Sub RemoveAutoCaptionLabel(ByRef objWord As Object)
objWord.Selection.HomeKey 6 'wdStory=6
With objWord.Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Style = "Caption"
.Text = ""
.Forward = True
.Wrap = 1 'wdFindContinue=1
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
Do While .Execute()
RemoveDoubleLable objWord.Selection.Range
objWord.Selection.Collapse 0 'wdCollapseEnd=0
Loop
End With
End Sub
Sub RemoveDoubleLable(ByRef capRange As Object)
Dim temp As String
Dim pos1 As Long
Dim pos2 As Long
temp = capRange.Text
pos1 = InStr(1, temp, ":", vbTextCompare)
pos2 = InStr(pos1 + 1, temp, ":", vbTextCompare)
If (pos1 > 0) And (pos2 > 0) Then
temp = Trim$(Right$(temp, Len(temp) - pos1 - 1))
capRange.Text = temp
End If
End Sub

Run Time Error '1004': Paste Method Of worksheet Class Failed error

Copy pasting 1 line of text from word to excel using VBA.
When the code reaches the below line I am getting the below error.
ActiveSheet.Paste
Run Time Error '1004': Paste Method Of worksheet Class Failed error
But if I click Debug button and press F8 then it's pasting the data in excel without any error.
This error occurs each time the loop goes on and pressing debug and F8 pasting the data nicely.
I did several testing and unable to find the root cause of this issue.
Also used DoEvents before pasting the data code but nothing worked.
Any suggestions?
EDIT:-
I am posting the code since both of you are saying the same. Here is the code for your review.
Sub FindAndReplace()
Dim vFR As Variant, r As Range, i As Long, rSource As Range
Dim sCurrRep() As String, sGlobalRep As Variant, y As Long, x As Long
Dim NumCharsBefore As Long, NumCharsAfter As Long
Dim StrFind As String, StrReplace As String, CountNoOfReplaces As Variant
'------------------------------------------------
Dim oWord As Object
Const wdReplaceAll = 2
Set oWord = CreateObject("Word.Application")
'------------------------------------------------
Application.ScreenUpdating = False
vFR = ThisWorkbook.Sheets("Sheet1").Range("A1").CurrentRegion.Value
On Error Resume Next
Set rSource = Cells.SpecialCells(xlCellTypeConstants)
On Error GoTo 0
If Not rSource Is Nothing Then
For Each r In rSource.Cells
For i = 2 To UBound(vFR)
If Trim(vFR(i, 1)) <> "" Then
With oWord
.Documents.Add
DoEvents
r.Copy
.ActiveDocument.Content.Paste
NumCharsBefore = .ActiveDocument.Characters.Count
With .ActiveDocument.Content.Find
.ClearFormatting
.Font.Bold = False
.Replacement.ClearFormatting
.Execute FindText:=vFR(i, 1), ReplaceWith:=vFR(i, 2), Format:=True, Replace:=wdReplaceAll
End With
.Selection.Paragraphs(1).Range.Select
.Selection.Copy
r.Select
ActiveSheet.Paste'Error occurs in this line pressing debug and F8 is pasting the data
StrFind = vFR(i, 1): StrReplace = vFR(i, 2)
NumCharsAfter = .ActiveDocument.Characters.Count
CountNoOfReplaces = (NumCharsBefore - NumCharsAfter) / (Len(StrFind) - Len(StrReplace))
.ActiveDocument.UndoClear
.ActiveDocument.Close SaveChanges:=False
If CountNoOfReplaces Then
x = x + 1
ReDim Preserve sCurrRep(1 To 3, 1 To x)
sCurrRep(1, x) = vFR(i, 1)
sCurrRep(2, x) = vFR(i, 2)
sCurrRep(3, x) = CountNoOfReplaces
End If
CountNoOfReplaces = 0
End With
End If
Next i
Next r
End If
oWord.Quit
'Some more gode goes here... which is not needed since error occurs in the above loop
End Sub
If you want to know why I have chosen word for replacement then please go through the below link.
http://www.excelforum.com/excel-programming-vba-macros/1128898-vba-characters-function-fails-when-the-cell-content-exceeds-261-characters.html
Also used the code from the below link to get the number of replacements count.
http://word.mvps.org/faqs/macrosvba/GetNoOfReplacements.htm
Characters(start, length).Delete() method really seems not to work with longer strings in Excel :(. So a custom Delete() method could be written which will work with decoupled formating informations and texts. So the text of the cell can be modified without loosing the formating information. HTH.
Add new class named MyCharacter. It will contain information about text and
formating of one character:
Public Text As String
Public Index As Integer
Public Name As Variant
Public FontStyle As Variant
Public Size As Variant
Public Strikethrough As Variant
Public Superscript As Variant
Public Subscript As Variant
Public OutlineFont As Variant
Public Shadow As Variant
Public Underline As Variant
Public Color As Variant
Public TintAndShade As Variant
Public ThemeFont As Variant
Add next new class named MyCharcters and wrap the code of the new
Delete method in it. With Filter method a new collection of MyCharacter is created. This collection contains only the characters which should remain. Finally in method Rewrite the text is re-written from this collection back to target range along with formating info:
Private m_targetRange As Range
Private m_start As Integer
Private m_length As Integer
Private m_endPosition As Integer
Public Sub Delete(targetRange As Range, start As Integer, length As Integer)
Set m_targetRange = targetRange
m_start = start
m_length = length
m_endPosition = m_start + m_length - 1
Dim filterdChars As Collection
Set filterdChars = Filter
Rewrite filterdChars
End Sub
Private Function Filter() As Collection
Dim i As Integer
Dim newIndex As Integer
Dim newChar As MyCharacter
Set Filter = New Collection
newIndex = 1
For i = 1 To m_targetRange.Characters.Count
If i < m_start Or i > m_endPosition Then
Set newChar = New MyCharacter
With newChar
.Text = m_targetRange.Characters(i, 1).Text
.Index = newIndex
.Name = m_targetRange.Characters(i, 1).Font.Name
.FontStyle = m_targetRange.Characters(i, 1).Font.FontStyle
.Size = m_targetRange.Characters(i, 1).Font.Size
.Strikethrough = m_targetRange.Characters(i, 1).Font.Strikethrough
.Superscript = m_targetRange.Characters(i, 1).Font.Superscript
.Subscript = m_targetRange.Characters(i, 1).Font.Subscript
.OutlineFont = m_targetRange.Characters(i, 1).Font.OutlineFont
.Shadow = m_targetRange.Characters(i, 1).Font.Shadow
.Underline = m_targetRange.Characters(i, 1).Font.Underline
.Color = m_targetRange.Characters(i, 1).Font.Color
.TintAndShade = m_targetRange.Characters(i, 1).Font.TintAndShade
.ThemeFont = m_targetRange.Characters(i, 1).Font.ThemeFont
End With
Filter.Add newChar, CStr(newIndex)
newIndex = newIndex + 1
End If
Next i
End Function
Private Sub Rewrite(chars As Collection)
m_targetRange.Value = ""
Dim i As Integer
For i = 1 To chars.Count
If IsEmpty(m_targetRange.Value) Then
m_targetRange.Value = chars(i).Text
Else
m_targetRange.Value = m_targetRange.Value & chars(i).Text
End If
Next i
For i = 1 To chars.Count
With m_targetRange.Characters(i, 1).Font
.Name = chars(i).Name
.FontStyle = chars(i).FontStyle
.Size = chars(i).Size
.Strikethrough = chars(i).Strikethrough
.Superscript = chars(i).Superscript
.Subscript = chars(i).Subscript
.OutlineFont = chars(i).OutlineFont
.Shadow = chars(i).Shadow
.Underline = chars(i).Underline
.Color = chars(i).Color
.TintAndShade = chars(i).TintAndShade
.ThemeFont = chars(i).ThemeFont
End With
Next i
End Sub
How to use it:
Sub test()
Dim target As Range
Dim myChars As MyCharacters
Application.ScreenUpdating = False
Set target = Worksheets("Demo").Range("A1")
Set myChars = New MyCharacters
myChars.Delete targetRange:=target, start:=300, length:=27
Application.ScreenUpdating = True
End Sub
Before:
After:
To make it more stable, you should:
Disable all events while operating
Never call .Activate or .Select
Paste directly in the targeted cell with WorkSheet.Paste
Cancel the Copy operation with Application.CutCopyMode = False
Reuse the same document and not create one for each iteration
Do as less operations as possible in an iteration
Use early binding [New Word.Application] instead of late binding [CreateObject("Word.Application")]
Your example refactored :
Sub FindAndReplace()
Dim dictionary(), target As Range, ws As Worksheet, cell As Range, i As Long
Dim strFind As String, strReplace As String, diffCount As Long, replaceCount As Long
Dim appWord As Word.Application, content As Word.Range, find As Word.find
dictionary = [Sheet1!A1].CurrentRegion.Value
Set target = Cells.SpecialCells(xlCellTypeConstants)
' launch and setup word
Set appWord = New Word.Application
Set content = appWord.Documents.Add().content
Set find = content.find
find.ClearFormatting
find.Font.Bold = False
find.replacement.ClearFormatting
' disable events
Application.Calculation = xlManual
Application.ScreenUpdating = False
Application.EnableEvents = False
' iterate each cell
Set ws = target.Worksheet
For Each cell In target.Cells
' copy the cell to Word and disable the cut
cell.Copy
content.Delete
content.Paste
Application.CutCopyMode = False
' iterate each text to replace
For i = 2 To UBound(dictionary)
If Trim(dictionary(i, 1)) <> Empty Then
replaceCount = 0
strFind = dictionary(i, 1)
strReplace = dictionary(i, 2)
' replace in the document
diffCount = content.Characters.count
find.Execute FindText:=strFind, ReplaceWith:=strReplace, format:=True, Replace:=2
' count number of replacements
diffCount = diffCount - content.Characters.count
If diffCount Then
replaceCount = diffCount \ (Len(strFind) - Len(strReplace))
End If
Debug.Print replaceCount
End If
Next
' copy the text back to Excel
content.Copy
ws.Paste cell
Next
' terminate Word
appWord.Quit False
' restore events
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
How about change it from: activesheet.paste
to:
activesheet.activate
activecell.pastespecial xlpasteAll
This post seems to explain the problem and provide two solutions:
http://www.excelforum.com/excel-programming-vba-macros/376722-runtime-error-1004-paste-method-of-worksheet-class-failed.html
Two items come to light in this post:
Try using Paste Special
Specify the range you wish to paste to.
Another solution would be to extract the targeted cells as XML, replace the text with a regular expression and then write the XML back to the sheet.
While it's much faster than working with Word, it might require some knowledge with regular expressions if the formats were to be handled. Moreover it only works with Excel 2007 and superior.
I've assemble an example that replaces all the occurences with the same style:
Sub FindAndReplace()
Dim area As Range, dictionary(), xml$, i&
Dim matchCount&, replaceCount&, strFind$, strReplace$
' create the regex object
Dim re As Object, match As Object
Set re = CreateObject("VBScript.RegExp")
re.Global = True
re.MultiLine = True
' copy the dictionary to an array with column1=search and column2=replacement
dictionary = [Sheet1!A1].CurrentRegion.Value
'iterate each area
For Each area In ActiveSheet.Cells.SpecialCells(xlCellTypeConstants)
' read the cells as XML
xml = area.Value(xlRangeValueXMLSpreadsheet)
' iterate each text to replace
For i = 2 To UBound(dictionary)
If Trim(dictionary(i, 1)) <> Empty Then
strFind = dictionary(i, 1)
strReplace = dictionary(i, 2)
' set the pattern
re.pattern = "(>[^<]*)" & strFind
' count the number of occurences
matchCount = re.Execute(xml).count
If matchCount Then
' replace each occurence
xml = re.Replace(xml, "$1" & strReplace)
replaceCount = replaceCount + matchCount
End If
End If
Next
' write the XML back to the sheet
area.Value(xlRangeValueXMLSpreadsheet) = xml
Next
' print the number of replacement
Debug.Print replaceCount
End Sub
DDuffy's answer is useful.
I found the code can run normally at slowly cpu PC .
add the bellow code before paste, the problem is sloved:
Application.Wait (Now + TimeValue("0:00:1"))'wait 1s or more
ActiveSheet.Paste

Word VBA: Get Range between Consecutive Headings

I looked up some examples, but I cannot quite understand how the Range object works. I am trying to loop through each of my headings (of level 4) and have a nested loop that looks through all the tables in between the headings. I cannot figure out how to set that specific range, so any help will be greatly appreciated.
Dim myHeadings As Variant
myHeadings = ActiveDocument.GetCrossReferenceItems(wdRefTypeHeading)
For iCount = LBound(myHeadings) To UBound(myHeadings)
level = getLevel(CStr(myHeadings(iCount)))
If level = 4 Then
'This is where I want to set a range between myHeadings(iCount) to myHeadings(iCount+1)
set aRange = ??
End If
Next iCount
You are on the right track here. The myHeadings variable you have simply gives a list of the strings of the Level 4 Headings in the document. What you need to do is then search the document for those strings to get the range of the Level 4 Headings.
Once you have the range of each of the headings you can check for the tables in the range between these headings. I've modified your code slightly to do this. Also note its good practice to put Option Explicit at the top of your module to ensure all variables are declared.
My code will tell you how many tables are between each of the Level 4 headings. NOTE: It does not check between the last heading and the end of the document, I'll leave that up to you ;)
Sub DoMyHeadings()
Dim iCount As Integer, iL4Count As Integer, Level As Integer, itabCount As Integer
Dim myHeadings As Variant, tbl As Table
Dim Level4Heading() As Range, rTableRange As Range
myHeadings = ActiveDocument.GetCrossReferenceItems(wdRefTypeHeading)
'We want to move to the start of the document so we can loop through the headings
Selection.HomeKey Unit:=wdStory
For iCount = LBound(myHeadings) To UBound(myHeadings)
Level = getLevel(CStr(myHeadings(iCount)))
If Level = 4 Then
'We can now search the document to find the ranges of the level 4 headings
With Selection.Find
.ClearFormatting 'Always clear find formatting
.Style = ActiveDocument.Styles("Heading 4") 'Set the heading style
.Text = VBA.Trim$(myHeadings(iCount)) 'This is the heading text (trim to remove spaces)
.Replacement.Text = "" 'We are not replacing the text
.Forward = True 'Move forward so we can each consecutive heading
.Wrap = wdFindContinue 'Continue to the next find
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
End With
'Just make sure the text matches (it should be I have a habit of double checking
If Selection.Text = VBA.Trim$(myHeadings(iCount)) Then
iL4Count = iL4Count + 1 'Keep a counter for the L4 headings for redim
ReDim Preserve Level4Heading(1 To iL4Count) 'Redim the array keeping existing values
Set Level4Heading(iL4Count) = Selection.Range 'Set the range you've just picked up to the array
End If
End If
Next iCount
'Now we want to loop through all the Level4 Heading Ranges
For iCount = LBound(Level4Heading) To UBound(Level4Heading) - 1
'Reset the table counter
itabCount = 0
'Use the start of the current heading and next heading to get the range in between which will contain the tables
Set rTableRange = ActiveDocument.Range(Level4Heading(iCount).Start, Level4Heading(iCount + 1).Start)
'Now you have set the range in the document between the headings you can loop through
For Each tbl In rTableRange.Tables
'This is where you can work your table magic
itabCount = itabCount + 1
Next tbl
'Display the number of tables
MsgBox "You have " & itabCount & " table(s) between heading " & Level4Heading(iCount).Text & " And " & Level4Heading(iCount + 1).Text
Next iCount
End Sub
You could jump from one heading to the next using Goto. See below how to loop through level 4 headings.
Dim heading As Range
Set heading = ActiveDocument.Range(start:=0, End:=0)
Do ' Loop through headings
Dim current As Long
current = heading.start
Set heading = heading.GoTo(What:=wdGoToHeading, Which:=wdGoToNext)
If heading.start = current Then
' We haven't moved because there are no more headings
Exit Do
End If
If heading.Paragraphs(1).OutlineLevel = wdOutlineLevel4 Then
' Now this is a level 4 heading. Let's do something with it.
' heading.Expand Unit:=wdParagraph
' Debug.Print heading.Text
End If
Loop
Don't look specifically for "Heading 4" because,
one may use non built-in styles,
it would not work with international versions of Word.
Check the wdOutlineLevel4 instead.
Now, to get the range for the whole level 4, here is a little known trick:
Dim rTableRange as Range
' rTableRange will encompass the region under the current/preceding heading
Set rTableRange = heading.GoTo(What:=wdGoToBookmark, Name:="\HeadingLevel")
This will work better for the last heading 4 in the document or the last one below a heading 3.