Find nearest Heading above the MS Word table - vba

I am enumerating tables in Microsoft Word in a following way:
Dim doc As Document, t As Table
Set doc = ActiveDocument
For Each t In doc.Tables
Next t
Now I would like to find the nearest paragraph with "Heading 2" style above the table and get it's text into a variable. Great if it could be accomplished without changing the selection focus in the document.
I can enumerate paragraphs in the document, but how to determine that some paragraph is above some table?

I solved that by building a list of paragraph start positions:
Private Type CaptionRec
Text As String
EndPos As Long
End Type
Dim caps() As CaptionRec
Dim i As Long
Dim p As Paragraph
ReDim caps(0)
i = 0
For Each p In doc.Paragraphs
If p.Style = "Überschrift 2" Then
i = i + 1
ReDim Preserve caps(i)
caps(i).Text = TrimGarbageAtEnd(p.Range.Text)
caps(i).EndPos = p.Range.Start 'Ok, this should be the end, not the start
End If
Next p
... and finding the minimum distance between table start and a "Heading 2" paragraph from array:
Public Function GetClosestCaption(tableStart As Long, ByRef caps() As CaptionRec) As String
Dim cap As CaptionRec, distance As Long, minDistance As Long, res As String, i As Long
minDistance = 2147483647 'Max long
res = ""
For i = LBound(caps) To UBound(caps)
cap = caps(i)
distance = tableStart - cap.EndPos
If distance >= 0 Then
If distance < minDistance Then
minDistance = distance
res = cap.Text
End If
End If
Next i
GetClosestCaption = res
End Function
The routine gets called in a following loop:
Public Sub MainRoutine()
For Each t In doc.Tables
If table_validity_criteria_go_here Then
caption = GetClosestCaption(t.Range.Start, caps)
For Each r In t.Rows
'Enumerate rows
Next r
End If
Next t
End Sub

An alternative is to reverse the logic. Instead of processing the tables and then looking for the associated heading, find the headings then process the tables within the range of the heading level, For example:
Sub FindHeading2Ranges()
Dim findRange As Range
Dim headingRange As Range
Set findRange = ActiveDocument.Content
With findRange.Find
.ClearFormatting
.Forward = True
.Wrap = wdFindStop
.Format = True
.Style = ActiveDocument.Styles(wdStyleHeading2)
Do While .Execute
Set headingRange = findRange.GoTo(What:=wdGoToBookmark, Name:="\HeadingLevel")
If headingRange.Tables.Count > 0 Then
ProcessTables headingRange, TrimGarbageAtEnd(findRange.text)
End If
findRange.Collapse wdCollapseEnd
Loop
End With
End Sub
Sub ProcessTables(headingRange As Range, caption As String)
Dim t As Table
For Each t In headingRange.Tables
If table_validity_criteria_go_here Then
For Each r In t.Rows
'Enumerate rows
Next r
End If
Next t
End Sub

Related

Word VBA Move Images and Text into a Table

I'm trying to check every section of a document for images or grouped images and, if they're found, create a table with 1 row and 2 columns at the beginning of that section, where the first column will contain the text (with original formatting) and the second column will contain the images. I have converted all the images in the document to inline shapes.
Edit: In the document, there's random amounts of text (and/or other characters) before, after and in between a random amount of images. Sometimes a section has no text and only images. For each section, I would like all of the text (with original formatting and in the order in which it occurs) to be contained in the first column, and all images and grouped images (also in their same order) to be contained in the second folder. Ideally, if the only things on the page are a heading and an image, they would be put into a 1x1 table (with the heading above the image).
I've tried a few variations of this with no success. Generally stuff starts getting pretty messy because I have no idea what I'm doing. I've left out the text in this code because it was only complicating things, but I would like to move the text as well.
Sub ToTables()
Dim iShp As InlineShape
Dim oRng As Range
Dim oTbl As Table
Dim i As Integer
Dim a As Integer
Dim b As Integer
a = ActiveDocument.BuiltInDocumentProperties("Number of Sections")
For i = 1 To a
Set oRng = ActiveDocument.GoTo(What:=wdGoToSection, Name:=i)
Set oRng = oRng.GoTo(What:=wdGoToBookmark, Name:="\section")
If Right(oRng, 1) = vbCr Then _
oRng = Left(oRng, Len(oRng) - 1)
b = oRng.InlineShapes.Count
If b >= 1 Then
oRng.Collapse Direction:=wdCollapseStart
Set oTbl = oRng.Tables.Add(oRng, 1, 2, AutoFitBehavior:=wdAutoFitContent)
For Each iShp In oRng.InlineShapes
iShp.Select
Selection.Cut
oTbl.Cell(1, 2).Range.Paste
Next iShp
End If
Next i
End Sub
Thanks
Try the revised code:
Sub Demo()
Application.ScreenUpdating = False
Dim Sctn As Section, Rng As Range, Tbl As Table, s As Long, w As Single
For Each Sctn In ActiveDocument.Sections
Set Rng = Sctn.Range: w = 0
Rng.End = Rng.End - 1
Set Tbl = Rng.ConvertToTable(, NumRows:=1, NumColumns:=1, InitialColumnWidth:=50, AutoFit:=True)
With Tbl
.Columns.Add
For s = .Range.InlineShapes.Count To 1 Step -1
With .Range.InlineShapes(s)
If .Width > w Then w = .Width
.Range.Rows(1).Cells(2).Range.FormattedText = .Range.FormattedText
.Delete
End With
Next
.Columns(1).Cells.Merge
.Columns(2).Cells.Merge
.PreferredWidthType = wdPreferredWidthPercent
.PreferredWidth = 100
If w > 0 Then .Columns(2).Width = w + .LeftPadding + .RightPadding
.Rows.HeightRule = wdRowHeightAuto
End With
Next
Application.ScreenUpdating = True
End Sub
Assuming the text precedes the inlineshapes:
Sub Demo()
Application.ScreenUpdating = False
Dim iShp As InlineShape
For Each iShp In ActiveDocument.InlineShapes
With iShp.Range
.Characters.First.Previous = vbTab
.Start = .Paragraphs.First.Range.Start
.ConvertToTable vbTab, 1, 2
End With
Next
Application.ScreenUpdating = True
End Sub
The above code assumes there is a single character between the text & inlineshape. That character could be a space, paragraph break, line break, anything at all.

Iterate And add ContentControls in Word VBA Macro

I have a hundreds of word documents that have multiple tables. Each table row has a specific custom style that was applied that identifies the data that goes in the cell. The need is to iterate through the word document, find the style, and add a ContentControl on that item. The issue that I have is the Selection.Find command restarts at the beginning of the document, so it ends up nesting ContentControls. I have tried adding in some counting mechanism, but while it fixes most of the issues, it leaves off at least some of the ContentControls and does have a few nests. I have tried only searching on a specific table but the Selection.Find overrides the selected table. Is there a way to iterate from the beginning of the document to the end so that I can dynamically add the content controls? Each document has 2 different types of tables. There will be only 1 of the following tables:
There can be 1 to 100 of this table:
The contentControl is supposed to encapsulate the data in the Document Level Metadata column. Here is the code I have up to this point
Option Explicit
Sub FindStyleReplaceWithCC()
Dim CCtrl As ContentControl
Do While ActiveDocument.ContentControls.Count > 0
For Each CCtrl In ActiveDocument.ContentControls
If CCtrl.LockContentControl = True Then CCtrl.LockContentControl = False
CCtrl.Delete False
Next
Loop
'For Each CCtrl In ActiveDocument.ContentControls
'For Each CCtrl In ActiveDocument.ContentControls
' MsgBox (CCtrl.Range)
'Next
'Dim CCtrl As ContentControl
Dim sty As Style
Dim oTbl As Table
''''''''''''''''''''''''''''''''''''''''
'Table 1
Dim thearray(1 To 13, 1 To 2)
Dim element As Variant
Dim arrWsNames() As Variant
Dim I As Integer
arrWsNames = Array("Sensitive Information Protection", "Applies To", "Functional Org", "Functional Process Owner", _
"Topic Owner", "Subject Matter Experts", "Author", "Corporate Source ID", "Superior Source", "CIPS Legacy Document", _
"Meta-Roles(DocLvl)", "SME Reviewer", "SourceDocs")
For I = 1 To 13
thearray(I, 1) = arrWsNames(I - 1)
thearray(I, 2) = 0
Next
Dim howmany As Integer
howmany = 0
For Each element In arrWsNames
Dim iterations As Integer
With Selection.Find
.ClearFormatting
.Style = ActiveDocument.Styles(element)
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = False
.Wrap = wdFindContinue
End With
Selection.Find.Execute
Selection.Range.ContentControls.Add (wdContentControlRichText)
Selection.ParentContentControl.Title = element
Next
'''''''''''''''''''''''''''''''''''''
'Table 2
Dim thearray2(1 To 8, 1 To 2)
Dim arrWsNames2() As Variant
arrWsNames2 = Array("Meta-ReqType", "Meta-Roles", "Meta-Input", "Meta-Output", "Meta-Toolset", _
"Meta-Sources", "Meta-Traced", "Meta-Objective_Evidence")
For I = 1 To 8
thearray2(I, 1) = arrWsNames2(I - 1)
thearray2(I, 2) = 0
Next
howmany = 0
For Each element In arrWsNames2
iterations = 1
For Each oTbl In ActiveDocument.Tables
oTbl.Select
With Selection.Find
.ClearFormatting
.Style = ActiveDocument.Styles(element)
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = False
.Wrap = wdFindContinue
End With
Selection.Find.Execute
If howmany + 1 = iterations Then
Selection.Range.ContentControls.Add (wdContentControlRichText)
Selection.ParentContentControl.Title = element
howmany = howmany + 1
iterations = iterations - 1
Else
iterations = iterations + 1
End If
Next
Next
MsgBox ("Done")
End Sub
If this can't be done in VBA, can it be done in .net?
This can definitely be done in VBA.
The first thing you need to do is to stop using the Selection object. Although there are occasions when Selection has to be used most things can be accomplished by using Range instead.
The next thing I recommend is breaking your code down into separate routines that only perform one element of the solution. This will not only enable you to simplify your code it will result in reusable routines.
I have edited your code as below and tested it in O365 on a document with a subset or your styles.
Sub AddContentControlsForMetadata()
RemoveContentControls ActiveDocument
Dim element As Variant
Dim arrWsNames() As Variant
arrWsNames = Array("Sensitive Information Protection", "Applies To", "Functional Org", "Functional Process Owner", _
"Topic Owner", "Subject Matter Experts", "Author", "Corporate Source ID", "Superior Source", "CIPS Legacy Document", _
"Meta-Roles(DocLvl)", "SME Reviewer", "SourceDocs", "Meta-ReqType", "Meta-Roles", "Meta-Input", "Meta-Output", "Meta-Toolset", _
"Meta-Sources", "Meta-Traced", "Meta-Objective_Evidence")
For Each element In arrWsNames
FindStyleReplaceWithCC ActiveDocument, CStr(element)
Next element
End Sub
Sub RemoveContentControls(docTarget As Document)
Dim ccIndex As Long
For ccIndex = docTarget.ContentControls.Count To 1 Step -1
With docTarget.ContentControls(ccIndex)
If .LockContentControl = True Then .LockContentControl = False
.Delete False
End With
Next ccIndex
End Sub
Sub FindStyleReplaceWithCC(searchDoc As Document, styleName As String)
Dim findRange As Range
Dim ccRange As Range
Set findRange = searchDoc.Range
With findRange.Find
.ClearFormatting
.Style = ActiveDocument.Styles(styleName)
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
Do While .Execute = True
If findRange.Information(wdWithInTable) Then
findRange.Expand wdCell
End If
Set ccRange = findRange.Duplicate
AddContentControlToRange ccRange, styleName
'need to collapse the findRange so that Find can continue without finding the same location again
findRange.Collapse wdCollapseEnd
Loop
End With
End Sub
Sub AddContentControlToRange(ByVal ccLocation As Range, ByVal ccTitle As String)
ccLocation.ContentControls.Add(wdContentControlRichText).Title = ccTitle
End Sub
EDIT:
To add both a tag and a title to the content control:
Sub AddContentControlToRange(ByVal ccLocation As Range, ByVal ccTitle As String, ByVal ccTag as String)
With ccLocation.ContentControls.Add(wdContentControlRichText)
.Title = ccTitle
.Tag = ccTag
End With
End Sub

How to get all text between <strong> </strong> in MS word to turn Bold using VBA?

Basically I want to transform the text in between the tags into bold. This text will always be in the comments. The current code doesnt do anything.
I am not really sure if this code makes any sense at all, but I usually use VBA for Excel and word seems to be a bit trickier.
Sub Bold()
Dim eCom As Comment
Dim iFound As Integer
Dim rbold As Range
Dim iDot As Integer
Dim flag As Boolean
Dim aDoc As Document
Set aDoc = ActiveDocument
flag = True
Application.ScreenUpdating = False
For Each eCom In ActiveDocument.Comments
iFound = InStr(eCom.Range.Text, "<strong>")
iDot = 0
If iFound > 0 Then
iDot = InStrRev(eCom.Range, "</") - iFound + 1
Set rbold = aDoc.Range(Start:=eCom.Range.Start + iFound, End:=eCom.Range.Start + InStrRev(eCom.Range, "<"))
rbold.Select
Selection.Font.Bold = wdToggle
End If
Next eCom
Application.ScreenUpdating = True
End Sub
There are a few problems here. First, it appears that the Comment Ranges do not use the same numbering as the document ranges. So
Set rbold = aDoc.Range(Start:=eCom.Range.Start + iFound, End:=eCom.Range.Start + InStrRev(eCom.Range, "<"))
is not actually the range in the comments, it is instead a range in the document starting with the place in the comment that has the strong html tag.
Second, even if this was working, it would start the bolding in the wrong place, starting with "strong>"
Third, there's no reason to select the range, just set it to bold.
This code will do what you want (I commented out a line as I couldn't figure out what it was supposed to do):
Sub Bold()
Dim eCom As Comment
Dim iFound As Integer
Dim rbold As Range
Dim iDot As Integer
Dim flag As Boolean
Dim aDoc As Document
Dim newCom As Comment
Set aDoc = ActiveDocument
flag = True
Application.ScreenUpdating = False
For Each eCom In ActiveDocument.Comments
iFound = InStr(eCom.Range.Text, "<strong>")
iDot = 0
If iFound > 0 Then
'iDot = InStrRev(eCom.Range, "</") - iFound + 1
Set rbold = eCom.Range
rbold.MoveEnd Unit:=wdCharacter, Count:=-(Len(rbold) - InStrRev(rbold, "</") + 1)
rbold.MoveStart Unit:=wdCharacter, Count:=iFound + Len("<strong>") - 1
rbold.Bold = True
End If
Next eCom
Application.ScreenUpdating = True
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

Find all Heading 1 Text and Put it into an Array

I am using a VBA Macro to render all the "Heading 1" style text from a word document.
It is working fine but taking huge time depends on the content of word doc.
I am looping each paragraph to check for "Heading 1" style and render the Text into an array.
I wonder if there is an alternative approach to simply find "Heading 1" style and store the text in array which would greatly reduce the execution time.
Below my Macro program and I would appreciate any expert thoughts regarding the above mentioned.
Sub ImportWordHeadings()
Dim wdDoc As Object
Dim wdFileName As Variant
Dim sHeader(50) As String
Dim Head1counter As Integer
Dim arrcount As Long
Dim mHeading As String
On Error Resume Next
wdFileName = Application.GetOpenFilename("Word files (*.doc),*.doc", , _
"Browse for file containing table to be imported")
If wdFileName = False Then Exit Sub '(user cancelled import file browser)
Set wdDoc = GetObject(wdFileName) 'open Word file
p = 1
RetCount = 0
parg = wdDoc.Paragraphs.Count
For Head1counter = 1 To parg
If wdDoc.Paragraphs(Head1counter).Range.Style = "Heading 1" Then
sHeader(p) = wdDoc.Paragraphs(Head1counter).Range.Text
p = p + 1
Else
p = p
End If
Next Head1counter
For arrcount = RetCount + 1 To UBound(sHeader)
If sHeader(arrcount) <> "" Then
Debug.Print sHeader(arrcount)
RetCount = arrcount
Exit For
Else
RetCount = RetCount
End If
Next arrcount
Set wdDoc = Nothing
End Sub
You can use the Find method to search for all of the headings, very similar to what I did over here on Code Review.
Set doc = ActiveDocument
Set currentRange = doc.Range 'start with the whole doc as the current range
With currentRange.Find
.Forward = True 'move forward only
.Style = wdStyleHeading1 'the type of style to find
.Execute 'update currentRange to the first found instance
dim p as long
p = 0
Do While .Found
sHeader(p) = currentRange.Text
' update currentRange to next found instance
.Execute
p = p + 1
Loop
End With