hausens
Goto Top

Formular Daten in Excel importieren

Guten Morgen,
Ich steh vor einem Problem bei dem ich nicht weiterkomme, Hoffe jemand von euch kann mir dabei helfen.
Es geht darum Daten welche in ein geschütztes Word Formular (.docx) eingetragen worden sind ins Excel zu importieren.

Mein Ansatz ist das ich das über XML mache, hab dazu auch schon eine Anleitung gefunden und bis zum VBA script läuft auch alles.
Bei mir mangelt es sicher auch an VBA Kenntnis um das Importieren vernünftig hinzubekommen.

Hier die Anleitung nach welcher ich vorgegangen bin:
http://www.pc-magazin.de/ratgeber/tipp-so-werten-sie-word-formulare-aus ...

Sonstige Infos:
Office 2010
von .docx ins Excel importieren
Windows 7
VBA Script Kenntnisse - mäßig bis schlecht

Bitte um Hilfe


Freundliche Grüße

Hausens

Content-ID: 238471

Url: https://administrator.de/contentid/238471

Ausgedruckt am: 22.11.2024 um 08:11 Uhr

colinardo
Lösung colinardo 19.05.2014, aktualisiert am 13.05.2021 um 18:10:49 Uhr
Goto Top
Servus Hausens,
schau dir mal diese Demo-Dokumente an. In dem ZIP-File hast du einmal ein Excel-Sheet mit dem Makro und zwei Word-Formulare die Daten enthalten. Zum Test öffne das Excel-Sheet, klicke auf den Button und wähle im Dialog die zwei Formulare aus dessen Daten importiert werden sollen (Habe es dir gleich so geschrieben das gleich mehrere Formulare auf einmal importiert werden können). Weitere Kommentare findest du im VBA-Code des Excel-Sheets.

Hier der essentielle Auschnitt aus dem Code des oben verlinkten Demo-Sheets:
'.....  
                Set doc = objWord.Documents.Open(.SelectedItems(i))
                ' Suche das CustomXML im Dokument  
                For Each cXML In doc.CustomXMLParts
                    If cXML.BuiltIn = False Then
                        Set rootNode = cXML.SelectSingleNode("root")  
                        If Not rootNode Is Nothing Then
                            Set myXMLPart = cXML
                            Exit For
                        End If
                    End If
                Next
                'Wenn der CustomXML-Part gefunden wurde ...  
                If Not myXMLPart Is Nothing Then
                    'Formularfelder zu Variablen zuordnen  
                    strVorname = myXMLPart.SelectSingleNode("/root/Vorname").Text  
                    strNachname = myXMLPart.SelectSingleNode("/root/Nachname").Text  
                    
                    'Neue Zeile in der Tabelle hinzufügen und den Spalten die entsprechenden Werte zuweisen  
                    lo.ListRows.Add
                    lo.ListRows(lo.ListRows.Count).Range(1, 1).Value = strVorname
                    lo.ListRows(lo.ListRows.Count).Range(1, 2).Value = strNachname
   ' ....  

Das Word Content Control Toolkit zum Anpassen der XML-Verknüpfung erhaltet ihr folgendermaßen:

Ladet euch das folgende Archiv herunter
https://codeplexarchive.blob.core.windows.net/archive/projects/dbe/dbe.z ...

Darin müsst Ihr nun eine Datei extrahieren (s. Bild) und diese dann in setup.msi umbenennen.

screenshot

Bevor das Archiv ganz verschwindet stelle ich das Toolkit auch noch fertig umbenannt als Download zur Verfügung:
Word_2007_Content_Control_Toolkit_238471.zip


back-to-topWie fügt man neue Felder hinzu?

Um neue verknüpfte Felder einem Dokument hinzuzufügen macht man dies mit dem Toolkit wie folgt:

1. Man lädt das Word-Dokument in das Toolkit.
2. Man fügt dem Custom XML-Part ein neues XML-Element hinzu in dem die Daten des Feldes gespeichert werden:

screenshot

3. Dann verknüpft man das Content-Control-Feld mit dem Element indem man das Element aus der rechten Liste auf das Content-Control zieht. Wenn man auf das CC doppelt klickt sieht man das das neue Element damit verknüpft wurde.

screenshot

4. Danach noch das Dokument speichern.

5. Danach natürlich nicht vergessen im Excel VBA-Code das entsprechende Feld zu ergänzen und die Tabelle um eine Spalte zu erweitern. Für das Beispiel-Feld aus den Bildern sähe das so aus
' .....  
strPLZ = myXMLPart.SelectSingleNode("/root/PLZ").Text  
' ....  
lo.ListRows(lo.ListRows.Count).Range(1, 3).Value = strPLZ
Oder man nutzt den Code in einem Kommentar weiter unten der alle Felder automatisch extrahiert ohne sie manuell definieren zu müssen.

back-to-topWie füge ich Felder ohne das Toolkit hinzu?


Für diejenigen die das ganze ohne das Toolkit machen wollen, das geht auch. Folgende Bilderstrecke sollte die Verknüpfungen in den XML-Dateien im Word-Dokument verdeutlichen:

Um an den Inhalt des Word-Dokuments zu kommen muss man dem Dokument einfach die Dateiendung *.zip geben, denn ein Word-Dokument im OpenXML-Format ist im Hintergrund nur eine einfache ZIP-Datei dessen Inhalt sich im Klartext bearbeiten lässt.

back-to-topCustom XML
screenshot

back-to-topReferenzierung in der Haupt-XML Inhalts-Datei unter word/document.xml im ZIP des Word-Dokuments

screenshot

back-to-topDie GUID der Custom XML die man in der document.xml im jeweiligen Content-Control angeben muss.

screenshot

Grüße Uwe
hausens
hausens 19.05.2014 um 13:15:50 Uhr
Goto Top
Vielen Dank, bekommst 3 Daumen hoch^^
Hast mir extrem geholfen, genau das was ich gesucht habe face-smile

wünsch dir noch einen schönen Tag

LG
Hausens
RenateB
RenateB 20.07.2015 um 15:30:21 Uhr
Goto Top
Moin,

ich habe deine tolle Anleitung Schritt für Schritt nachgebaut. Klappte ganz gut bis ich die von dir vorgegebenen Formulare veränderte:

If Not myXMLPart Is Nothing Then
'Formularfelder zu Variablen zuordnen
strVorname = myXMLPart.SelectSingleNode("/root/Vorname").Text
strNachname = myXMLPart.SelectSingleNode("/root/Nachname").Text
strBeruf = myXMLPart.SelectSingleNode("/root/Beruf").Text
strDatum = myXMLPart.SelectSingleNode("/root/Datum").Text

'Neue Zeile in der Tabelle hinzufügen und den Spalten die entsrpechenden Werte zuweisen
lo.ListRows.Add
lo.ListRows(lo.ListRows.Count).Range(1, 1).Value = strVorname
lo.ListRows(lo.ListRows.Count).Range(1, 2).Value = strNachname
lo.ListRows(lo.ListRows.Count).Range(1, 3).Value = strBeruf
lo.ListRows(lo.ListRows.Count).Range(1, 4).Value = strDatum
End If

Über Word 2007 Content Control Toolkit habe ich die fehlenden Variablen in die XML-Datei eingefügt. Excel interessiert dieses überhaupt nicht. Er fügt den Vorn- und Nachnamen ein und lässt die Felder Beruf und Datum leer. Es wird zudem ein Zeilenumbruch angelegt, der vorher m. E. nicht da war. Was mache ich falsch?

Viele Grüße

Renate
colinardo
colinardo 20.07.2015 aktualisiert um 16:54:10 Uhr
Goto Top
Hallo Renate
Was mache ich falsch?
Du hast sehr wahrscheinlich vergessen den ContentControls die Tagnamen zuzuweisen face-wink. Außerdem solltest du natürlich die Tabelle vorher manuell um die weiteren Spalten erweitern.

7eb894a2b09e5ca81a3fa103e95edb94

Grüße Uwe
RenateB
RenateB 20.07.2015 um 20:48:06 Uhr
Goto Top
Moin Uwe,

das hatte ich alles gemacht. Die Tags waren in der XML Datei nicht richtig zugewiesen. Als das erledigt war, lief das Formular einwandfrei.

Danke für deine Mühe.
RenateB
RenateB 26.07.2015 um 16:17:12 Uhr
Goto Top
Hi Uwe,

deine Änderungen habe ich soweit umgesetzt. Sie funktionieren. Vielen Dank. Jetzt kommt das nächste "Problem". Das Makro soll auf dem zweiten Tabellenblatt auch laufen und die Daten auf Blatt 2 (mit dem Name Köln) ausgeben. Es kommt die Fehlermeldung "Index außerhalb des gültigen Bereichs". Das Tabellenblatt ist mit dem Namen "Köln" versehen". Ist in dem Makro vielleicht nur 1 Blatt für die gesamte Mappe definiert?

Sub btnImportFormA()
Dim WB As Workbook
Dim WS As Worksheet, lo As ListObject, objWord As Object, doc As Object, cXML As CustomXMLPart, myXMLPart As CustomXMLPart, dlg As FileDialog
'Arbeitsmappe referenzieren
Set WB = ThisWorkbook
'Tabelle im Dokument referenzieren
Set WS = WB.Sheets("Köln")
Set lo = WS.ListObjects("MeineDatenTabelle")
'Word Objekt erzeugen
Set objWord = CreateObject("Word.Application")

Nächster Test:

Sub btnImportFormA()
On Error Resume Next
Dim WS As Worksheet, lo As ListObject, objWord As Object, doc As Object, cXML As CustomXMLPart, myXMLPart As CustomXMLPart, dlg As FileDialog
'Tabelle im Dokument referenzieren
Set WS = Worksheet(2)
'List-Object Tabelle mit Namen referenzieren
Set lo = WS.ListObjects("MeineDatenTabelle")

Jetzt sagt er mir "Sub oder Prozedur nicht definiert".

Ich drehe durch!

Viele Grüße

Renate
colinardo
colinardo 26.07.2015 aktualisiert um 21:10:40 Uhr
Goto Top
Du musst auf dem zweiten Blatt ebenfalls eine richtige Tabelle (ListObject) erstellen und Ihr den entsprechenden Namen geben ("MeineDatenTabelle" oder wie du willst dann musst du es im Code auch darauf hin anpassen)
Einen Bereich macht man mit Als Tabelle formatieren zu einer richtigen Tabelle mit Spaltenfiltern, das siehst du auch an meinem Demodokument! Das ist der Grund warum du einen Index-Fehler erhältst, weil auf deinem zweiten Sheet dieses ListObject noch nicht existiert.
RenateB
RenateB 26.07.2015 um 22:29:19 Uhr
Goto Top
Zur Vollständigkeit Hinweis dazu:

wenn man einen Bereich als Tabelle formatiert (Start --> Formatvorlagen) bekommt man eine kontextsensitive Registerkarte namens Entwurf. In dieser hat man einen Tabellenbereíchsnamensfeld. Dieses Feld muss so heißen wie den Bereich den man durchsucht.

Viele Grüße.
colinardo
colinardo 27.07.2015 um 09:37:41 Uhr
Goto Top
Zitat von @RenateB:

Zur Vollständigkeit Hinweis dazu:
Dieses Feld muss so heißen wie den Bereich den man durchsucht.
Muss nicht, du kannst ihn nennen wie du willst musst aber in der Zeile
Set lo = WS.ListObjects("MeineDatenTabelle")  
dann den Namen des ListObjects anpassen.
RenateB
RenateB 27.07.2015 um 12:16:58 Uhr
Goto Top
Moin,

letzte Frage (versprochen):

in dem Word-Text sind Kontrollkästchen eingebaut. Sie sind alle in XML definiert.

Hier ein Ausschnitt:

strFuehrung = myXMLPart.SelectSingleNode("/root/FKT/Fuehrung").Text
strStrategie = myXMLPart.SelectSingleNode("/root/FKT/Strategie").Text
strPV = myXMLPart.SelectSingleNode("/root/FKT/PV").Text
strMS = myXMLPart.SelectSingleNode("/root/FKT/MS").Text
strFiF = myXMLPart.SelectSingleNode("/root/FKT/FiF").Text

Wenn ich mich an mein Programmieren - vor Jahrzehnten - erinnere, entspricht ein angekreuztes Kontrollkästchen der Ziffer 1. Es ist auch definiert wie die Ausgabe erfolgen soll:


If strFuehrung = 0 Then
lo.ListRows(lo.ListRows.Count).Range(1, 3).Text = "nein"
Else
lo.ListRows(lo.ListRows.Count).Range(1, 3).Text = "ja"
End If

If strStrategie = 0 Then
lo.ListRows(lo.ListRows.Count).Range(1, 4).Text = "nein"
Else
lo.ListRows(lo.ListRows.Count).Range(1, 4).Text = "ja"
End If
If strPV = 0 Then
lo.ListRows(lo.ListRows.Count).Range(1, 5).Text = "nein"
Else
lo.ListRows(lo.ListRows.Count).Range(1, 5).Text = "ja"
End If

If strMS = 0 Then
lo.ListRows(lo.ListRows.Count).Range(1, 6).Text = "nein"
Else
lo.ListRows(lo.ListRows.Count).Range(1, 6).Text = "ja"
End If

If strFiF = 0 Then
lo.ListRows(lo.ListRows.Count).Range(1, 7).Text = "nein"
Else
lo.ListRows(lo.ListRows.Count).Range(1, 7).Text = "ja"
End If

Es passiert überhaupt nichts! Die Felder bleiben leer, egal ob die Kontrollkästchen in Word aktiviert sind oder nicht. Muss ich ein Array definieren? Wenn ja, wie?

Viele Grüße und danke für deine Geduld.

Renate
colinardo
colinardo 27.07.2015 aktualisiert um 12:35:18 Uhr
Goto Top
Du musst hier nicht mit 0 oder 1 vergleichen sondern einenTextvergleich mit "true" oder "false" machen.
Beispiel:
If LCase(strFuehrung) = "true" Then  
    lo.ListRows(lo.ListRows.Count).Range(1, 3).Text = "ja"  
Else
    lo.ListRows(lo.ListRows.Count).Range(1, 3).Text = "nein"  
End If
Weil der Text je nach System eventuell groß oder klein ausgegeben werden könnte noch die zusätzliche Umwandlung des Wertes in Kleinbuchstaben mit LCase (), um eine konsistente Abfrage zu erhalten.

Grüße Uwe
schwazza
schwazza 23.02.2016 um 10:00:28 Uhr
Goto Top
Moin,

vielen Dank für die Anleitung. Hat mir schon mal sehr geholfen.

Ich stehe jetzt aber vor dem Problem, dass ich zu viele Formularfelder aus dem Word-Dokument auslesen möchte.
Ich erhalte daher die Fehlermeldung "Fehler beim Kompilieren: Prozedur zu groß". Ich habe von der Möglichkeit gelesen, dass eine Prozedur aufgeteilt werden könnte, aber da der komplette Code in einer Prozedur steht, fällt mir das Aufteilen schwer.

In erster Linie wird der Code durch die Zeilen "strVorname = myXMLPart.SelectSingleNode("/root/Vorname").Text" ff. und "lo.ListRows(lo.ListRows.Count).Range(1, 1).Value = strVorname" ff. sehr lang. Gibt es da sonst eine Möglichkeit, die Felder anders einzulesen und einzufügen?
Ich habe gesehen, dass es möglich ist eine .xml-Datei mit Hilfe des Word 2007 Content Control Toolkits zu exportieren. Ist es darüber evtl. möglich oder gibt es eine gute Möglichkeit die Prozedur aufzuteilen oder eine andere Variante, die mir nicht einfällt?

LG
schwazza

Sonstige Infos:
Office 2013
Windows 7
VBA Script Kenntnisse - mäßig bis schlecht
colinardo
colinardo 25.02.2016 um 16:42:28 Uhr
Goto Top
Hallo schwazza,
das ist kein Problem man kann die xPath Query so anpassen das einem alle Felder zurückgeliefert werden. Diese lassen sich dann mit einer For-Each Schleife entsprechend verarbeiten ohne das man für jedes Feld manuell einen Eintrag erzeugen müsste.

Dieses Beispiel setzt dies um und ermittelt von selbst die Anzahl an Feldern und deren Überschriften.
Sub btnImportForms()
    On Error Resume Next
    Dim ws As Worksheet, objWord As Object, doc As Object, cXML As CustomXMLPart, myXMLPart As CustomXMLPart, dlg As FileDialog, n As Long
    'Tabelle im Dokument referenzieren  
    Set ws = Worksheets(1)
    'Word Objekt erzeugen  
    Set objWord = CreateObject("Word.Application")  
    objWord.Visible = False
    objWord.DisplayAlerts = False
    'Dateiauswahl-Dialog erstellen  
    Set dlg = Application.FileDialog(msoFileDialogFilePicker)
    With dlg
        'Einstellungen für den Dialog setzen  
        .AllowMultiSelect = True
        .Title = "Bitte markieren sie ein oder mehere Formulare deren Daten importiert werden sollen"  
        .Filters.Add "Word Dateien", "*.docx; *.docm", 1  
        .FilterIndex = 1
        ' Wenn der Dialog mit OK geschlossen wurde mache weiter  
        If .Show = -1 Then
            ' Für jedes Dokument das im Dialog ausgewählt wurde ..  
            For i = 1 To .SelectedItems.Count
                'Öffne das Dokument  
                Set doc = objWord.Documents.Open(.SelectedItems(i))
                ' Suche das CustomXML im Dokument  
                For Each cXML In doc.CustomXMLParts
                    If cXML.BuiltIn = False Then
                        Set rootNode = cXML.SelectSingleNode("root")  
                        If Not rootNode Is Nothing Then
                            Set myXMLPart = cXML
                            Exit For
                        End If
                    End If
                Next
                'Wenn der CustomXML-Part gefunden wurde ...  
                If Not myXMLPart Is Nothing Then
                    'Alle Felder holen  
                    Set objNodes = myXMLPart.SelectNodes("/root/*")  
                    'Daten der Felder in Arrays schreiben  
                    Dim headers() As String
                    Dim values() As String
                    For n = 1 To objNodes.Count
                        ReDim Preserve headers(1 To n)
                        ReDim Preserve values(1 To n)
                        headers(n) = objNodes(n).BaseName
                        values(n) = objNodes(n).Text
                    Next
                    
                    'Wenn Überschriften noch nicht gesetzt sind ...  
                    If i = 1 Then
                        'Überschriften ins Sheet in Zeile 5 schreiben  
                        With ws.Range("A5").Resize(1, UBound(headers))  
                            .Value = headers
                            .Font.Bold = True
                        End With
                    End If
                    'Ergänze die Daten der Felder in der nächsten unbelegten Zeile  
                    ws.Range("A" & ws.UsedRange.SpecialCells(xlCellTypeLastCell).Row + 1).Resize(1, UBound(values)).Value = values  
                    
                End If
                'Dokument schließen  
                doc.Close False
            Next
        End If
    End With
    
    'Word schließen  
    objWord.DisplayAlerts = False
    objWord.Quit
    Set objWord = Nothing
End Sub
Grüße Uwe
schwazza
schwazza 01.03.2016 um 17:24:44 Uhr
Goto Top
Super, das hat mir schon mal sehr geholfen. Vielen Dank!

Jetzt habe ich aber leider auch schon das nächste Problem:
Ich habe eine Spalte (als Beispiel "B"), in der bisher folgendermaßen der Name des ausgelesenen Dokumentes als Hyperlink stand:

'           Finde den Dateinamen und -pfad heraus +Hyperlink  
            DocName1 = Right(File, Len(File) - InStrRev(File, "\"))  
            DocName2 = Left(DocName1, (InStrRev(DocName1, ".") - 1))  
            ActiveSheet.Hyperlinks.Add Anchor:=Lo.ListRows(Lo.ListRows.Count).Range(1, 2), _
            Address:=File, ScreenTip:=File, TextToDisplay:=DocName2


Nun wird aber der gesamte ausgelesene Inhalt in eine Zeile eingefügt und danach passiert das Gleiche mit dem nächsten Dokument. Dadurch steht in der Spalte B für den Hyperlink bereits ein Text.
Außerdem habe ich noch ein paar weitere Spalten in die ein fester Wert soll.

Gibt es da zufällig auch eine Möglichkeit?
Kann man evtl. einige Spalten beim Einfügen der Daten der Felder überspringen und im Anschluss diese Zellen gezielt ansprechen?

Gruß
schwazza
colinardo
colinardo 01.03.2016 aktualisiert um 23:55:57 Uhr
Goto Top
Hallo Schwazza,
ja das ist kein Problem, im obigen Skript wird ja in der FOR-Schleife nur ein Array aus den Daten gebaut. Stattdessen kannst du die Daten dort auch direkt in die gewünschten Zellen schreiben und bei Bedarf auch Spalten hinzufügen.

Aber bitte, im Sinne des Thread Owners sehen wir hier das Kapern von fremden Threads im Allgemeinen sehr ungerne (s. Forenrichtlinien
). Also bitte habe Verständnis das ich das hier jetzt abbreche.

Bei weiteren Fragen kannst du mich gerne via PM kontaktieren.

Merci.
Grüße Uwe
schwazza
schwazza 02.03.2016 um 09:13:09 Uhr
Goto Top
Das mit dem "Kapern" war mir so nicht bewusst. Ich hatte gedacht, dass evtl. auch andere das Problem haben könnten und hier dann auch direkt zu diesem Problem eine Lösung vorfinden können, da es thmatisch sehr gut zur ursprünglichen Frage passte.

Vielen Dank für deine Idee.
Ich werde dich dann ggf. via PM kontaktieren.

Gruß
schwazza