Tip: Suchen und ersetzen in Word-Dokumenten

Einbindung von Office-Komponenten wie Word, Excel usw.

Moderator: Moderatoren

Antworten
Benutzeravatar
Tom
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 9361
Registriert: Do, 22. Sep 2005 23:11
Wohnort: Berlin
Hat sich bedankt: 101 Mal
Danksagung erhalten: 361 Mal
Kontaktdaten:

Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Tom »

Manch ein Anwender arbeitet gerne mit Word, und viele Anwender wünschen sich, Formulare oder Briefe mit Word gestalten zu können, aus denen die Applikationen dann "echte" Texte macht, indem Platzhalter ersetzt werden. Ich hatte selbst auf Basis eines veränderten Makros mal eine Active-X-Ansteuerung für Word geschrieben, die die Suchen-und-ersetzen-Funktion von Word benutzt hat, um Platzhalter zu finden. Das war quälend langsam und hat oft auch einfach nicht funktioniert. Das in den Alaska-Samples enthaltene Programm "FEED.PRG" hat mich dann auf eine Idee gebracht. Word bietet als Objekte der Klasse "Documents" "Words" an, und das sind alle im Dokument enthaltenen Wörter. Damit ist es ganz einfach. Die Bookmarks-Variante hat nämlich zwei Nachteile: 1. Ein Bookmark darf nur einmal enthalten sein (und wenn z.B. der Kundenname auf mehreren Seiten erscheinen soll, wird es hier schon schwierig) und 2. Kunden müssten erst lernen, wie das mit den Bookmarks geht. Platzhalter kann man demgegenüber einfach über die Zwischenablage oder - noch eleganter - über ein eigenes (per Makro in eine Dokumentenvorlage gelegtes) Menü in Word verfügbar machen. Nehmen wir für mein Beispiel an, dass Platzhalter immer mit dem Klammeraffen (@) beginnen. Es ginge aber auch ohne.

Merke: Sonderzeichen sind für Word jeweils ein eigenes Wort. "@NAME" sind zwei Wörter, also @ und NAME.

Here we go:

Code: Alles auswählen

FUNCTION WordReplace(cMeinText)
local oWord, oDoc, oWords, lHabePlatzHalter
oWord := CreateObject("Word.Application")
IF Empty( oWord )
  MsgBox( "Microsoft Word ist nicht installiert" )
  return nil
ENDIF
oWord := oWord:dynamicCast(ActiveXObject())
oWord:Visible := .F.
oWord:documents:open(cMeinText)
oDoc := oWord:ActiveDocument
oWords := oDoc:Words // alle Wörter des Dokuments
lHabePlatzHalter := .F. // wird gesetzt, sobald die Platzhalter-Einleitung "@" gefunden wird
for i := 1 to oWords:Count // Anzahl der Wörter
  if IstEinPlatzHalter("@"+oWords:Item(i):Text) .and. lHabePlatzHalter
    oWords:Item(i):Text := ErsetzePlatzHalter("@"+oWords:Item(i):Text)
    oWords:Item(i-1):Text := ""
  endif
  if oWords:Item(i):Text = "@"
    lHabePlatzHalter := .T.
  else
    lHabePlatzHalter := .F.
  endif
next
sleep(200)
oWord:ActiveDocument:Save()
oWord:Quit()
oWord:Destroy()
return nil
Die - zu verfassende - Funktion "IstEinPlatzhalter" müsste prüfen, ob @+Text in der Platzhalterliste enthalten ist, "ErsetzePlatzHalter" retourniert die dazugehörigen Echtdaten. Das ginge natürlich auch ohne das einleitende "@".

Geht ganz schnell und läuft völlig problemlos. Die Performance ist um ein Hundertfaches besser als bei "Suchen und ersetzen" über die entsprechende Word-Funktion.
Herzlich,
Tom
Benutzeravatar
Werner_Bayern
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 2125
Registriert: Sa, 30. Jan 2010 22:58
Wohnort: Niederbayern
Hat sich bedankt: 30 Mal
Danksagung erhalten: 75 Mal

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Werner_Bayern »

Servus Tom,

vielen Dank für die nützliche und schnelle Funktion. Hab sie fast 1:1 übernommen, zu beachten sind jedoch 2 Punkte:

Die Wörter aus oWords:Item(i):Text haben angefügte Leerzeichen, wenn dies so im Word-Text enthalten ist. "Dies ist ein @Platzhalter im Text" ==> oWords = "Platzhalter ".
Zum Zweiten führt das Ersetzen der Platzhalter dazu, dass sich auch sofort der zu durchsuchende Text ändert und deshalb auch die Worte nochmal durchsucht werden, die bereits ersetzt wurden, wenn aus einem Platzhalter mehrere werden: Wird "@Briefanrede" ersetzt mit "Sehr geehrte Damen und Herren," werden auch die Worte "geehrte Damen und Herren" nochmal durchsucht. Das habe ich jedoch nicht geändert, ist vielleicht mal eine Interessante Funktion!

Ausserdem habe ich die Funktion noch etwas optimiert (z. B. wird ascan nur durchgeführt, wenn es auch ein Platzhalter ist, also nicht mehr jedesmal) und gleich ergänzt, dann kann sie universell verwendet werden.

Code: Alles auswählen

FUNCTION WordReplace(cDatei, aSuch, aErsetz)
local oWord, oDoc, oWords, lHabePlatzHalter, i, nPos
#include "activex.ch"

oWord := CreateObject("Word.Application")
IF Empty( oWord )
  MsgBox( "Microsoft Word ist nicht installiert" )
  return NIL
ENDIF
oWord := oWord:dynamicCast(ActiveXObject())
oWord:Visible := .f.
oWord:documents:open(cDatei, .f., .t.)    // keine Konvertierungen anzeigen, readonly, dann auch kein Problem, wenn Dokument durch einen anderen User geöffnet ist
oDoc := oWord:ActiveDocument
oWords := oDoc:Words // alle Wörter des Dokuments
lHabePlatzHalter := .F. // wird gesetzt, sobald die Platzhalter-Einleitung "@" gefunden wird
for i := 1 to oWords:Count // Anzahl der Wörter, Count ändert sich dynamisch während des Ersetzens
   if lHabePlatzHalter .and. (nPos := ascan(aSuch, "@" + rtrim(oWords:Item(i):Text))) > 0
//      msgbox(aErsetz[nPos], oWords:Item(i):Text)
      oWords:Item(i):Text := aErsetz[nPos] + " "
      oWords:Item(i-1):Text := ""   // Platzhalter weg
   endif
   lHabePlatzHalter := oWords:Item(i):Text == "@"
next
oWord:ActiveDocument:Saveas(...)   // dann wird das Original nicht verändert
oWord:Visible := .t.
oWord:Application:Activate()   // evtl. zum Ausdruck anbieten
//oWord:Quit()
oWord:Destroy()
return NIL
es grüßt

Werner

<when the music is over, turn off the lights!>
Benutzeravatar
Rolf Ramacher
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 1930
Registriert: Do, 09. Nov 2006 10:33
Wohnort: Bergheim
Danksagung erhalten: 3 Mal
Kontaktdaten:

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Rolf Ramacher »

Hi

ich mache es ähnlich- rtf-Datei aus vorlage wird mit memoread gelesen. mit strtran werden die Werte ersetzt und als neue RTF-gespeichert - und die dann über Word an den Drucker übergeben.
Gruß Rolf

Mitglied der Gruppe XUG-Cologne
www.xug-cologne.de
Benutzeravatar
Tom
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 9361
Registriert: Do, 22. Sep 2005 23:11
Wohnort: Berlin
Hat sich bedankt: 101 Mal
Danksagung erhalten: 361 Mal
Kontaktdaten:

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Tom »

Hallo, Rolf.
ich mache es ähnlich
Mit Verlaub, machst Du nicht. Hier wird ein "lebendes" Word-Dokument (Format spielt keine Rolle!) innerhalb von Word geparst, unter Nutzung der Word-Objektstruktur und völlig unabhängig von Formatierungsvorgaben. Was Du machst, ist ein simples wiederholtes StrTran() auf einem Textdokument. Das scheitert übrigens, wie Du vielleicht schon bemerkt hast, wenn ein Kunde innerhalb eines Platzhalters die Formatierung wechselt, also z.B. so etwas macht: "@PLATZHALTER" (einfach mal ausprobieren! :wink: ). Mit der oben genannten Lösung hat man solche Probleme nicht, sie ist aber auch für andere Fälle gedacht.
Herzlich,
Tom
Benutzeravatar
Herbert
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 1991
Registriert: Do, 14. Aug 2008 0:22
Wohnort: Gmunden am Traunsee, Österreich
Danksagung erhalten: 3 Mal
Kontaktdaten:

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Herbert »

Sehr interessanter Ansatz, Tom.
Ich habe die Word-Lösung genau deswegen weggelassen.
Zudem sind auch in Kopf- und Fusszeilen keine Bookmarks möglich.
Werde das mal testen und berichten.
Grüsse Herbert
Immer in Bewegung...
Benutzeravatar
azzo
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 483
Registriert: So, 28. Mär 2010 19:21
Danksagung erhalten: 11 Mal

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von azzo »

Danke für den Tip. Wie kann ich mit Word 2010 einen Platzhalter einfügen.
Danke im voraus.
Gruß
Otto
Benutzeravatar
Tom
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 9361
Registriert: Do, 22. Sep 2005 23:11
Wohnort: Berlin
Hat sich bedankt: 101 Mal
Danksagung erhalten: 361 Mal
Kontaktdaten:

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Tom »

Wie kann ich mit Word 2010 einen Platzhalter einfügen.
Ist das eine Frage?
Herzlich,
Tom
Benutzeravatar
Werner_Bayern
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 2125
Registriert: Sa, 30. Jan 2010 22:58
Wohnort: Niederbayern
Hat sich bedankt: 30 Mal
Danksagung erhalten: 75 Mal

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Werner_Bayern »

Servus,

wie Du im Quelltext von Tom sehen kannst, wird der Klammeraffe "@" als Platzhalter-Symbol benutzt. Schreib also einfach im Text entsprechende Platzhalter, z. B. "@Briefanrede" und definiere für die Funktion im Array aSuch u. a. ein "@Briefanrede" und an gleicher Index-Position in aErsetz z. B. dann "Sehr geehrter Herr Sowieso". Die Funktion von Tom ersetzt dann den Platzhalter mit dem Text.
Schnell, einfach und zuverlässig.
es grüßt

Werner

<when the music is over, turn off the lights!>
Benutzeravatar
azzo
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 483
Registriert: So, 28. Mär 2010 19:21
Danksagung erhalten: 11 Mal

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von azzo »

Hallo Tom,
es sollte eine Frage sein.

Ich habe für docx Dateien eine ähnliche Lösung wie Rolf für RTF beschreibt. Habe ich unter

http://www.xbaseforum.de/viewtopic.php? ... zzo#p42891 gepostet.

Veränderung der eingefügten eigenen "@PLATZHALTER" sind auch hier ein Problem.

Ich würde gerne mit den Word eigenen Platzhaltern arbeiten. Finde aber nicht, wie man diese einfügt.

Gruß
Otto
Benutzeravatar
Tom
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 9361
Registriert: Do, 22. Sep 2005 23:11
Wohnort: Berlin
Hat sich bedankt: 101 Mal
Danksagung erhalten: 361 Mal
Kontaktdaten:

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Tom »

Hallo, Otto.

Word hat keine eingebaute Funktion, um solche Platzhalter einzufügen. Wir bieten dafür parallel zwei Möglichkeiten an: 1. Kann man in der Applikation die verfügbaren (und natürlich nur bei uns verwendbaren) Platzhalter in einer Tabelle markieren und an die Zwischenablage übergeben, um so von dort in Word einzufügen. Und 2. gibt es ein vorgefertigtes Makro, das in Word einen Menüeintrag anlegt. Dort kann man dann die Platzhalter auswählen und sie werden im Text an der aktuellen Stelle eingefügt. Das Makro liefern wir in einer entsprechenden Word-Vorlagedatei aus.

Ergänzung: Deine Lösung für DOCX-Dokumente ist nicht nur im Hinblick auf Formatierungen innerhalb von Platzhaltern problematisch. Bestimmte Textelemente (und längst nicht nur die Zeichen "<" und ">") können die gesamte XML-Struktur zerschießen. Die oben gezeigte und von Werner verbesserte Lösung arbeitet übrigens auch mit DOCX.
Herzlich,
Tom
Benutzeravatar
azzo
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 483
Registriert: So, 28. Mär 2010 19:21
Danksagung erhalten: 11 Mal

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von azzo »

Hallo Tom,
danke für deine Erklärung.
Ich verstehe das jetzt mit den Platzhaltern. Wir machen das auch so. Ich habe eine Listbox, die ich im Vordergrund halte und per Drag&drop kann man dann "unsere" Platzhalter einfügen.
Es ist richtig, dass einige Zeichen beim Ersetzen direkt in der XML-Datei Probleme machen.
Wir fangen diese Zeichen mit STRTRAN ab.
cVData := STRTRAN(cVData, "&", "+" )
cVData := STRTRAN(cVData, "€", "€" )
cVData := STRTRAN(cVData, "ä", "ä" )
cVData := STRTRAN(cVData, "ö", "ö" )
cVData := STRTRAN(cVData, "ü", "ü" )
cVData := STRTRAN(cVData, "Ä", "Ä" )
...

Leider gibt es keine 100% ige Lösung. Bei der Lösung mit AktiveX besteht das Problem mit dem Makrozugriff, der sehr oft deaktiviert ist bzw. von der Antivirensoftware geblockt wird und bei einer Click to run Installation gar nicht verfügbar ist.

Gruß
Otto
Benutzeravatar
brandelh
Foren-Moderator
Foren-Moderator
Beiträge: 15696
Registriert: Mo, 23. Jan 2006 20:54
Wohnort: Germersheim
Hat sich bedankt: 66 Mal
Danksagung erhalten: 33 Mal
Kontaktdaten:

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von brandelh »

Hi,

vor kurzem musste ich hunderte DOC ausfüllen und wollte nicht ewig auf ActiveX warten.
Zuerst versuchte ich es mit RTF, das ist aber unzuverlässig wenn man ein Formular in Word zusammenbaut.
Zumindest habe ich bei mir nach einer Änderung des Namens von {NachName} auf {MeinNachName}
das Schlüsselwort nicht mehr "am Stück" gefunden, sondern mit RTF Steuerzeichen.
Wenn man das Dokument direkt in RTF aufbaut mag das kein Problem sein.

Ich habe dann mit Word XML Dateien erzeugt und dort mit memoread() und StrTran() getauscht.
Ging rasend schnell ;-)
Gruß
Hubert
Benutzeravatar
Herbert
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 1991
Registriert: Do, 14. Aug 2008 0:22
Wohnort: Gmunden am Traunsee, Österreich
Danksagung erhalten: 3 Mal
Kontaktdaten:

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Herbert »

Habe die Geschwindigkeit angesehen

Beispiel: 1-Seitiges Dokument mit etwa 20 Feldern
1. Mit Bookmarks = 1.25 sec
2. Mit Tom's Methode = 1.32 sec
3. Mit reinen Textdateien (aber inkl. Druckersteuerungen darin) = 0.1 sec

Beispiel: 3-Seitiges Dokument mit etwa 80 Feldern
1. Mit Bookmarks = 2sec
2. Mit Tom's Methode = 5 sec
3. Mit reinen Textdateien (aber inkl. Druckersteuerungen darin) = 0.1 sec

Hmm, also bei gröseren Dokumenten mit vielen Feldern...
Das reine Öffnen des Word braucht 0.8 sec

Fall 1:

Code: Alles auswählen

FOR i := 1 TO LEN(aBook)
  IF aBook[i,2] == 'XXX'
    aBook[i,2] := ' '
  ENDIF
  IF oDoc:Bookmarks:Exists(aBook[i,1])   // DO WHILE
         oBookmark := oDoc:Bookmarks(aBook[i,1])
         oBookmark:Select()
         oDoc:Application:Selection:Text := (aBook[i,2])
         oBookmark:Destroy()
         nRepl++
  ENDIF
NEXT
Fall2:

Code: Alles auswählen

oWords := oDoc:Words          // alle Wörter des Dokuments
FOR i := 1 TO oWords:Count    // Anzahl der Wörter, Count ändert sich dynamisch während des Ersetzens
  IF lHabePlatzHalter .AND. (nPos := ASCAN(aSuch, RTRIM(oWords:Item(i):Text))) > 0
    oWords:Item(i):Text := aRepl[nPos] + " "
    oWords:Item(i-1):Text := ""   // Platzhalter weg
    nRepl++
  ENDIF
  lHabePlatzHalter := oWords:Item(i):Text == "@" 
NEXT
Heisst für mich:
Word ist nur brauchbar, wenn kleinere Dokumente mit wenig Feldern eingesetzt werden.
Welche Methode man einsetzt spielt eigentlich keine Rolle. Tom hat die Vorzüge der Methode 1 erläutert und leuchten mir ein.
Grüsse Herbert
Immer in Bewegung...
Benutzeravatar
azzo
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 483
Registriert: So, 28. Mär 2010 19:21
Danksagung erhalten: 11 Mal

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von azzo »

Hallo Herbert,
Start 0.00
nach 7Z.exe -0.16
nach MemoRead -0.16
nach ersetzen -0.17
nach memowrit -0.17
nach 7z a -tzip -1.86
nach WAITRUN -1.86
nach COPYFILE -1.90
WinExec Word -1.90
habe im Bereich vom Öffnen der Wordvorlage bis zum Ende des Abspeichern des "gemischten" Briefes eine Log-Funktion eingebaut.
logfile( "Zeit.log", {"nach ersetzen", nStart - seconds() } )

In der Vergangenheit habe ich auch mit Macros bzw. ActiveX gearbeitet. Aber das Problem mit dem Makrozugriff wird von Word zu Word Version größer.
Gruß
Otto
Benutzeravatar
Koverhage
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 2470
Registriert: Fr, 23. Dez 2005 8:00
Wohnort: Aalen
Hat sich bedankt: 102 Mal
Danksagung erhalten: 3 Mal
Kontaktdaten:

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Beitrag von Koverhage »

Hallo,

habe dies hier gefunden. So kann man die Bookmarks mehrfach verwenden, bin mir aber noch nicht sicher
wie ich das in der Xbase++ Anwendung machen kann.

Meine Suche bezog sich aber auf folgendes Problem.
Ich wollte (wie in Excel) mit Range arbeiten, um mir die im Doc vorhandenen Bookmarks
als Tabelle in einem Rutsch laden und dann die Tabelle abarbeiten. Damit würde die
Prüfung ob Bookmarks vorhanden sind entfallen und das ganze beschleunigen.
Vielleicht hat ja jemand ein Tip wie ich das machen kann.

Hier jetzt wie man Bookmarks mehrfach verwenden kann:
Bookmarks eignen sich ganz hervorragend, um in Word oder Excel Vorlagen zu erstellen, die dann später von einer Applikation mit Daten gefüllt werden.

Dabei geht man beispielsweise in Word hin und definiert ein Bookmark, indem man eine Stelle im Dokument markiert und mit "Einfügen / Textmarke" einen Namen für dieses Bookmark festlegt. Aus der Applikation heraus kann man dann später diese Felder Füllen, mit Befehlen wie:
.Bookmarks("Name1").Range.Text = "Müller"
Einen kleinen Haken hat die Sache: Durch diesen Befehl geht das Bookmark verloren, kann also nicht mehr referenziert werden. Die ist aber manchmal nötig, z.B. wenn man in der Kopf- oder Fußzeile ein Datenfeld aufführen möchte, und ein zu füllendes Bookmark referenziert.

Mit folgenden Befehlen kann man ein Bookmark füllen, ohne daß die Bookmark-Definition verloren geht:
Dim objRange As Word.Range
Set objRange = .Bookmarks("txtScheinNr").Range
objRange.Text = CStr(strScheinNr)
objRange.Bookmarks.Add "txtScheinNr", objRange
Gruß
Klaus
Antworten