it-swarm.com.de

Generieren Sie Word-Dokumente (in Excel VBA) aus einer Reihe von Dokumentvorlagen

Hallo zusammen. Ich werde versuchen, dies kurz und einfach zu machen. :)

Ich habe

  1. 40 Boilerplate Word-Dokumente mit einer Reihe von Feldern (Name, Adresse usw.), die ausgefüllt werden müssen. Dies wird in der Vergangenheit manuell durchgeführt, ist aber wiederholend und umständlich.
  2. Eine Arbeitsmappe, in der ein Benutzer umfangreiche Informationen über eine Person eingegeben hat.

Ich brauche

  • Eine Möglichkeit, programmgesteuert (von Excel VBA) aus diese Boilerplate-Dokumente zu öffnen, den Wert von Feldern aus verschiedenen benannten Bereichen in der Arbeitsmappe zu bearbeiten und die ausgefüllten Vorlagen in einem lokalen Ordner zu speichern.

Wenn ich mit VBA bestimmte Werte in einer Gruppe von Tabellenkalkulationsprogrammen programmgesteuert bearbeiten würde, würde ich alle diese Tabellenkalkulationen so bearbeiten, dass sie eine Reihe benannter Bereiche enthalten, die während des automatischen Füllvorgangs verwendet werden könnten Feld 'in einem Word-Dokument.

Wie kann ich die Dokumente bearbeiten und eine VBA-Routine erstellen, so dass ich jedes Dokument öffnen, nach einer Reihe von Feldern suchen kann, die möglicherweise ausgefüllt werden müssen, und einen Wert eingeben?

Zum Beispiel etwas, das funktioniert wie:

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

Dinge, an die ich gedacht habe:

  • Seriendruck - dies ist jedoch unzureichend, da jedes Dokument manuell geöffnet und die Arbeitsmappe als Datenquelle strukturiert werden muss. Ich möchte das Gegenteil. Die Vorlagen sind die Datenquelle, und die Arbeitsmappe durchläuft sie. Beim Seriendruck werden viele identische Dokumente anhand einer Tabelle mit verschiedenen Daten erstellt. Ich habe viele Dokumente, die alle dieselben Daten verwenden.
  • Verwenden Sie Platzhaltertext wie "#NAME #" und öffnen Sie jedes Dokument für eine Suche und Ersetzung. Dies ist die Lösung, auf die ich zurückgreifen würde, wenn nichts eleganteres vorgeschlagen wird.
20
Alain

Es ist lange her, dass ich diese Frage gestellt habe, und meine Lösung wurde immer weiter verfeinert. Ich musste mich mit allen möglichen Sonderfällen auseinandersetzen, z. B. mit Werten, die direkt aus der Arbeitsmappe stammen, Abschnitten, die speziell auf der Grundlage von Listen generiert werden müssen, und der Notwendigkeit, Ersetzungen in Kopf- und Fußzeilen vorzunehmen.

Es stellte sich heraus, dass die Verwendung von Lesezeichen nicht ausreichte, da Benutzer später Dokumente bearbeiten konnten, um Platzhalterwerte in den Dokumenten zu ändern, hinzuzufügen und zu entfernen. Die Lösung bestand in der Tat darin, Schlüsselwörter wie folgt zu verwenden:

enter image description here

Dies ist nur eine Seite aus einem Beispieldokument, die einige der möglichen Werte verwendet, die automatisch in ein Dokument eingefügt werden können. Über 50 Dokumente existieren mit völlig unterschiedlichen Strukturen und Layouts und unter Verwendung unterschiedlicher Parameter. Das einzige allgemeine Wissen, das von den Word-Dokumenten und der Excel-Tabelle geteilt wird, ist das Wissen darüber, was diese Platzhalterwerte darstellen sollen. In Excel wird dies in einer Liste von Schlüsselwörtern für die Dokumentenerstellung gespeichert, die das Schlüsselwort enthalten, gefolgt von einem Verweis auf den Bereich, der diesen Wert tatsächlich enthält:

enter image description here

Dies waren die beiden wichtigsten Zutaten, die benötigt wurden. Mit etwas cleverem Code musste ich nur noch jedes zu generierende Dokument durchlaufen und dann den Bereich aller bekannten Schlüsselwörter durchlaufen und für jedes Schlüsselwort in jedem Dokument suchen und ersetzen.


Erstens habe ich die Wrapper-Methode, die dafür sorgt, dass eine Instanz von Microsoft Word über alle zum Generieren ausgewählten Dokumente iteriert, die Dokumente nummeriert und die Benutzeroberfläche bearbeitet (wie das Behandeln von Fehlern, das Anzeigen des Ordners für den Benutzer usw.). )

' 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

Diese Routine ruft RunReplacements auf, das sich darum kümmert, das Dokument zu öffnen, die Umgebung auf einen schnellen Austausch vorzubereiten, einmal erfolgte Verknüpfungen zu aktualisieren, Fehler zu behandeln usw.

' 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

Diese Routine ruft dann RunSimpleReplacements auf. und RunAdvancedReplacements. Im ersten Fall durchlaufen wir den Satz der Schlüsselwörter für die Dokumentenerstellung und rufen WordDocReplace auf, wenn das Dokument unser Schlüsselwort enthält. Beachten Sie, dass es viel schneller ist, zu versuchen, Find eine Reihe von Wörtern zu verwenden, um herauszufinden, dass sie nicht vorhanden sind, und dann wahllos replace aufzurufen. Wir überprüfen daher immer, ob ein Schlüsselwort vorhanden ist, bevor wir versuchen, es zu ersetzen.

' 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

Mit dieser Funktion wird festgestellt, ob das Dokument ein Schlüsselwort enthält:

' 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

Und hier trifft der Gummi auf die Straße - den Code, der den Austausch ausführt. Diese Routine wurde komplizierter, als ich auf Schwierigkeiten stieß. Hier sind die Lektionen, die Sie nur aus Erfahrung lernen werden:

  1. Sie können den Ersetzungstext direkt festlegen oder die Zwischenablage verwenden. Ich habe herausgefunden, wie schwierig es ist, wenn Sie eine VBA-Ersetzung in Word mit einer Zeichenfolge durchführen, die länger als 255 Zeichen ist. Der Text wird abgeschnitten, wenn Sie versuchen, ihn in den Find.Replacement.Text einzufügen. Sie können jedoch "^c" als Ersatztext verwenden es wird es direkt aus der Zwischenablage bekommen. Dies war die Problemumgehung, die ich verwenden musste.

  2. Durch einfaches Aufrufen von "Ersetzen" werden Schlüsselwörter in einigen Textbereichen wie Kopf- und Fußzeilen übersehen. Aus diesem Grund müssen Sie tatsächlich den document.StoryRanges durchlaufen und die Suche und Ersetzung für jeden einzelnen ausführen, um sicherzustellen, dass Sie alle Instanzen des zu ersetzenden Worts abfangen.

  3. Wenn Sie den Replacement.Text direkt festlegen, müssen Sie Excel-Zeilenumbrüche (vbNewLine und Chr(10)) mit einem einfachen vbCr konvertieren, damit sie in Word ordnungsgemäß angezeigt werden. Andernfalls werden an allen Stellen, an denen Ihr Ersetzungstext Zeilenumbrüche aus einer Excel-Zelle aufweist, seltsame Symbole in Word eingefügt. Wenn Sie jedoch die Zwischenablagemethode verwenden, müssen Sie dies nicht tun, da die Zeilenumbrüche beim Ablegen in die Zwischenablage automatisch konvertiert werden.

Das erklärt alles. Kommentare sollten auch ziemlich klar sein. Hier ist die goldene Routine, die die Magie ausführt:

' 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

Wenn sich der Staub gelegt hat, verbleibt eine schöne Version des ursprünglichen Dokuments mit Produktionswerten anstelle der mit einem Hash markierten Schlüsselwörter. Ich würde gerne ein Beispiel zeigen, aber natürlich enthält jedes ausgefüllte Dokument alle geschützten Informationen.


Der einzige Gedanke, den ich noch erwähnen muss, ist wohl der Abschnitt RunAdvancedReplacements. Es funktioniert sehr ähnlich - es ruft dieselbe WordDocReplace -Funktion auf, aber das Besondere an den hier verwendeten Schlüsselwörtern ist, dass sie nicht mit einer einzelnen Zelle in der ursprünglichen Arbeitsmappe verknüpft sind, sondern im Code generiert werden. hinter von Listen in der Arbeitsmappe. So würde zum Beispiel eine der erweiterten Ersetzungen so aussehen:

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

Und dann wird es eine entsprechende Routine geben, die einen String zusammenstellt, der alle vom Benutzer konfigurierten Schiffsinformationen enthält:

' 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

die resultierende Zeichenfolge kann genau wie der Inhalt einer Excel-Zelle verwendet und an die Ersetzungsfunktion übergeben werden, die die Zwischenablagemethode entsprechend verwendet, wenn sie mehr als 255 Zeichen enthält.

Also diese Vorlage:

enter image description here

Plus diese Tabellendaten:

enter image description here

Wird dieses Dokument:

enter image description here


Ich hoffe aufrichtig, dass dies eines Tages jemandem hilft. Es war definitiv ein großes Unterfangen und ein komplexes Rad, das neu erfunden werden musste. Die Anwendung ist riesig, mit über 50.000 Zeilen VBA-Code. Wenn ich also in meinem Code auf eine wichtige Methode verwiesen habe, die jemand benötigt, hinterlassen Sie bitte einen Kommentar und ich werde ihn hier einfügen.

29
Alain

http://www.computorcompanion.com/LPMArticle.asp?ID=224 Beschreibt die Verwendung von Word bookmarks.

Ein Textabschnitt in einem Dokument kann Lesezeichen sein und einen Variablennamen erhalten. Mit VBA kann auf diese Variable zugegriffen werden, und der Inhalt des Dokuments kann durch alternativen Inhalt ersetzt werden. Dies ist eine Lösung, um Platzhalter wie Name und Adresse im Dokument zu haben.

Darüber hinaus können Dokumente mithilfe von Lesezeichen geändert werden, um auf mit Lesezeichen versehenen Text zu verweisen. Wenn ein Name mehrmals in einem Dokument angezeigt wird, kann die erste Instanz mit einem Lesezeichen versehen werden, und weitere Instanzen können auf das Lesezeichen verweisen. Wenn jetzt die erste Instanz programmgesteuert geändert wird, werden auch alle anderen Instanzen der Variablen im Dokument automatisch geändert.

Jetzt müssen Sie alle Dokumente aktualisieren, indem Sie den Platzhaltertext mit einem Lesezeichen versehen und in allen Dokumenten eine einheitliche Namenskonvention verwenden. Durchlaufen Sie dann alle Dokumente und ersetzen Sie das Lesezeichen, falls vorhanden:

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

Ich kann das Problem von Variablen, die in einem bestimmten Dokument nicht vorkommen, möglicherweise mit der Klausel on error resume next lösen, bevor ich die einzelnen Ersetzungen versuche.

Vielen Dank an Doug Glancy für die Erwähnung von Lesezeichen in seinem Kommentar. Ich hatte vorher keine Kenntnis von ihrer Existenz. Ich werde dieses Thema auf dem Laufenden halten, ob diese Lösung ausreicht.

3
Alain

Sie könnten einen XML-basierten Ansatz in Betracht ziehen.

Word verfügt über eine Funktion, die als benutzerdefinierte XML-Datenbindung oder datengebundene Inhaltssteuerelemente bezeichnet wird. Eine Inhaltskontrolle ist im Wesentlichen ein Punkt im Dokument, der Inhalt enthalten kann. Ein "datengebundenes" Inhaltssteuerelement erhält seinen Inhalt aus einem XML-Dokument, das Sie in die docx-Zip-Datei einfügen. Ein XPath-Ausdruck wird verwendet, um das XML-Bit anzugeben. Sie müssen also lediglich Ihre XML-Datei einbinden, den Rest erledigt Word.

Excel bietet Möglichkeiten, Daten als XML herauszuholen, sodass die gesamte Lösung gut funktionieren sollte.

Es gibt viele Informationen zur Inhaltsbindungsdatenbindung in MSDN (von denen einige in früheren SO Fragen referenziert wurden), so dass ich mich hier nicht darum kümmern möchte.

Sie benötigen jedoch eine Möglichkeit, die Bindungen einzurichten. Sie können entweder das Content Control Toolkit oder, wenn Sie es von Word aus machen möchten, mein OpenDoPE-Add-In verwenden.

2
JasonPlutext

Nachdem ich eine ähnliche Aufgabe ausgeführt hatte, stellte ich fest, dass das Einfügen von Werten in Tabellen viel schneller war als die Suche nach benannten Tags. Die Daten können dann wie folgt eingefügt werden:

    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

.Zelle (i + 1, 4) .Range.Text = "Total:" End Within diesem Fall waren Zeile 1 der Tabelle die Kopfzeilen; Zeile 2 war leer und es gab keine weiteren Zeilen - somit gilt der row.add einmal mehr als eine Zeile wurde angehängt. Die Tabellen können sehr detaillierte Dokumente sein. Durch Ausblenden der Rahmen und Zellränder kann der Text wie gewöhnlicher Text aussehen. Die Tabellen werden nach dem Dokumentenfluss fortlaufend nummeriert. (d.h. Doc.Tables (1) ist die erste Tabelle ... 

0
Simon N