title: Regex Microsoft Word without destroying document formatting
body: <p>It's well known that word's find and replace "wildcards" features suffer some severe limitations.</p>
  
  <p>The following code implements true regex find and replace in a word document, and variations on it are found in other Stackoverflow and SuperUser questions.</p>
  
  <pre><code>Sub RegEx_PlainText(Before As String, After As String)
  
      Dim regexp As Object
      Set regexp = CreateObject("vbscript.regexp")            
  
      With regexp
          .Pattern = Before
          .IgnoreCase = True
          .Global = True
  
           'could be any Range , .Range.Text , or selection object
           ActiveDocument.Range = .Replace(ActiveDocument.Range, After)
  
      End With
  End Sub
  </code></pre>
  
  <p>However, this wipes the document of all formatting.</p>
  
  <p>Word will not preserve formatting character by character even if the strings are of the same length or indeed the same string, so <code>ActiveDocument.Range = ActiveDocument.Range</code> or <code>Selection.Text=Selection.Text</code> will wipe all formatting (or more accurately, format the whole range the same as the first character in the range, and add a carriage return).  Upon reflection, this behavior isn't so surprising.</p>
  
  <p><strong>To solve this, the following code runs a regex find, then loops through the matches and runs <code>.replace</code> only on the range where the match is found.</strong>  This then, would only lose formatting if the match iself had a variety of formatting (for example an italicised word would be lost)</p>
  
  <p>Hopefully the code comments make this quite transparent.</p>
  
  <pre><code>Sub RegEx(Before As String, After As String, _
            Optional CaseSensitive As Boolean = False, _
            Optional Location As Range = Nothing, _
            Optional DebugMode As Boolean = False)
  
      'can't declare activedocument.range in parameters
      If Location Is Nothing Then Set Location = ActiveDocument.Range
  
      Dim regexp As Object
      Dim Foundmatches As Object
      Dim Match As Object
      Dim MatchRange As Range
      Dim offset As Integer: offset = 0
      Set regexp = CreateObject("vbscript.regexp")
  
     With regexp
          .Pattern = Before
          .IgnoreCase = Not CaseSensitive
          .Global = True
  
          'set foundmatches to collection of all regex matches
          Set Foundmatches = .Execute(Location.text)
  
          For Each Match In Foundmatches
  
              'set matchrange to location of found string in source doc.
              'offset accounts for change in length of  document from already completed replacements
              Set MatchRange = Location.Document _
                     .Range(Match.FirstIndex + offset, _
                            Match.FirstIndex + Match.Length + offset)
  
              'debugging
              If DebugMode Then
                      Debug.Print "strfound      = " &amp; Match.Value
                      Debug.Print "matchpoint    = " &amp; Match.FirstIndex
                      Debug.Print "origstrlength = " &amp; Match.Length
                      Debug.Print "offset        = " &amp; offset
                      Debug.Print "matchrange    = " &amp; MatchRange.text
                      MatchRange.Select
                  Stop
  
              Else
              'REAL LIFE
                  'run the regex replace just on the range containing the regex match
                  MatchRange = .Replace(MatchRange, After)
  
                  'increment offset to account for change in length of document
                  offset = offset + MatchRange.End - MatchRange.Start - Match.Length
              End If
          Next
      End With
  End Sub
  </code></pre>
  
  <p><strong>This works on simple documents, but when I run it on a real document, <code>matchrange</code> ends up being at some point near the where the match was found, but not exactly right.</strong>  It's not predictably off, sometimes it is to the right, and sometimes to the left. Generally the more complex the document. (links, tables of context, formatting etc.) the more wrong it ends up being.</p>
  
  <p><strong>Does anyone know why this doesn't work, and how to fix it?</strong>  If I could understand why this isn't working, then I might be able to determine whether this approach can be fixed, or if I just need to try a different method.  </p>
  
  <p><strong>Code includes DebugMode param which means it will just loop through the doc and highlight all matches, performing no changes.</strong>  Also outputs a bunch of stuff to the console.  This should be helpful for anyone kind enough to tackle this problem with me.</p>
  
  <p><a href="https://calibre-ebook.com/downloads/demos/demo.docx" rel="noreferrer">https://calibre-ebook.com/downloads/demos/demo.docx</a> Here is a sample document (not produced by me) which may be useful.</p>