问题
I write an application that does also document management. I do this in Delphi but the question may apply to c# too. I just got a very useful reply to this question I asked that made ma Search&Replace work easier.
One requirement I have is automatically insert some data in the document according to the db data every time the user opens a document. Up to now (and this includes the subject of the question mentioned above) I was doing this just once: "create document from template" = substitute once and the work is done. Search & Replace works perfectly for this, it is also very easy for users.
Now the requirement is to do it continuously, every time I open a document, so the same document should contain the "placeholder" and the "real data" (once I open it).
A simple example is this:
in the header of a word docuement the user wants to insert a kind of placeholder for 3 fields: company_logo (an image), revision numner (an integer), revision date (a datetime).
Currently the only techniques I am aware of are:
1) Search & Replace - I already use this for doing other things
2) placeholders - I never used that but I guess it is doable (there are several posts here like this one)
With Search & Replace I would do like this:
a) I ask a user to prepare a word document where he writes db fields in curly braces like {NAME} {SURNAME} (note: this are just curly braces, not MSWord placeholders). b) when the user "checks out" the document for editing it he will keep reading {NAME} and SURNAME} c) when the user "opens in read only mode" I do the Search&Replace trick
This for sure will work sith Search & Replace and it is a very simple technique to understand for the user.
I guess the same will work with Placeholders (the ones that is possible to inserti in Word with CTRL + F9).
But what to do with images?
And which are the alternatives one can use to insert content at runtime in a word document?
回答1:
In a comment on your question how to avoid the 255 limit ( How to bypass the 255 char limitation in MSWord Search&Replace using OLE ), I mentioned document variables as perhaps an easier way to add run-time content to a word document.
I think they are also the answer to what you want to do here. However, they are not exactly intuitive for end-users, so it would be nice to keep the place holders as well. Unfortunately that is not entirely possible, because you would end up with a document containing both a place holder and a document variable field. And that would in the end result in values being repeated.
Still, we can retain the place holders for the end-users. Certainly for their initial editing of the "template". And even for adding new place holders to an existing word document that has already been "converted to using variables".
The code below shows how to go about it. I haven't included the "standard" Word automation parts of opening and closing Word and/or a document. I start from the point where you have used the template to open a new document and are now ready to start replacing values. The solution then lies in three methods:
HideExistingFieldCodes(Doc);
AddFieldDocVarsToPlaceHolders(Doc);
SetValuesForDocVars(Doc);
The HideExistingFieldCodes is needed because we use the place holders as names for the document variables as well and if the fields are showing their codes instead of their values, we end up replacing the document variable name with a new document variable and that will probably make Word barf loudly.
procedure HideExistingFieldCodes(const Doc: WordDocument);
var
i: Integer;
begin
for i := 1 to Doc.Fields.Count do begin
// Only interested in document variables.
if Doc.Fields.Item( i ).Type_ = wdFieldDocVariable then begin
Doc.Fields.Item( i ).ShowCodes := False;
end;
end;
// Do not call Doc.Fields.Update, because that will show all fields' code again.
// Doc.Fields.Update;
end;
Once the codes of any existing fields have been hidden, we can scan the document for place holders and replace each with a document variable field.
procedure AddFieldDocVarsToPlaceHolders(const Doc: WordDocument);
var
i: Integer;
OleTrue: OleVariant;
OleFalse: OleVariant;
OleEmpty: OleVariant;
FindText: OleVariant;
Replace: OleVariant;
FieldType: OleVariant;
NewField: Field;
begin
OleTrue := True;
OleFalse := False;
OleEmpty := '';
Replace := wdReplaceOne;
FieldType := wdFieldDocVariable;
// Skip the titles.
for i := 1 to PlaceHoldersEdit.Strings.Count do begin
FindText := Format('%s', [PlaceHoldersEdit.Keys[i]]);
FWord.Selection.SetRange(0, 0); // Back to the beginning of the document.
while FWord.Selection.Find.ExecuteOld({FindText}FindText, {MatchCase}EmptyParam, {MatchWholeWord}EmptyParam,
{MatchWildcards}EmptyParam, {MatchSoundsLike}EmptyParam, {MatchAllWordForms}EmptyParam, {Forward}OleTrue,
{Wrap}OleFalse, {Format}EmptyParam, {ReplaceWith}OleEmpty, {Replace}Replace )
do begin
NewField := FWord.Selection.Fields.Add({Range}FWord.Selection.Range, {Type_}FieldType, {Text}FindText, {PreserveFormatting}OleTrue);
NewField.ShowCodes := False; // Make sure document variable name is hidden
// Select this field and set selection to the end of its definition, making
// doubly sure we won't find its name and replace it again.
NewField.Select;
FWord.Selection.SetRange(FWord.Selection.End_, FWord.Selection.End_);
end;
end;
end;
I used a TValueListEditor (named PlaceHoldersEdit) to hold the place holder names and values. Its first row contains the captions of the columns in this control and is skipped. After that it is a matter of looping over all the place holders and for each looping over its occurences in the Word document.
Then finally we can start adding the actual document variables which will hold the values for the document variable fields. (Oh how I love all these similar sounding names.)
procedure SetValuesForDocVars(const Doc: WordDocument);
function ExtractVarNameFromField(VarCode: WideString): OleVariant;
var
s: string;
i: Integer;
begin
// Code Text: DOCVARIABLE Naam \* MERGEFORMAT
// Code Text: DOCVARIABLE Naam
s := Trim(VarCode);
Delete(s, 1, Length('DOCVARIABLE '));
i := Pos('\* MERGEFORMAT', s);
if i > 0 then begin
Delete(s, i, Length('\* MERGEFORMAT'));
end;
Result := Trim(s); // Somebody might have added extra spaces by hand...
end;
function PlaceHolderValue(const aKey: string): string;
begin
Result := PlaceHoldersEdit.Values[aKey];
end;
function ReplaceCrLfWithCr(const aSource: string): string;
begin
Result := StringReplace(aSource, #13#10, #13, [rfReplaceAll]);
end;
var
i: Integer;
OleVarName: OleVariant;
OleVarValue: OleVariant;
DocVar: Variable;
begin
for i := 1 to Doc.Fields.Count do begin
// Only interested in document variable fields.
if Doc.Fields.Item( i ).Type_ = wdFieldDocVariable then begin
OleVarName := ExtractVarNameFromField( Doc.Fields.Item( i ).Code.Text );
OleVarValue := ReplaceCrLfWithCr(PlaceHolderValue(OleVarName));
// Word removes fields/variables with an empty string as their value,
// adding a space prevents that.
if OleVarValue = '' then begin
OleVarValue := ' ';
end;
DocVar := Doc.Variables.Item(OleVarName);
if VarIsNull(DocVar) then begin
Doc.Variables.Add( OleVarName, OleVarValue );
end else begin
DocVar.Value := OleVarValue;
end;
end;
end;
Doc.Fields.Update;
Doc.Fields.ToggleShowCodes;
end;
In this method I loop over all document variable fields and extract the variable name from each fields' code. Then I try to find a document variable of that name. If it doesn't exist yet, I add it. If it does exist I change its value. If performance is a concern and a variable name is used in many fields, this could be optimised a bit to change the value only once.
Oh, I have tested with a "saved" document, but I haven't gone as far as adding new place holders to a document that has already been through converting place holders to document variable fields. So, you might still have to smooth things out a bit.
By the way: In Word, Shft-F9 toggles a field between showing its code and showing its value. You will have to use the Options dialog (Word2003, don't know where the ended up in later versions) to show/hide them all in one fell swoop. The option is "Field codes" under "Show" or "Display" on the "Display" or "View" tab (translated from Dutch).
来源:https://stackoverflow.com/questions/6791590/alternatives-to-search-and-replace-to-use-for-telling-to-an-application-where-to