从一系列文档模板生成 Word 文档(在 Excel VBA 中)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5106743/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 11:10:06  来源:igfitidea点击:

Generate Word Documents (in Excel VBA) from a series of Document Templates

excel-vbams-worddocumentation-generationvbaexcel

提问by Alain

Hey all. I'll try to make this brief and simple. :)

大家好。我会尽量使这个简短而简单。:)

I have

我有

  1. 40 or so boilerplate word documents with a series of fields (Name, address, etc) that need to be filled in. This is historically done manually, but it's repetitive and cumbersome.
  2. A workbook where a user has filled a huge set of information about an individual.
  1. 40 个左右的样板 Word 文档,其中包含需要填写的一系列字段(姓名、地址等)。这在历史上是手动完成的,但重复且繁琐。
  2. 用户在其中填写了大量有关个人的信息的工作簿。

I need

我需要

  • A way to programatically (from Excel VBA) open up these boilerplate documents, edit in the value of fields from various named ranges in the workbook, and save the filled in templates to a local folder.
  • 一种以编程方式(从 Excel VBA)打开这些样板文档的方法,编辑工作簿中各种命名范围的字段值,并将填充的模板保存到本地文件夹。

If I were using VBA to programatically edit particular values in a set of spreadsheets, I would edit all those spreadsheets to contain a set of named ranges which could be used during the auto-fill process, but I'm not aware of any 'named field' feature in a Word document.

如果我使用 VBA 以编程方式编辑一组电子表格中的特定值,我将编辑所有这些电子表格以包含一组可在自动填充过程中使用的命名范围,但我不知道任何“命名” Word 文档中的字段”功能。

How could I edit the documents, and create a VBA routine, so that I can open each document, look for a set of fields which might need to be filled in, and substitute a value?

我如何编辑文档,并创建一个 VBA 例程,以便我可以打开每个文档,查找可能需要填写的一组字段,并替换一个值?

For instance, something that works like:

例如,一些工作如下:

for each document in set_of_templates
    if document.FieldExists("Name") then document.Field("Name").value = strName
    if document.FieldExists("Address") then document.Field("Name").value = strAddress
    ...

    document.saveAs( thisWorkbook.Path & "\GeneratedDocs\ " & document.Name )
next document

Things I've considered:

我考虑过的事情:

  • Mail merge - but this is insufficient because it requires opening each document manually and structuring the workbook as a data source, I kind of want the opposite. The templates are the data source and the workbook is iterating through them. Also, mail merge is for creating many identical documents using a table of different data. I have many documents all using the same data.
  • Using placeholder text such as "#NAME#" and opening each document for a search and replace. This is the solution I would resort to if nothing more elegant is proposed.
  • 邮件合并 - 但这还不够,因为它需要手动打开每个文档并将工作簿构建为数据源,我有点想要相反的。模板是数据源,工作簿正在遍历它们。此外,邮件合并用于使用不同数据的表创建许多相同的文档。我有很多文档都使用相同的数据。
  • 使用占位符文本(例如“#NAME#”)并打开每个文档进行搜索和替换。如果没有更优雅的提议,这就是我会采用的解决方案。

回答by Alain

It's been a long time since I asked this question, and my solution has undergone more and more refinement. I've had to deal with all sorts of special cases, such as values that come directly from the workbook, sections that need to be specially generated based on lists, and the need to do replacements in headers and footers.

很久没问这个问题了,我的解决方案也越来越细化了。我不得不处理各种特殊情况,例如直接来自工作簿的值、需要根据列表专门生成的部分以及需要在页眉和页脚中进行替换。

As it turns out, it did not suffice to use bookmarks, as it was possible for users to later edit documents to change, add, and remove placeholder values from the documents. The solution was in fact to use keywordssuch as this:

事实证明,仅使用书签是不够的,因为用户可以稍后编辑文档以更改、添加和删除文档中的占位符值。解决方案实际上是使用这样的关键字

enter image description here

在此处输入图片说明

This is just a page from a sample document which uses some of the possible values that can get automatically inserted into a document. Over 50 documents exist with completely different structures and layouts, and using different parameters. The only common knowledge shared by the word documents and the excel spreadsheet is a knowledge of what these placeholder values are meant to represent. In excel, this is stored in a list of document generation keywords, which contain the keyword, followed by a reference to the range that actually contains this value:

这只是示例文档中的一个页面,它使用了一些可以自动插入到文档中的可能值。存在 50 多个文件,它们具有完全不同的结构和布局,并使用不同的参数。word文档和excel电子表格共享的唯一共同知识是这些占位符值的含义的知识。在 excel 中,它存储在文档生成关键字列表中,其中包含关键字,后跟对实际包含此值的范围的引用:

enter image description here

在此处输入图片说明

These were the key two ingredients required. Now with some clever code, all I had to do was iterate over each document to be generated, and then iterate over the range of all known keywords, and do a search and replace for each keyword in each document.

这是所需的两个关键成分。现在有了一些巧妙的代码,我所要做的就是遍历要生成的每个文档,然后遍历所有已知关键字的范围,并对每个文档中的每个关键字进行搜索和替换。



First, I have the wrapper method, which takes care of maintaining an instance of microsoft word iterating over all documents selected for generation, numbering the documents, and doing the user interface stuff (like handling errors, displaying the folder to the user, etc.)

首先,我有包装器方法,它负责维护一个 microsoft word 的实例,它迭代所有选择生成的文档,为文档编号,并执行用户界面操作(如处理错误、向用户显示文件夹等)。 )

' Purpose: Iterates over and generates all documents in the list of forms to generate
'          Improves speed by creating a persistant Word application used for all generated documents
Public Sub GeneratePolicy()
    Dim oWrd As New Word.Application
    Dim srcPath As String
    Dim cel As Range

    If ERROR_HANDLING Then On Error GoTo errmsg
    If Forms.Cells(2, FormsToGenerateCol) = vbNullString Then _
        Err.Raise 1, , "There are no forms selected for document generation."
    'Get the path of the document repository where the forms will be found.
    srcPath = FindConstant("Document Repository")
    'Each form generated will be numbered sequentially by calling a static counter function. This resets it.
    GetNextEndorsementNumber reset:=True
    'Iterate over each form, calling a function to replace the keywords and save a copy to the output folder
    For Each cel In Forms.Range(Forms.Cells(2, FormsToGenerateCol), Forms.Cells(1, FormsToGenerateCol).End(xlDown))
        RunReplacements cel.value, CreateDocGenPath(cel.Offset(0, 1).value), oWrd
    Next cel
    oWrd.Quit
    On Error Resume Next
    'Display the folder containing the generated documents
    Call Shell("explorer.exe " & CreateDocGenPath, vbNormalFocus)
    oWrd.Quit False
    Application.StatusBar = False
    If MsgBox("Policy generation complete. The reserving information will now be recorded.", vbOKCancel, _
              "Policy Generated. OK to store reserving info?") = vbOK Then Push_Reserving_Requirements
    Exit Sub
errmsg:
    MsgBox Err.Description, , "Error generating Policy Documents"
End Sub

That routine calls RunReplacementswhich takes care of opening the document, prepping the environment for a fast replacement, updating links once done, handling errors, etc:

该例程调用RunReplacements负责打开文档、为快速替换准备环境、完成后更新链接、处理错误等:

' Purpose: Opens up a document and replaces all instances of special keywords with their respective values.
'          Creates an instance of Word if an existing one is not passed as a parameter.
'          Saves a document to the target path once the template has been filled in.
'
'          Replacements are done using two helper functions, one for doing simple keyword replacements,
'          and one for the more complex replacements like conditional statements and schedules.
Private Sub RunReplacements(ByVal DocumentPath As String, ByVal SaveAsPath As String, _
                            Optional ByRef oWrd As Word.Application = Nothing)
    Dim oDoc As Word.Document
    Dim oWrdGiven As Boolean
    If oWrd Is Nothing Then Set oWrd = New Word.Application Else oWrdGiven = True

    If ERROR_HANDLING Then On Error GoTo docGenError
    oWrd.Visible = False
    oWrd.DisplayAlerts = wdAlertsNone

    Application.StatusBar = "Opening " & Mid(DocumentPath, InStrRev(DocumentPath, "\") + 1)
    Set oDoc = oWrd.Documents.Open(Filename:=DocumentPath, Visible:=False)
    RunAdvancedReplacements oDoc
    RunSimpleReplacements oDoc
    UpdateLinks oDoc 'Routine which will update calculated statements in Word (like current date)
    Application.StatusBar = "Saving " & Mid(DocumentPath, InStrRev(DocumentPath, "\") + 1)
    oDoc.SaveAs SaveAsPath

    GoTo Finally
docGenError:
    MsgBox "Un unknown error occurred while generating document: " & DocumentPath & vbNewLine _
            & vbNewLine & Err.Description, vbCritical, "Document Generation"
Finally:
    If Not oDoc Is Nothing Then oDoc.Close False: Set oDoc = Nothing
    If Not oWrdGiven Then oWrd.Quit False
End Sub

That routine then invokes RunSimpleReplacements. and RunAdvancedReplacements. In the former, we iterate over the set of Document Generation Keywords and call WordDocReplaceif the document contains our keyword. Note that it's much faster to try and Finda bunch of words to figure out that they don't exist, then to call replace indiscriminately, so we always check if a keyword exists before attempting to replace it.

然后该例程调用RunSimpleReplacements. 和RunAdvancedReplacements。在前者中,我们遍历文档生成关键字集并调用WordDocReplace文档是否包含我们的关键字。请注意,尝试和Find一堆单词找出它们不存在的速度要快得多,然后不加选择地调用替换,因此我们总是在尝试替换之前检查关键字是否存在。

' Purpose: While short, this short module does most of the work with the help of the generation keywords
'          range on the lists sheet. It loops through every simple keyword that might appear in a document
'          and calls a function to have it replaced with the corresponding data from pricing.
Private Sub RunSimpleReplacements(ByRef oDoc As Word.Document)
    Dim DocGenKeys As Range, valueSrc As Range
    Dim value As String
    Dim i As Integer

    Set DocGenKeys = Lists.Range("DocumentGenerationKeywords")
    For i = 1 To DocGenKeys.Rows.Count
        If WordDocContains(oDoc, "#" & DocGenKeys.Cells(i, 1).Text & "#") Then
            'Find the text that we will be replacing the placeholder keyword with
            Set valueSrc = Range(Mid(DocGenKeys.Cells(i, 2).Formula, 2))
            If valueSrc.MergeCells Then value = valueSrc.MergeArea.Cells(1, 1).Text Else value = valueSrc.Text
            'Perform the replacement
            WordDocReplace oDoc, "#" & DocGenKeys.Cells(i, 1).Text & "#", value
        End If
    Next i
End Sub

This is the function used to detect whether a keyword exists in the document:

这是用于检测文档中是否存在关键字的函数:

' Purpose: Function called for each replacement to first determine as quickly as possible whether
'          the document contains the keyword, and thus whether replacement actions must be taken.
Public Function WordDocContains(ByRef oDoc As Word.Document, ByVal searchFor As String) As Boolean
    Application.StatusBar = "Checking for keyword: " & searchFor
    WordDocContains = False
    Dim storyRange As Word.Range
    For Each storyRange In oDoc.StoryRanges
        With storyRange.Find
            .Text = searchFor
            WordDocContains = WordDocContains Or .Execute
        End With
        If WordDocContains Then Exit For
    Next
End Function

And this is where the rubber meets the road - the code that executes the replacement. This routine got more complicated as I encountered difficulties. Here are the lessons you will only learn from experience:

这就是橡胶遇到道路的地方 - 执行替换的代码。当我遇到困难时,这个例程变得更加复杂。以下是您只能从经验中学到的教训:

  1. You can set the replacement text directly, or you can use the clipboard. I found out the hard way that if you are doing a VBA replace in word using a string longer than 255 characters, the text will get truncated if you try to place it in the Find.Replacement.Text, but you can use "^c"as your replacement text, and it will get it directly from the clipboard. This was the workaround I got to use.

  2. Simply calling replace will miss keywords in some text areas like headers and footers. Because of this, you actually need to iterate over the document.StoryRangesand run the search and replace on each one to ensure that you catch all instances of the word you want to replace.

  3. If you're setting the Replacement.Textdirectly, you need to convert Excel line breaks (vbNewLineand Chr(10)) with a simple vbCrfor them to appear properly in word. Otherwise, anywhere your replacement text has line breaks coming from an excel cell will end up inserting strange symbols into word. If you use the clipboard method however, you do not need to do this, as the line breaks get converted automatically when put in the clipboard.

  1. 可以直接设置替换文本,也可以使用剪贴板。我发现了艰辛的道路,如果你正在做一个VBA使用字符串长度超过255个字符的字代替,文本会得到,如果你尝试把它截断Find.Replacement.Text,但你可以使用"^c"作为替换文本,它会直接从剪贴板中获取。这是我必须使用的解决方法。

  2. 简单地调用 replace 会丢失某些文本区域(如页眉和页脚)中的关键字。因此,您实际上需要迭代document.StoryRanges并运行搜索和替换,以确保捕获要替换的单词的所有实例。

  3. 如果您Replacement.Text直接设置,则需要使用简单的方式转换 Excel 换行符 (vbNewLineChr(10)) 以vbCr使其在 Word 中正确显示。否则,您的替换文本在任何地方都有来自 Excel 单元格的换行符,最终会在 word 中插入奇怪的符号。但是,如果您使用剪贴板方法,则无需执行此操作,因为将换行符放入剪贴板时会自动转换。

That explains everything. Comments should be pretty clear too. Here's the golden routine that executes the magic:

这说明了一切。评论也应该很清楚。这是执行魔法的黄金例程:

' Purpose: This function actually performs replacements using the Microsoft Word API
Public Sub WordDocReplace(ByRef oDoc As Word.Document, ByVal replaceMe As String, ByVal replaceWith As String)
    Dim clipBoard As New MSForms.DataObject
    Dim storyRange As Word.Range
    Dim tooLong As Boolean

    Application.StatusBar = "Replacing instances of keyword: " & replaceMe

    'We want to use regular search and replace if we can. It's faster and preserves the formatting that
    'the keyword being replaced held (like bold).  If the string is longer than 255 chars though, the
    'standard replace method doesn't work, and so we must use the clipboard method (^c special character),
    'which does not preserve formatting. This is alright for schedules though, which are always plain text.
    If Len(replaceWith) > 255 Then tooLong = True
    If tooLong Then
        clipBoard.SetText IIf(replaceWith = vbNullString, "", replaceWith)
        clipBoard.PutInClipboard
    Else
        'Convert excel in-cell line breaks to word line breaks. (Not necessary if using clipboard)
        replaceWith = Replace(replaceWith, vbNewLine, vbCr)
        replaceWith = Replace(replaceWith, Chr(10), vbCr)
    End If
    'Replacement must be done on multiple 'StoryRanges'. Unfortunately, simply calling replace will miss
    'keywords in some text areas like headers and footers.
    For Each storyRange In oDoc.StoryRanges
        Do
            With storyRange.Find
                .MatchWildcards = True
                .Text = replaceMe
                .Replacement.Text = IIf(tooLong, "^c", replaceWith)
                .Wrap = wdFindContinue
                .Execute Replace:=wdReplaceAll
            End With
            On Error Resume Next
            Set storyRange = storyRange.NextStoryRange
            On Error GoTo 0
        Loop While Not storyRange Is Nothing
    Next
    If tooLong Then clipBoard.SetText ""
    If tooLong Then clipBoard.PutInClipboard
End Sub

When the dust settles, we're left with a beautiful version of the initial document with production values in place of those hash marked keywords. I'd love to show an example, but of course every filled in document contain all-proprietary information.

当一切尘埃落定后,我们就会得到一个漂亮的初始文档版本,其中包含生产值而不是那些带有哈希标记的关键字。我很想展示一个例子,但当然每个填写的文档都包含所有专有信息。



The only think left to mention I guess would be that RunAdvancedReplacementssection. It does something extremely similar - it ends up calling the same WordDocReplacefunction, but what's special about the keywords used here is that they don't link to a single cell in the original workbook, they get generated in the code-behind from lists in the workbook. So for instance, one of the advanced replacements would look like this:

我想剩下的唯一想法就是那个RunAdvancedReplacements部分。它做了一些非常相似的事情——它最终调用了相同的WordDocReplace函数,但是这里使用的关键字的特别之处在于它们没有链接到原始工作簿中的单个单元格,它们是在代码隐藏中从列表中的列表中生成的工作簿。因此,例如,高级替换之一将如下所示:

'Generate the schedule of vessels
If WordDocContains(oDoc, "#VESSELSCHEDULE#") Then _
    WordDocReplace oDoc, "#VESSELSCHEDULE#", GenerateVesselSchedule()

And then there will be a corresponding routine which puts together a string containing all the vessel information as configured by the user:

然后会有一个相应的例程,将包含用户配置的所有船只信息的字符串放在一起:

' Purpose: Generates the list of vessels from the "Vessels" sheet based on the user's configuration
'          in the booking tab. The user has the option to generate one or both of Owned Vessels
'          and Chartered Vessels, as well as what fields to display. Uses a helper function.
Public Function GenerateVesselSchedule() As String
    Dim value As String

    Application.StatusBar = "Generating Schedule of Vessels."
    If Booking.Range("ListVessels").value = "Yes" Then
        Dim VesselCount As Long

        If Booking.Range("ListVessels").Offset(1).value = "Yes" Then _
            value = value & GenerateVesselScheduleHelper("Vessels", VesselCount)
        If Booking.Range("ListVessels").Offset(1).value = "Yes" And _
           Booking.Range("ListVessels").Offset(2).value = "Yes" Then _
            value = value & "(Chartered Vessels)" & vbNewLine
        If Booking.Range("ListVessels").Offset(2).value = "Yes" Then _
            value = value & GenerateVesselScheduleHelper("CharteredVessels", VesselCount)
        If Len(value) > 2 Then value = Left(value, Len(value) - 2) 'Remove the trailing line break
    Else
        GenerateVesselSchedule = Booking.Range("VesselSchedAlternateText").Text
    End If
    GenerateVesselSchedule = value
End Function

' Purpose: Helper function for the Vessel Schedule generation routine. Generates either the Owned or
'          Chartered vessels based on the schedule parameter passed. The list is numbered and contains
'          the information selected by the user on the Booking sheet.
' SENSITIVE: Note that this routine is sensitive to the layout of the Vessel Schedule tab and the
'            parameters on the Configure Quotes tab. If either changes, it should be revisited.
Public Function GenerateVesselScheduleHelper(ByVal schedule As String, ByRef VesselCount As Long) As String
    Dim value As String, nextline As String
    Dim numInfo As Long, iRow As Long, iCol As Long
    Dim Inclusions() As Boolean, Columns() As Long

    'Gather info about vessel info to display in the schedule
    With Booking.Range("VesselInfoToInclude")
        numInfo = Booking.Range(.Cells(1, 1), .End(xlToRight)).Columns.Count - 1
        ReDim Inclusions(1 To numInfo)
        ReDim Columns(1 To numInfo)
        On Error Resume Next 'Some columns won't be identified
        For iCol = 1 To numInfo
            Inclusions(iCol) = .Offset(0, iCol) = "Yes"
            Columns(iCol) = sumSchedVessels.Range(schedule).Cells(1).EntireRow.Find(.Offset(-1, iCol)).Column
        Next iCol
        On Error GoTo 0
    End With

    'Build the schedule
    With sumSchedVessels.Range(schedule)
        For iRow = .row + 1 To .row + .Rows.Count - 1
            If Len(sumSchedVessels.Cells(iRow, Columns(1)).value) > 0 Then
                VesselCount = VesselCount + 1
                value = value & VesselCount & "." & vbTab
                nextline = vbNullString
                'Add each property that was included to the description string
                If Inclusions(1) Then nextline = nextline & sumSchedVessels.Cells(iRow, Columns(1)) & vbTab
                If Inclusions(2) Then nextline = nextline & "Built: " & sumSchedVessels.Cells(iRow, Columns(2)) & vbTab
                If Inclusions(3) Then nextline = nextline & "Length: " & _
                                      Format(sumSchedVessels.Cells(iRow, Columns(3)), "#'") & vbTab
                If Inclusions(4) Then nextline = nextline & "" & sumSchedVessels.Cells(iRow, Columns(4)) & vbTab
                If Inclusions(5) Then nextline = nextline & "Hull Value: " & _
                                      Format(sumSchedVessels.Cells(iRow, Columns(5)), "$#,##0") & vbTab
                If Inclusions(6) Then nextline = nextline & "IV: " & _
                                      Format(sumSchedVessels.Cells(iRow, Columns(6)), "$#,##0") & vbTab
                If Inclusions(7) Then nextline = nextline & "TIV: " & _
                                      Format(sumSchedVessels.Cells(iRow, Columns(7)), "$#,##0") & vbTab
                If Inclusions(8) And schedule = "CharteredVessels" Then _
                    nextline = nextline & "Deductible: " & Format(bmCharterers.Range(schedule).Cells( _
                               iRow - .row, 9), "$#,##0") & vbTab
                nextline = Left(nextline, Len(nextline) - 1) 'Remove the trailing tab
                'If more than 4 properties were included insert a new line after the 4th one
                Dim tabloc As Long: tabloc = 0
                Dim counter As Long: counter = 0
                Do
                    tabloc = tabloc + 1
                    tabloc = InStr(tabloc, nextline, vbTab)
                    If tabloc > 0 Then counter = counter + 1
                Loop While tabloc > 0 And counter < 4
                If counter = 4 Then nextline = Left(nextline, tabloc - 1) & vbNewLine & Mid(nextline, tabloc)
                value = value & nextline & vbNewLine
            End If
        Next iRow
    End With

    GenerateVesselScheduleHelper = value
End Function

the resulting string can be used just like the contents of any excel cell, and passed to the replacement function, which will appropriately use the clipboard method if it exceeds 255 characters.

结果字符串可以像任何excel单元格的内容一样使用,并传递给替换函数,如果它超过255个字符,它将适当地使用剪贴板方法。

So this template:

所以这个模板:

enter image description here

在此处输入图片说明

Plus this spreadsheet data:

加上这个电子表格数据:

enter image description here

在此处输入图片说明

Becomes this document:

成为这个文件:

enter image description here

在此处输入图片说明



I sincerely hope that this helps someone out some day. It was definitely a huge undertaking and a complex wheel to have to re-invent. The application is huge, with over 50,000 lines of VBA code, so if I've referenced a crucial method in my code somewhere that someone needs, please leave a comment and I'll add it in here.

我真诚地希望有一天这能帮助某人。这绝对是一项艰巨的任务,需要重新发明一个复杂的轮子。该应用程序非常庞大,有超过 50,000 行的 VBA 代码,所以如果我在代码中引用了某人需要的关键方法,请发表评论,我会将其添加到此处。

回答by Alain

http://www.computorcompanion.com/LPMArticle.asp?ID=224Describes the use of Word bookmarks

http://www.computorcompanion.com/LPMArticle.asp?ID=224描述Word书签的使用

A section of text in a document can be bookmarked, and given a variable name. Using VBA, this variable can be accessed and the content in the document can be replaced with alternate content. This is a solution to having placeholders such as Name and Address in the document.

可以为文档中的一段文本添加书签,并为其指定一个变量名称。使用 VBA,可以访问该变量,并且可以用替代内容替换文档中的内容。这是在文档中包含名称和地址等占位符的解决方案。

Furthermore, using bookmarks, documents can be modified to reference bookmarked text. If a name appears several times throughout a document, the first instance can be bookmarked, and additional instances can reference the bookmark. Now when the first instance is programatically changed, all other instances of the variable throughout the document are also automatically changed.

此外,使用书签,可以修改文档以引用书签文本。如果某个名称在整个文档中多次出现,则可以为第一个实例添加书签,并且其他实例可以引用该书签。现在,当以编程方式更改第一个实例时,整个文档中变量的所有其他实例也将自动更改。

Now all that's needed is to update all the documents by bookmarking the placeholder text and using a consistent naming convention throughout the documents, then iterate through each documents replacing the bookmark if it exists:

现在所需要做的就是通过为占位符文本添加书签并在整个文档中使用一致的命名约定来更新所有文档,然后遍历每个文档替换书签(如果存在):

document.Bookmarks("myBookmark").Range.Text = "Inserted Text"

I can probably solve the problem of variables that don't appear in a given document using the on error resume next clause before attempting each replacement.

在尝试每次替换之前,我可能可以使用 on error resume next 子句来解决未出现在给定文档中的变量的问题。

Thanks to Doug Glancyfor mentioning the existance of bookmarks in his comment. I had no knowledge of their existence beforehand. I will keep this topic posted on whether this solution suffices.

感谢Doug Glancy在他的评论中提到书签的存在。我事先不知道他们的存在。我将继续发布关于此解决方案是否足够的主题。

回答by JasonPlutext

You might consider an XML based approach.

您可以考虑使用基于 XML 的方法。

Word has a feature called Custom XML data-binding, or data-bound content controls. A content control is essentially a point in the document which can contain content. A "data-bound" content control gets its content from an XML document you include in the docx zip file. An XPath expression is used to say which bit of XML. So all you need to do is include your XML file, and Word will do the rest.

Word 具有称为自定义 XML 数据绑定或数据绑定内容控件的功能。内容控件本质上是文档中可以包含内容的一个点。“数据绑定”内容控件从包含在 docx zip 文件中的 XML 文档获取其内容。XPath 表达式用于说明 XML 的哪一部分。因此,您需要做的就是包含您的 XML 文件,其余的工作将由 Word 完成。

Excel has ways to get data out of it as XML, so the whole solution should work nicely.

Excel 有办法将数据作为 XML 从中取出,因此整个解决方案应该可以很好地工作。

There is plenty of information on content control data-binding on MSDN (some of which has been referenced in earlier SO questions) so I won't bother including them here.

MSDN 上有大量关于内容控制数据绑定的信息(其中一些已在较早的 SO 问题中被引用),因此我不会在这里包括它们。

But you do need a way of setting up the bindings. You can either use the Content Control Toolkit, or if you want to do it from within Word, my OpenDoPE add-in.

但是您确实需要一种设置绑定的方法。您可以使用内容控制工具包,也可以从 Word 中使用我的 OpenDoPE 插件。

回答by Simon N

Having done a similar task I found that inserting values into tables was much quicker than searching for named tags - the data can then be inserted like this:

完成类似的任务后,我发现将值插入表比搜索命名标签要快得多 - 然后可以像这样插入数据:

    With oDoc.Tables(5)
    For i = 0 To Data.InvoiceDictionary.Count - 1
        If i > 0 Then
            oDoc.Tables(5).rows.Add
        End If
         Set invoice = Data.InvoiceDictionary.Items(i)
        .Cell(i + 2, 1).Range.Text = invoice.InvoiceCCNumber
        .Cell(i + 2, 2).Range.Text = invoice.InvoiceDate
        .Cell(i + 2, 3).Range.Text = invoice.TransactionType
        .Cell(i + 2, 4).Range.Text = invoice.Description
        .Cell(i + 2, 5).Range.Text = invoice.SumOfValue

    Next i

.Cell(i + 1, 4).Range.Text = "Total:" End With in this case row 1 of the table was the headers; row 2 was empty and there were no further rows - thus the rows.add applies once more than one row was attached. The tables can be very detailed documents and by hiding the borders and cell borders can be made to look like ordinary text. Tables are numbered sequentially following the document flow. (i.e. Doc.Tables(1) is the first table...

.Cell(i + 1, 4).Range.Text = "Total:" End With 在这种情况下,表格的第 1 行是标题;第 2 行是空的,没有更多的行 - 因此,rows.add 一旦附加了多行就适用。表格可以是非常详细的文档,通过隐藏边框和单元格边框可以使之看起来像普通文本。表格按照文档流程顺序编号。(即 Doc.Tables(1) 是第一个表...