Прошу помочь с парсингом CSV
Добавлено: 13.07.2018 12:53:04
Доброго времени суток.
Вкратце - есть устройство, генерирующее логи в формате CSV. Есть прога, которую я написал для чтения этих логов с FTP и построения графика (еще не готова, но основной функционал работает).
Проблема вот в чем: часть файлов читается, часть - нет. Кодировки проверял, всякие LF+CR тоже, еще что-то проверял, всего уже не помню.
Помогите пожалуйста определить где проблема - у меня в коде (я пока еще новичок), в самих CSV или может в сторонних модулях?
Lazarus 1.8.4, FPC 3.0.4., используются csvdocument и synapse.
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
Если кто-то всерьез заинтересуется, вышлю проект целиком.
Вкратце - есть устройство, генерирующее логи в формате 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
Если кто-то всерьез заинтересуется, вышлю проект целиком.