Некоторые изменения
Предлагаю несколько изменений в исходные тексты FR3. Если техподдержка не сочтет нужным внести эти изменения в очередной релиз, читатели форума могут сделать это сами. Эти изменения относятся к версии 3.20 (стабильная версия), хотя, если верить change log, к более поздним версиям эти изменения тоже можно применить.
1. Преобразование отчетов из формата FR2x в формат FR3 (реализовано в модуле frx2xto30.pas)
- ОШИБКА: если отчет FR2x содержал подотчеты (subreports), то при построении такого отчета в FR3 возникала ошибка Access Violation. Это происходило из-за ошибки в функции TfrSubreportLoadFromStream. Исправленная версия:
- ОШИБКА: При преобразовании теряются значения DisplayFormat, и в FR3 это свойство приходится устанавливать заново. Вот измененная функция TfrMemoViewLoadFromStream, частично решающая эту проблему преобразование числовых форматов в некоторых случаях по-прежнему происходит некорректно).
- ОШИБКА: Если в программу добавить модуль frx2xto30.pas, то изменяется поведение Pascal-скрипта: становится возможным не описывать переменные. Причем это изменение - глобальное, а не только для сконвертированных отчетов. В документации об этом упоминания нет. Более того, в таком режиме Pascal-скрипт работает не так, как работал скрипт FR2x: в FR2x переменная при первом обращении неявно описывалась как глобальная; а в FR3 (с включенным frx2xto30.pas) не описанная переменная неявно описывается как локальная для данной процедуры, при этом наличие глобальных переменных не проверяется. В результате становится невозможным использовать в скрипте глобальные (т.е. описанные вне всех процедур) переменные. Очевидно, налицо ошибка в логике обработки параметра <declarevars text="0"/> в XML-описании синтаксиса скрипта. В данном случае более предсказуемого поведения удалось добиться, закомментировав вызов процедуры fsModifyPascalForFR2 в секции initialization модуля frx2xto30.pas.
2. Как выяснилось, FastScript предоставляет крайне примитивные возможности по диагностике ошибок времени выполнения. Во многих случаях про возникновении ошибки во время выполнения скрипта не выдается никаких подсказок о месте возникновения ошибки. Это сильно затрудняет отладку, особенно на больших скриптах. Поэтому, для улучшения ситуации, в функции TfsScript.CallFunction и TfsScript.CallFunction1 (в модуле fs_iinterpreter.pas) добавлена обработка исключений так, чтобы в сообщении присутствовало имя функции скрипта, при выполнении которой произошла ошибка.
Вот текст измененных функций:
1. Преобразование отчетов из формата FR2x в формат FR3 (реализовано в модуле frx2xto30.pas)
- ОШИБКА: если отчет FR2x содержал подотчеты (subreports), то при построении такого отчета в FR3 возникала ошибка Access Violation. Это происходило из-за ошибки в функции TfrSubreportLoadFromStream. Исправленная версия:
procedure TfrSubreportLoadFromStream;
var
s: TfrxSubreport;
SubPage: Integer;
begin
TfrViewLoadFromStream;
s := TfrxSubreport.Create(Page);
SetfrxComponent(s);
Stream.Read(SubPage, 4);
s.Page := TfrxReportPage(Report.Pages[SubPage]);
if s.Page.Name = '' then
s.Page.CreateUniqueName;
s.Page.LeftMargin := 0;
s.Page.RightMargin := 0;
s.Page.TopMargin := 0;
s.Page.BottomMargin := 0;
end;
- ОШИБКА: При преобразовании теряются значения DisplayFormat, и в FR3 это свойство приходится устанавливать заново. Вот измененная функция TfrMemoViewLoadFromStream, частично решающая эту проблему преобразование числовых форматов в некоторых случаях по-прежнему происходит некорректно).
procedure TfrMemoViewLoadFromStream;
var
w: Word;
i: Integer;
Alignment: Integer;
Highlight: TfrHighlightAttr;
HighlightStr: String;
LineSpacing, CharacterSpacing: Integer;
m: TfrxMemoView;
procedure DecodeDisplayFormat;
var
LCategory: Byte;
LType: Byte;
LNoOfDecimals: Byte;
LSeparator: Char;
begin
LCategory := (Format and $0F000000) shr 24;
LType := (Format and $00FF0000) shr 16;
LNoOfDecimals := (Format and $0000FF00) shr 8;
LSeparator := Chr(Format and $000000FF);
case LCategory of
0: { text }
m.DisplayFormat.Kind := fkText;
1: { number }
begin
m.DisplayFormat.Kind := fkNumeric;
m.DisplayFormat.DecimalSeparator := LSeparator;
case LType of
0: m.DisplayFormat.FormatStr := '%2.' + IntToStr(LNoOfDecimals) + 'g';
1: m.DisplayFormat.FormatStr := '%g';
2: m.DisplayFormat.FormatStr := '%2.' + IntToStr(LNoOfDecimals) + 'f';
3: m.DisplayFormat.FormatStr := '%2.' + IntToStr(LNoOfDecimals) + 'n';
else
m.DisplayFormat.FormatStr := '%g' { can't convert custom format string };
end;
end;
2: { date }
begin
m.DisplayFormat.Kind := fkDateTime;
case LType of
0: m.DisplayFormat.FormatStr := 'dd.mm.yy';
1: m.DisplayFormat.FormatStr := 'dd.mm.yyyy';
2: m.DisplayFormat.FormatStr := 'd mmm yyyy';
3: m.DisplayFormat.FormatStr := LongDateFormat;
4: m.DisplayFormat.FormatStr := FormatStr;
end;
end;
3: { time }
begin
m.DisplayFormat.Kind := fkDateTime;
case LType of
0: m.DisplayFormat.FormatStr := 'hh:nn:ss';
1: m.DisplayFormat.FormatStr := 'h:nn:ss';
2: m.DisplayFormat.FormatStr := 'hh:nn';
3: m.DisplayFormat.FormatStr := 'h:nn';
4: m.DisplayFormat.FormatStr := FormatStr;
end;
end;
4: { boolean }
begin
m.DisplayFormat.Kind := fkBoolean;
case LType of
0: m.DisplayFormat.FormatStr := '0,1';
1: m.DisplayFormat.FormatStr := 'Нет,Да';
2: m.DisplayFormat.FormatStr := '_,X';
3: m.DisplayFormat.FormatStr := 'False,True';
4: m.DisplayFormat.FormatStr := FormatStr;
end;
end;
end;
end;
begin
TfrViewLoadFromStream;
m := TfrxMemoView.Create(Page);
SetfrxComponent(m);
SetfrxView(m);
with Stream do
begin
{ font info }
m.Font.Name := ReadString(Stream);
Read(i, 4);
m.Font.Size := i;
Read(w, 2);
m.Font.Style := frSetFontStyle(w);
Read(i, 4);
m.Font.Color := i;
{ text align, rotation }
Read(Alignment, 4);
if (Alignment and frtaRight) <> 0 then
m.HAlign := haRight;
if (Alignment and frtaCenter) <> 0 then
m.HAlign := haCenter;
if (Alignment and 3) = 3 then
m.HAlign := haBlock;
if (Alignment and frtaVertical) <> 0 then
m.Rotation := 90;
if (Alignment and frtaMiddle) <> 0 then
m.VAlign := vaCenter;
if (Alignment and frtaDown) <> 0 then
m.VAlign := vaBottom;
{ charset }
Read(w, 2);
if frVersion < 23 then
w := DEFAULT_CHARSET;
m.Font.Charset := w;
Read(Highlight, 10);
HighlightStr := ReadString(Stream);
m.Highlight.Condition := HighlightStr;
m.Highlight.Color := Highlight.FillColor;
m.Highlight.Font.Color := Highlight.FontColor;
m.Highlight.Font.Style := frSetFontStyle(Highlight.FontStyle);
if frVersion >= 24 then
begin
Read(LineSpacing, 4);
m.LineSpacing := LineSpacing;
Read(CharacterSpacing, 4);
m.CharSpacing := CharacterSpacing;
end;
end;
if frVersion = 21 then
Flags := Flags or flWordWrap;
if (Flags and flStretched) <> 0 then
m.StretchMode := smMaxHeight;
m.WordWrap := (Flags and flWordWrap) <> 0;
m.WordBreak := (Flags and flWordBreak) <> 0;
m.AutoWidth := (Flags and flAutoSize) <> 0;
m.AllowExpressions := (Flags and flTextOnly) = 0;
m.SuppressRepeated := (Flags and flSuppressRepeated) <> 0;
m.HideZeros := (Flags and flHideZeros) <> 0;
m.Underlines := (Flags and flUnderlines) <> 0;
m.RTLReading := (Flags and flRTLReading) <> 0;
DecodeDisplayFormat;
ConvertMemoExpressions(m, Memo.Text);
end;
- ОШИБКА: Если в программу добавить модуль frx2xto30.pas, то изменяется поведение Pascal-скрипта: становится возможным не описывать переменные. Причем это изменение - глобальное, а не только для сконвертированных отчетов. В документации об этом упоминания нет. Более того, в таком режиме Pascal-скрипт работает не так, как работал скрипт FR2x: в FR2x переменная при первом обращении неявно описывалась как глобальная; а в FR3 (с включенным frx2xto30.pas) не описанная переменная неявно описывается как локальная для данной процедуры, при этом наличие глобальных переменных не проверяется. В результате становится невозможным использовать в скрипте глобальные (т.е. описанные вне всех процедур) переменные. Очевидно, налицо ошибка в логике обработки параметра <declarevars text="0"/> в XML-описании синтаксиса скрипта. В данном случае более предсказуемого поведения удалось добиться, закомментировав вызов процедуры fsModifyPascalForFR2 в секции initialization модуля frx2xto30.pas.
2. Как выяснилось, FastScript предоставляет крайне примитивные возможности по диагностике ошибок времени выполнения. Во многих случаях про возникновении ошибки во время выполнения скрипта не выдается никаких подсказок о месте возникновения ошибки. Это сильно затрудняет отладку, особенно на больших скриптах. Поэтому, для улучшения ситуации, в функции TfsScript.CallFunction и TfsScript.CallFunction1 (в модуле fs_iinterpreter.pas) добавлена обработка исключений так, чтобы в сообщении присутствовало имя функции скрипта, при выполнении которой произошла ошибка.
Вот текст измененных функций:
function TfsScript.CallFunction(const Name: String; const Params: Variant): Variant;
var
i: Integer;
v: TfsCustomVariable;
p: TfsProcVariable;
begin
v := FindLocal(Name);
if (v <> nil) and (v is TfsProcVariable) then
begin
p := TfsProcVariable(v);
if VarIsArray(Params) then
for i := 0 to VarArrayHighBound(Params, 1) do
p.Params[i].Value := Params[i];
try
Result := p.Value;
except
on e: Exception do
begin
e.Message := 'Ошибка при исполнении функции "' + v.Name + '": ' + e.Message;
raise;
end;
end;
end
else
begin
Result := Null;
end;
end;
function TfsScript.CallFunction1(const Name: String; var Params: Variant): Variant;
var
i: Integer;
v: TfsCustomVariable;
p: TfsProcVariable;
begin
v := FindLocal(Name);
if (v <> nil) and (v is TfsProcVariable) then
begin
p := TfsProcVariable(v);
if VarIsArray(Params) then
for i := 0 to VarArrayHighBound(Params, 1) do
p.Params[i].Value := Params[i];
try
Result := p.Value;
except
on e: Exception do
begin
e.Message := 'Ошибка при исполнении функции "' + v.Name + '": ' + e.Message;
raise;
end;
end;
if VarIsArray(Params) then
for i := 0 to VarArrayHighBound(Params, 1) do
Params[i] := p.Params[i].Value;
end
else
Result := Null;
end;