Вкратце - есть устройство, генерирующее логи в формате CSV. Есть прога, которую я написал для чтения этих логов с FTP и построения графика (еще не готова, но основной функционал работает).
Проблема вот в чем: часть файлов читается, часть - нет. Кодировки проверял, всякие LF+CR тоже, еще что-то проверял, всего уже не помню.
Помогите пожалуйста определить где проблема - у меня в коде (я пока еще новичок), в самих CSV или может в сторонних модулях?
Lazarus 1.8.4, FPC 3.0.4., используются csvdocument и synapse.
- Код: Выделить всё
- unit mfr;
 {$mode objfpc}{$H+}
 interface
 uses
 Classes, SysUtils, FileUtil, TAGraph, TASeries, TATransformations, TATools,
 Forms, Controls, Graphics, Dialogs, StdCtrls, DBGrids, Grids, ComCtrls,
 ExtCtrls, FileCtrl, EditBtn, Buttons, Arrow, csvdocument, TAChartAxis,
 Ipfilebroker, RTTICtrls, laz_synapse, ftpsend;
 type
 { TForm1 }
 TForm1 = class(TForm)
 Arrow1: TArrow;
 btDrawAll: TButton;
 btLoad: TButton;
 btCompare: TButton;
 btShift: TButton;
 CATransformLeftAutoScaleAxisTransform1: TAutoScaleAxisTransform;
 CATransformRifgtAutoScaleAxisTransform1: TAutoScaleAxisTransform;
 Chart1: TChart;
 CGChecker: TCheckGroup;
 CATransformLeft: TChartAxisTransformations;
 CATransformRifgt: TChartAxisTransformations;
 CATransformBar2: TChartAxisTransformations;
 CATransformBar2AutoScaleAxisTransform1: TAutoScaleAxisTransform;
 CATransformBar1: TChartAxisTransformations;
 CATransformBar1AutoScaleAxisTransform1: TAutoScaleAxisTransform;
 CheckBox1: TCheckBox;
 FileListBox1: TFileListBox;
 Label1: TLabel;
 ListBox1: TListBox;
 ListView1: TListView;
 Memo1: TMemo;
 StringGrid1: TStringGrid;
 TrackBar1: TTrackBar;
 procedure btCompareClick(Sender: TObject);
 procedure btDrawAllClick(Sender: TObject);
 procedure btLoadClick(Sender: TObject);
 procedure CheckBox1Change(Sender: TObject);
 procedure FormCreate(Sender: TObject);
 procedure FormDestroy(Sender: TObject);
 procedure ListView1DblClick(Sender: TObject);
 procedure TrackBar1Change(Sender: TObject);
 procedure UpdateFl;
 private
 { private declarations }
 public
 FTPSend: TFTPSend;
 { public declarations }
 end;
 type csvLog=class(TCSVDocument)
 public
 Title,Date,Sample,Data:string;
 Combination: boolean;
 Units,Combi:array[0..16] of string;
 ur:integer;
 //загрузка данных и обработка шапки файла
 procedure FTPReadInfo(filename:string);
 //поиск № строки по тексту в первой колонке
 function RowSearch(text:string):Integer;
 //поиск № колонки по тексту и номеру строки
 function ColSearch(text:string;CRow:Integer):Integer;
 end;
 var
 Form1: TForm1;
 csv:csvLog;
 test,s0: string;
 syn1,syn2,syn9:integer;
 autoshift,combination:boolean;
 limits:array[1..10,1..2] of real;
 axisind:array[1..6] of Integer;
 const
 COLORS: array [0..15] of Integer =
 ($000080,$008000,$008080,$800000,$800080,$808000,$808080,$C0C0C0,$0000FF,$00FF00,$00FFFF,$FF0000,$FF00FF,$FFFF00,$000000,$F0CAA6);
 implementation
 {$R *.lfm}
 { TForm1 }
 procedure csvLog.FTPReadInfo(filename:string);
 var i,dr,cr: integer;
 begin
 csv.Clear;
 //чтение из файла в режиме возможной докачки
 if Form1.FTPSend.RetrieveFile(filename,true) then Self.LoadFromStream(Form1.FTPSend.DataStream) else exit;
 //заголовок файла
 Title:='TITLE: '+Cells[1,RowSearch('TITLE')];
 //формирование даты
 dr:=RowSearch('TRG_TIME');
 Date:='TRG_TIME: '+Cells[3,dr]+'/'+Cells[2,dr]+'/20'+Cells[1,dr]+' '+Cells[4,dr]+':'+Cells[5,dr]+':'+Cells[6,dr];
 //интервал замеров
 Sample:='TRG_SAMPLE: '+Cells[1,RowSearch('TRG_SAMPLE') ]+' ms';
 //массив заголовков данных
 ur:=RowSearch('UNIT');
 Combination:=false;
 Units[0]:='UNIT: ';
 for i:=1 to ColCount[ur] do
 begin
 Units[i]:=Cells[i,ur];
 end;
 //подсчет кол-ва строк с данными
 Data:='DATA: '+IntToStr(RowCount-ur-2)+' lines';
 //массив заголовков данных для комбинированного файла
 cr:=RowSearch('COMBI');
 if Cells[0,cr]='COMBI' then
 begin
 Combination:=true;
 Combi[0]:='COMBI: ';
 for i:=1 to ColCount[cr-1] do
 begin
 Combi[i]:=Cells[i,cr]+',';
 end;
 end;
 end;
 function csvLog.RowSearch(text:string):Integer;
 var i:integer;
 begin
 i:=0;
 while (Self.Cells[0,i]<>text) and (i<Self.RowCount-1) do inc(i);
 if i=Self.RowCount-2 then result:=-1;
 result:=i;
 end;
 function csvLog.ColSearch(text:string;CRow:Integer):Integer;
 var i:integer;
 begin
 i:=0;
 while (Self.Cells[i,CRow]<>text) and (i<Self.ColCount[CRow]-1) do inc(i);
 if i=(Self.ColCount[CRow]-2) then result:=-1;
 result:=i;
 end;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
 //создаем соединение с FTP
 FTPSend:=TFTPSend.Create;
 FTPSend.TargetHost:='172.16.170.11';
 FTPSend.UserName:='anonymous';
 FTPSend.Password:='';
 FTPSend.PassiveMode:=True;
 if FTPSend.Login then
 begin
 //получаем информацию об объектах в директории
 FTPSend.List(FTPSend.GetCurrentDir, false);
 //обновляем список
 UpdateFL;
 end;
 end;
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
 csv.Free;
 FTPSend.Logout;
 FTPSend.Free;
 end;
 //обновление списка файлов
 procedure TForm1.UpdateFl;
 var LI: TlistItem;
 I: Integer;
 begin
 ListView1.Items.BeginUpdate;
 try
 ListView1.Items.Clear;
 //создаем элемент для перехода на один уровень вверх
 LI:=ListView1.Items.Add;
 LI.Caption:='[...]';
 //заполняем список информацией об объектах в текущей директории
 for I := 0 to FTPSend.FtpList.Count-1 do
 begin
 LI:=ListView1.Items.Add;
 LI.Caption:=FTPSend.FtpList[i].FileName;
 if FTPSend.FtpList[i].Directory then
 Li.SubItems.Add('Папка')
 else
 Li.SubItems.Add('Файл');
 LI.SubItems.Add(IntToStr(FTPSend.FtpList[i].FileSize));
 LI.SubItems.Add((DateToStr(FTPSend.FtpList[i].FileTime)));
 end;
 finally
 ListView1.Items.EndUpdate;
 end;
 end;
 //обработка двойного клика в списке файлов
 procedure TForm1.ListView1DblClick(Sender: TObject);
 var b:boolean;
 s,line:string;
 i:integer;
 begin
 btLoad.Enabled:=false;
 if not Assigned(ListView1.Selected) then Exit;
 //переход на уровень вверх
 if ListView1.ItemIndex=0 then
 b:=FTPSend.ChangeToParentDir
 else
 //смена директории на выбранную
 b:=FTPSend.ChangeWorkingDir(ListView1.Selected.Caption);
 if b then
 begin
 //получаем данные об объектах в текущей директории
 FTPSend.List(EmptyStr,False);
 //обновляем список
 UpdateFL;
 end
 else
 begin
 s:=FTPSend.FtpList[ListView1.Selected.Index-1].FileName;
 //если файл нужного формата (csv), выводим информацию
 if Pos('.csv',s)>0 then
 begin
 //активация кнопки "Загрузить"
 btLoad.Enabled:=true;
 csv:=csvLog.Create;
 csv.FTPReadInfo(s);
 Memo1.Clear;
 Memo1.Lines.Add(csv.Title);
 Memo1.Lines.Add(csv.Date);
 Memo1.Lines.Add(csv.Sample);
 //создание списка заголовков в зависимости от типа файла
 if Combination then
 for i:=0 to 16 do line:=line+csv.Combi[i]
 else
 for i:=0 to 16 do line:=line+csv.Units[i];
 Memo1.Lines.Add(line);
 Memo1.Lines.Add(csv.Data);
 end;
 end;
 end;
 //загрузка данных в таблицу
 procedure LoadGrid(Grid:TStringGrid);
 var col,row:integer;
 begin
 Grid.BeginUpdate;
 Grid.Clear;
 Grid.RowCount:=csv.RowCount;
 Grid.ColCount:=csv.ColCount[csv.ur+1];
 for col:=0 to Grid.ColCount-1 do
 // "row+csv.ur" задает сдвиг по строкам для вывода только данных с первой строки таблицы
 for row:=0 to Grid.RowCount-1 do Grid.Cells[col,row]:=csv.Cells[col,row+csv.ur];
 Grid.EndUpdate;
 end;
 //замена точки на запятую, вычисление максимумов и точки синхронизации
 procedure ReplGrid(Grid: TStringGrid);
 var i,y,c,x:integer;
 curr: real;
 begin
 syn1:=0; syn2:=0;
 for x:=1 to Grid.ColCount-1 do
 for y:=1 to 2 do limits[x,y]:=0.0; //обнуление массива пределов
 Grid.BeginUpdate;
 for i:=1 to Grid.RowCount-1 do begin
 for c:=1 to Grid.ColCount-1 do
 begin
 if Grid.Cells[c,i]='' then break;
 Grid.Cells[c,i]:=StringReplace(Grid.Cells[c,i],'.',',',[rfReplaceAll, rfIgnoreCase]); //замена
 curr:=StrToFloat(Grid.Cells[c,i]);
 if curr>limits[c,1] then limits[c,1]:=curr; //максимум
 if curr<limits[c,2] then limits[c,2]:=curr; //минимум
 if (Grid.Col=8) and (curr<>0) and (syn1<>0) then syn1:=Grid.Row; //точка синхронизации
 end;
 end;
 Form1.Memo1.Lines.Add(FloatToStr(limits[2,1])+'/'+FloatToStr(limits[2,2])+', '+FloatToStr(limits[3,1])+'/'+FloatToStr(limits[3,2]));
 Grid.EndUpdate;
 end;
 //отображение позиции трекбара
 procedure TForm1.TrackBar1Change(Sender: TObject);
 begin
 Label1.Caption:=IntToStr(TrackBar1.Position);
 end;
 //отрисовка графика по таблице
 procedure TForm1.btDrawAllClick(Sender: TObject);
 var i,j:integer;
 ls: TLineSeries;
 trl,trr: TChartAxisTransformations;
 begin
 Chart1.ClearSeries; //все серии удаляются
 Chart1.AxisList[2].Visible:=false;
 Chart1.AxisList[0].Transformations.Destroy;
 trl:= TChartAxisTransformations.Create(Self);
 TAutoScaleAxisTransform.Create(Self).Transformations:=trl;//трансформация для левой оси
 Chart1.AxisList[0].Transformations:=trl;
 Chart1.AxisList[2].Transformations.Destroy;
 trr:= TChartAxisTransformations.Create(Self);
 TAutoScaleAxisTransform.Create(Self).Transformations:=trr;//трансформация для правой оси
 Chart1.AxisList[2].Transformations:=trr;
 for i := 1 to 6 do //создание новых серий
 begin
 ls := TLineSeries.Create(Self);
 Chart1.AddSeries(ls);
 if CGChecker.Checked[i-1] then //проверяем, отмечена ли серия для отрисовки
 begin
 ls.SeriesColor := COLORS[i];
 ls.LinePen.Width := 2;
 ls.Legend.Visible:=true;
 ls.Title:=StringGrid1.Cells[i+1,0];
 //серии с большими максимумами привязываются к правой оси
 if (limits[i+1,1]>100) or (limits[i+1,2]<-100) then
 begin
 ls.AxisIndexY:=2;
 Chart1.AxisList[2].Visible:=true;
 end
 else ls.AxisIndexY:=0;
 //флаговые серии привязываются к 2-м нижним осям
 // if i=7 then ls.AxisIndexY:=3;
 // if i=8 then ls.AxisIndexY:=4;
 axisind[i]:=ls.AxisIndexY; //запись в массив индексов осей
 for j := 1 to StringGrid1.RowCount-1 do
 begin
 if (StringGrid1.Cells[1,j]='') or (StringGrid1.Cells[i+1,j]='') then break;
 ls.AddXY(StrToFloat(StringGrid1.Cells[1,j]),StrToFloat(StringGrid1.Cells[i+1,j]));
 end;
 end;
 end;
 end;
 //отрисовка вторичных серий и синхронизация
 procedure TForm1.btCompareClick(Sender: TObject);
 var csv2:csvLog;
 ccol,crow,i,c,x,urow,shift:Integer;
 ls2: TLineSeries;
 begin
 shift:=0;
 x:=Chart1.SeriesCount;
 while x>6 do
 begin
 Chart1.DeleteSeries(Chart1.Series.Items[x-1]); //удаление старых вторичных серий если есть
 x:=Chart1.SeriesCount;
 end;
 for c:=1 to 6 do
 begin
 ls2:=TLineSeries.Create(Self); //создание новых серий
 Chart1.AddSeries(ls2);
 if CGChecker.Checked[c-1] then //проверка на сравнение отмеченных серий
 begin
 ls2.SeriesColor:=COLORS[c];
 ls2.Legend.Visible:=false;
 ls2.LinePen.Width:=1;
 ls2.AxisIndexY:=axisind[c];
 if not(FileExists(FileListBox1.FileName)) then exit; //TODO: FTP
 csv2:=csvLog.Create;
 csv2.LoadFromFile(FileListBox1.FileName);
 urow:=csv2.RowSearch('UNIT');
 //поиск нужной колонки по заголовку в соответствующем чекере
 ccol:=csv2.ColSearch(CGChecker.Items.Strings[c-1],urow);
 crow:=csv2.RowSearch('DATA 1');
 //если отмечена автосинхронизация, сравниваются точки синхронизации
 if autoshift then
 begin
 //поиск совпадающих меток
 for i:=crow to csv2.RowCount-1 do
 begin
 if (StrToInt(csv2.Cells[8,i])<>0) and (syn2<>0) then syn2:=i;
 end;
 if syn1<>syn2 then shift:=abs(syn1-syn2);
 end
 //при ручной синхронизации задается сдвиг по позиции трекбара
 else if abs(TrackBar1.Position)<(csv2.RowCount-urow+1) then shift:=abs(TrackBar1.Position);
 for i:=crow to StringGrid1.RowCount-1-shift do
 begin
 csv2.Cells[1,i]:=StringReplace(csv2.Cells[1,i],'.',',',[rfReplaceAll, rfIgnoreCase]);
 csv2.Cells[ccol,i+shift]:=StringReplace(csv2.Cells[ccol,i+shift],'.',',',[rfReplaceAll, rfIgnoreCase]);
 //обнуление пустых ячеек, если встречаются
 if csv2.Cells[1,i]='' then csv2.Cells[1,i]:='0';
 //сдвиг идет только по колонке с данными
 if csv2.Cells[ccol,i+shift]='' then csv2.Cells[ccol,i+shift]:='0';
 ls2.AddXY(StrToFloat(csv2.Cells[1,i]),StrToFloat(csv2.Cells[ccol,i+shift]));
 end;
 end;
 end;
 csv2.Free;
 end;
 //основная функция: чтение из файла, загрузка в таблицу, очистка, замена знака
 //TODO: FTP
 procedure TForm1.btLoadClick(Sender: TObject);
 var i:integer;
 begin
 btCompare.Enabled:=true;
 LoadGrid(StringGrid1);
 ReplGrid(StringGrid1);
 CGChecker.Caption:=s0;
 btDrawAll.Enabled:=true;
 CGChecker.Items.Clear;
 for i:=1 to 6 do
 begin
 CGChecker.Items.Add(StringGrid1.Cells[i+1,0]);
 if not Combination then CGChecker.Checked[i-1]:=true;
 end;
 end;
 //интерфейсная часть синхронизации
 procedure TForm1.CheckBox1Change(Sender: TObject);
 begin
 if CheckBox1.Checked then
 begin
 autoshift:=true;
 btShift.Enabled:=false;
 TrackBar1.Position:=0;
 TrackBar1.Enabled:=false;
 Label1.Caption:='';
 end
 else
 begin
 autoshift:=false;
 btShift.Enabled:=true;
 TrackBar1.Enabled:=true;
 end;
 end;
 end.
CSV файлики:
http://s000.tinyupload.com/index.php?file_id=56053932908693594650 - этот парсится нормально
http://s000.tinyupload.com/index.php?file_id=03757174214884548185 - парсится частично, только некоторые ячейки
http://s000.tinyupload.com/index.php?file_id=69652745427712141833 - вообще 0

Если кто-то всерьез заинтересуется, вышлю проект целиком.






