Page 1 of 1

Problem replacing variables / merge text

Posted: Tue Apr 30, 2013 6:21 am
by Joe
Hello all,

I have no more idea what to do further. I analyzed all forum topics and samples and now hope for your help.

In a database application in a table there are blob fields that hold richview text (edited by TDBRichViewEdit). The user can freely design these

richview texts and can especially insert field variables that will later be filled with data from the database (for example name and address of a

customer). These contents serve as different templates/layout records for offers, bills and other documents that can be created for customers. The

insertion of variables is done by:
Self.BodyPrintHeader.InsertStringWTag(VarName, TagName);
Self.BodyPrintHeader.SetFocus;
where VarName is a string like "@Firstname@" and TagName is a string "Name1" (that corresponds to the field name of a customer table from which on

printing the data will be filled).

Before printing the offers, bills and others, the desired layout is loaded from the templates table, the variables/tags are filled with real data

from the customer table, and the so modified RichView text is saved as blob to a temporary table (with other temp. data) for printing. On printing

most of the replaced variables are shown correctly, but others not. This means:

- If in the TRichView template the text holds variables and is edited/modified after the insertion of the variables, e.g. inserted a new line or

some text before or after a variable, the replaced variable text (=field value) shows up several times, for example 4 x the customer name, each in a

new line (unpredictable result)

- If in the TRichView template the text holds more than 1 variables in one line, the result will be without the "non variable"-text. The text around

the variables is completely lost.
Example:
"...will be send to: @Title@ @Firstname@ @Lastname@"
replaced result should be:
"...will be send to: Professor Peter Hawk"
but result is:
"ProfessorPeterHawk"

This is my code:

// rsVg: the record with the real customer data to read field values
// Idx: the primary key for the record with the template data
function VgLayoutReplaceItemText(rsVg : TADOQuery; const Idx : string) : boolean;
var
rsTmp : TADOStoredProcedure;
rsVgLyt : TADOStoredProcedure;
mStream : TMemoryStream;
trv : TRichViewEdit;
i : integer;
RVStyle : TRVStyle;
fldName : string;
begin

// Memory Stream for working on the richview layout
mStream := TMemoryStream.Create();

// RichView Element
RVStyle := TRVStyle.Create(Nil);
trv := TRichViewEdit.Create(Nil);
trv.Style := RVStyle;
trv.RTFReadProperties.TextStyleMode := rvrsAddifNeeded;
trv.RTFReadProperties.ParaStyleMode := rvrsAddifNeeded;
trv.RVFOptions := trv.RVFOptions + [rvfoSaveTextStyles, rvfoSaveParaStyles];
trv.Options := trv.Options + [rvoTagsArePChars];

// Load template record. DONT WASTE TIME ON THIS - THIS WORKS RELIABLY!
rsVgLyt := TADOStoredProcedure.Create(Nil);
sqlGetRecordset(gActDB, rsVgLyt, jmsSGRGetVgTypLayoutRecord,
rsVg.Fields.FieldByName('VgTyp').AsInteger, Idx, Null, Null);

// Load record in temp. table for saving the replaced data DONT WASTE TIME ON THIS - THIS WORKS RELIABLY!
rsTmp := TADOStoredProcedure.Create(Nil);
sqlGetRecordset(gActDB, rsTmp, jmsSGRGetTmpVgLayoutRecord,
rsVg.Fields.FieldByName('VgCode').AsString,
getCPrsCode(), Null, Null);

// Do 2 turns, one for the header layout, one for the footer layout
// The problem has nothing to do with the loop - appears also when the the loop is taken away

for i := 1 to 2 do
begin
if(i = 1) then
fldName := 'BodyPrintHeader'
else
fldName := 'BodyPrintFooter';

mStream.Clear;
mStream.Position := 0;

TBlobField(rsVgLyt.Fields.FieldByName(fldName)).SaveToStream(mStream);

mStream.Position := 0;
trv.LoadRVFFromStream(mStream);
//trv.Format; // Only possible if trv is located in a window
//trv.Change; // informs the linked dataset that data were modified

// == Replace variables ==================================================
VgLayoutFillFields(trv.RVData, rsVg);
//trv.Format; // Only possible if trv is located in a window

// Load RichView back to stream for saving
mStream.Clear;
mStream.Position := 0;
trv.SaveRVFToStream(mStream, false);

// Save modified rich view to temp. table
rsTmp.Edit;
rsTmp.Fields.FieldByName(fldName).Clear;
TBlobField(rsTmp.Fields.FieldByName(fldName)).LoadFromStream(mStream);
rsTmp.Post;
end;

rsTmp.Close;
rsTmp.Free;

rsVgLyt.Close;
rsVgLyt.Free;

trv.Free;
RVStyle.Free;

mStream.Free;
mStream := Nil;

result := True;

end;
{------------------------------------------------------------------------------}
// Taken originally from Sergey Tkachenko, only widened to work with other data than string
{
This function iterates through all items in RVData, and if a tag of
some text contains a non-empty text, it calls GetFieldValueFromDatabase(tag) and
replaces this text with the returned value.
You can move this function to your application unchanged.
Initial call: FillFields(RichView.RVData);
}
procedure VgLayoutFillFields(RVData: TCustomRVData; rs : TADOQuery);
var
i,r,c: Integer;
table: TRVTableItemInfo;
FieldName: String;
vTmp : variant;
sTmp : string;
begin
for i := 0 to RVData.ItemCount-1 do
begin
if(RVData.GetItemStyle(i) = rvsTable) then
begin
table := TRVTableItemInfo(RVData.GetItem(i));
for r := 0 to table.Rows.Count-1 do
for c := 0 to table.Rows[r].Count-1 do
if(table.Cells[r,c] <> Nil) then
VgLayoutFillFields(table.Cells[r,c].GetRVData, rs);
table.Changed;
end
else if(RVData.GetItemStyle(i) >= 0) then
begin
FieldName := RVData.GetItemTag(i);
if(FieldName <> STRNOTHING) then
begin
vTmp := rs.Fields.FieldByName(FieldName).value;

// Original by Sergey
//RVData.SetItemText(i, GetFieldValueFromDatabase(FieldName));

// Modified by JM, also for numeric values
case TField(rs.Fields.FieldByName(FieldName)).DataType of
{ -- Perhaps for future use
ftWideMemo: // In Access Datentyp 'Memo'
begin
// ??
end;

ftBlob, ftGraphic: // BLOB oder in Access OLE Objekt
begin
// ??
end;
------------------}
ftDate:
begin
if(vTmp = Null) then
sTmp := STRNOTHING
else
sTmp := System.SysUtils.DateToStr(vTmp);
end;

ftSmallint, ftInteger, ftWord, ftBoolean:
begin
if(vTmp = Null) then
sTmp := STRNOTHING
else
sTmp := System.SysUtils.IntToStr(vTmp);
end;

ftFloat, ftCurrency:
begin
if(vTmp = Null) then
sTmp := STRNOTHING
else
sTmp := System.SysUtils.FloatToStr(vTmp);
end;

else
sTmp := stdNZ(rs.Fields.FieldByName(FieldName).value, STRNOTHING);
end;

RVData.SetItemTextW(i, sTmp);
end;
end;
end;
end;

{------------------------------------------------------------------------------}


My environment:
TrichView 14.0.3
Delphi XE2


Additional information:
I do not necessarily need the handling with the 'tag's. The only thing I need is to hold a variable that will be replaced by other text. I also

tried this:

if(trv.SearchTextW('@CustName@', [rvseoDown, rvseoWholeWord])) then
begin
trv.InsertTextW('Here should be the customer name', false);
end;

but in the first line if(trv.SearchTextW(.... I get an "index out of..." error (Puuuuhhh....).

I hope for any help...

Joe

Posted: Tue Apr 30, 2013 4:06 pm
by Sergey Tkachenko
Is it possible to create a simple project (preferably using files instead of DB) and send it to me to richviewgmailcom?

Some guesses
1. Probably, when you press Enter before a field, a new empty line is created using properties of the current text (field), so it inherits its tag, and now you have two fields in your document. It may be solved by protecting the field's text style (for example, rvprDoNotAutoSwitch must work).
2. As for disappearing normal text. Two possible reasons
- a bug in field replacement procedure (but it's hard to find bugs in browser, so I need a test project)
- this normal text is not really a normal text but it has the same Tag as a field so it it treated as a field.

Posted: Wed May 01, 2013 1:05 pm
by Joe
Sergey, thank you for your response.

As it is rather complex to make a sample that will work with files instead of a database, first I will do some further actions according to your suggestions.

Perhaps I will be back soon...

Best regards
Joe