Seite 1 von 1

Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Mo, 05. Jan 2009 20:08
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.

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 12:37
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

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 12:45
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.

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 13:09
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.

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 13:26
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.

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 21:30
von azzo
Danke für den Tip. Wie kann ich mit Word 2010 einen Platzhalter einfügen.
Danke im voraus.
Gruß
Otto

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 22:26
von Tom
Wie kann ich mit Word 2010 einen Platzhalter einfügen.
Ist das eine Frage?

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 22:41
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.

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 22:47
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

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 22:51
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.

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Do, 29. Jul 2010 23:20
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

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Fr, 30. Jul 2010 0:07
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 ;-)

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Fr, 30. Jul 2010 21:26
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.

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Fr, 30. Jul 2010 23:04
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

Re: Tip: Suchen und ersetzen in Word-Dokumenten

Verfasst: Fr, 13. Aug 2010 9:57
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