0
votes

I'm writing a VBA script to generate word documents from an already defined template. In it, I need to be able to write headings along with a body for each heading. As a small example, I have a word document that contains only <PLACEHOLDER>. For each heading and body I need to write, I use the find-and-replace feature in VBA to find <PLACEHOLDER> and replace it with the heading name, a newline, and then <PLACEHOLDER> again. This is repeated until each heading name and body is written and then the final <PLACEHOLDER> is replaced with a newline.

The text replacing works fine, but the style I specify gets overwritten by the next call to the replacement. This results in everything I just replaced having the style of whatever my last call to my replacement function is.

VBA code (run main)

Option Explicit

Sub replace_stuff(search_string As String, replace_string As String, style As Integer)
    With ActiveDocument.Range.Find
        .Text = search_string
        .Replacement.Text = replace_string
        .Replacement.style = style
        .Forward = True
        .Wrap = wdFindContinue
        .Format = True
        .MatchWholeWord = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
        .Execute Replace:=wdReplaceAll
    End With
End Sub

Sub main()
    Dim section_names(2) As String
    section_names(0) = "Introduction"
    section_names(1) = "Background"
    section_names(2) = "Conclusion"

    Dim section_bodies(2) As String
    section_bodies(0) = "This is the body text for the introduction! Fetched from some file."
    section_bodies(1) = "And Background... I have no issue fetching data from the files."
    section_bodies(2) = "And for the conclusion... But I want the styles to 'stick'!"

    Dim i As Integer
    For i = 0 To 2
        ' Writes each section name as wsStyleHeading2, and then the section body as wdStyleNormal
        Call replace_stuff("<PLACEHOLDER>", section_names(i) & Chr(11) & "<PLACEHOLDER>", wdStyleHeading2)
        Call replace_stuff("<PLACEHOLDER>", section_bodies(i) & Chr(11) & "<PLACEHOLDER>", wdStyleNormal)
    Next i
    Call replace_stuff("<PLACEHOLDER>", Chr(11), wdStyleNormal)

End Sub

Input document: A word document with only <PLACEHOLDER> in it.

<PLACEHOLDER>

Expected Output: I expect that each heading will be displayed in the style I specified and can be viewed from the navigation pane like this:

expected output

Actual Output: However what I actually get is everything as wdStyleNormal style like this:

actual

I think the problem can be solved by inserting a paragraph break between every style transition, but when I try using vbCrLF or Chr(10) & Chr(13) or vbNewLine instead of the chr(11) I am using now, Each line begins with a boxed question mark like this:

enter image description here

2
Why are you adding "<PLACEHOLER>" to the text that replaces? The document doesn't contain these? There would certainly be a better way to code this if that's the case...Cindy Meister
@CindyMeister, I add <PLACEHOLDER> after every replace so the next find-and-replace will be able to "find" it and know where to insert the text. It's replaced each time, and the final one is replaced with a new line (chr(11)) which is why no <PLACEHOLDER>s appear in the final document. This is the approach I came up with, but I'm not very familiar with vba so there probably is a better waySyntaxVoid supports Monica

2 Answers

1
votes

Update from discussion in comments on another answer. The problem described below applies to Word 2016 and earlier. Starting in Office 365 (and probably Word 2019, but that's not been confirmed) the Replace behavior has been changed to "convert" ANSI 13 to a "real" paragraph mark, so the problem in the question would not occur.

Answer

The reason for the odd formatting behavior is the use of Chr(11), which inserts a new line (Shift + Enter) instead of a new paragraph. So a paragraph style applied to any part of this text formats the entire text with the same style.

In this particular case (working with Replace), vbCr or the equivalent Chr(13) also don't work because these are not really Word's native paragraph. A paragraph is much more than just ANSI code 13 - it contains paragraph formatting information. So, while the code is running, Word is not really recognizing these as true paragraph marks and the paragraph style assignment is being applied to "everything".

What does work is to use the string ^p, which in Word's Find/Replace is the "alias" for a complete paragraph mark. So, for example:

replace_stuff "<PLACEHOLDER>", section_names(i) & "^p" & "<PLACEHOLDER>", wdStyleHeading2
replace_stuff "<PLACEHOLDER>", section_bodies(i) & "^p" & "<PLACEHOLDER>", wdStyleNormal

There is, however, a more efficient way to build a document than inserting a placeholder for each new item and using Find/Replace to replace the placeholder with the document content. The more conventional approach is to work with a Range object (think of it like an invisible selection)...

Assign content to the Range, format it, collapse (like pressing right-arrow for a selection) and repeat. Here's an example that returns the same result as the (corrected) code in the question:

Sub main()
  Dim rng As Range

  Set rng = ActiveDocument.content
  Dim section_names(2) As String
  section_names(0) = "Introduction"
  section_names(1) = "Background"
  section_names(2) = "Conclusion"

  Dim section_bodies(2) As String
  section_bodies(0) = "This is the body text for the introduction! Fetched from some file."
  section_bodies(1) = "And Background... I have no issue fetching data from the files."
  section_bodies(2) = "And for the conclusion... But I want the styles to 'stick'!"

  Dim i As Integer
  For i = 0 To 2
    BuildParagraph section_names(i), wdStyleHeading2, rng
    BuildParagraph section_bodies(i), wdStyleNormal, rng
  Next i
End Sub

Sub BuildParagraph(para_text As String, para_style As Long, rng As Range)
        rng.Text = para_text
        rng.style = para_style
        rng.InsertParagraphAfter
        rng.Collapse wdCollapseEnd
End Sub
0
votes

The problem is caused by your use of Chr(11) which is a manual line break. This results in all of the text being in a single paragraph. When the paragraph style is applied it applies to the entire paragraph.

Replace Chr(11) with vbCr to ensure that each piece of text is in a separate paragraph.