mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2025-08-26 11:17:57 +08:00
Refactor dataset2html/csv/xml routines:
- Use TMemoryStreams for copy actions, TFileStreams for export actions. Should save some memory when saving to files and improves performance by factor ~100 on larger grid results. - Fetch text from the Grid.Text, not from the underlying TGridResult.Rows array. Fixes issue #685 . - Removes non functionial export of image files from BLOBs - Sanitize helpers function from GUI code (mousecursor, opening result file, ...) Todo: - Limit export to a user defined number of rows. Important for large tables. - Fetch entire TEXT field content, currently limited to 256 chars
This commit is contained in:
@ -82,9 +82,9 @@ type
|
||||
function encrypt(str: String): String;
|
||||
function decrypt(str: String): String;
|
||||
function htmlentities(str: WideString): WideString;
|
||||
function dataset2html(ds: TGridResult; htmltitle: WideString; filename: String = ''; ConvertHTMLEntities: Boolean = true; Generator: String = ''): Boolean;
|
||||
function dataset2csv(ds: TGridResult; Separator, Encloser, Terminator: String; filename: String = ''): Boolean;
|
||||
function dataset2xml(ds: TGridResult; title: WideString; filename: String = ''): Boolean;
|
||||
procedure GridToHtml(Grid: TVirtualStringTree; Title: WideString; ConvertHTMLEntities: Boolean; S: TStream);
|
||||
procedure GridToCsv(Grid: TVirtualStringTree; Separator, Encloser, Terminator: String; S: TStream);
|
||||
procedure GridToXml(Grid: TVirtualStringTree; root: WideString; S: TStream);
|
||||
function esc2ascii(str: String): String;
|
||||
function StrCmpBegin(Str1, Str2: string): Boolean;
|
||||
function Max(A, B: Integer): Integer; assembler;
|
||||
@ -135,11 +135,13 @@ type
|
||||
function GetDBObjectType( TableStatus: TFields ): Byte;
|
||||
procedure SetWindowSizeGrip(hWnd: HWND; Enable: boolean);
|
||||
procedure SaveUnicodeFile(Filename: String; Text: WideString);
|
||||
function CreateUnicodeFileStream(Filename: String): TFileStream;
|
||||
procedure OpenTextFile(const Filename: String; out Stream: TFileStream; out FileCharset: TFileCharset);
|
||||
function GetFileCharset(Stream: TFileStream): TFileCharset;
|
||||
function ReadTextfileChunk(Stream: TFileStream; FileCharset: TFileCharset; ChunkSize: Int64 = 0): WideString;
|
||||
function ReadTextfile(Filename: String): WideString;
|
||||
procedure CopyToClipboard(Value: WideString);
|
||||
procedure StreamToClipboard(S: TMemoryStream);
|
||||
|
||||
var
|
||||
MYSQL_KEYWORDS : TStringList;
|
||||
@ -729,235 +731,180 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
procedure ExportStatusMsg(Node: PVirtualNode; RootNodeCount: Cardinal; StreamSize: Int64);
|
||||
begin
|
||||
Mainform.Showstatus('Exporting row '+FormatNumber(Node.Index+1)+' of '+FormatNumber(RootNodeCount)+
|
||||
' ('+IntToStr(Trunc((Node.Index+1) / RootNodeCount *100))+'%, '+FormatByteNumber(StreamSize)+')'
|
||||
);
|
||||
end;
|
||||
|
||||
|
||||
{***
|
||||
Converts a TDataSet to a HTML-Table.
|
||||
If a filename is given, save HTML to disk, otherwise copy content to clipboard
|
||||
|
||||
@param TDataSet Object which holds data to export
|
||||
Converts a Grid to a HTML-Table.
|
||||
@param Grid Object which holds data to export
|
||||
@param string Text used in <title>
|
||||
@param string Filename to use for saving. If not given, copy to clipboard.
|
||||
@param boolean Use htmlentities() on cell-contents?
|
||||
@param string Generator, used for meta-tag in HTML-head
|
||||
@return boolean True on access, False in case of any error
|
||||
}
|
||||
function dataset2html(ds: TGridResult; htmltitle: WideString; filename: String = ''; ConvertHTMLEntities: Boolean = true; Generator: String = ''): Boolean;
|
||||
procedure GridToHtml(Grid: TVirtualStringTree; Title: WideString; ConvertHTMLEntities: Boolean; S: TStream);
|
||||
var
|
||||
I, J : Integer;
|
||||
Buffer, cbuffer, data : Widestring;
|
||||
blobfilename, extension : WideString;
|
||||
bf : Textfile;
|
||||
header, attribs : WideString;
|
||||
tofile : Boolean;
|
||||
i: Integer;
|
||||
tmp, Data, Attribs, Generator: WideString;
|
||||
Node: PVirtualNode;
|
||||
begin
|
||||
tofile := filename <> '';
|
||||
try
|
||||
buffer := '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' + crlf + crlf +
|
||||
'<html>' + crlf +
|
||||
'<head>' + crlf +
|
||||
' <title>' + htmltitle + '</title>' + crlf +
|
||||
' <meta name="GENERATOR" content="'+ Generator + '">' + crlf +
|
||||
' <style type="text/css">' + crlf +
|
||||
' tr#header {background-color: ActiveCaption; color: CaptionText;}' + crlf +
|
||||
' th, td {vertical-align: top; font-family: "'+Mainform.Childwin.DataGrid.Font.Name+'"; font-size: '+IntToStr(Mainform.Childwin.DataGrid.Font.Size)+'pt; padding: 0.5em; }' + crlf +
|
||||
' table, td {border: 1px solid silver;}' + crlf +
|
||||
' table {border-collapse: collapse;}' + crlf +
|
||||
' td.isnull {background-color: '+TColorToHex(COLOR_NULLVALUE) +'}' + crlf +
|
||||
' td.pk {background-color: #EEEEEE; font-weight: bold;}' + crlf +
|
||||
' </style>' + crlf +
|
||||
'</head>' + crlf + crlf +
|
||||
'<body>' + crlf + crlf +
|
||||
'<h3>' + htmltitle + ' (' + inttostr(Length(ds.Rows)) + ' rows)</h3>' + crlf + crlf +
|
||||
'<table >' + crlf +
|
||||
' <tr id="header">' + crlf;
|
||||
for j:=0 to Length(ds.Columns)-1 do
|
||||
buffer := buffer + ' <th>' + ds.Columns[j].Name + '</th>' + crlf;
|
||||
buffer := buffer + ' </tr>' + crlf;
|
||||
|
||||
cbuffer := buffer;
|
||||
|
||||
for I := 0 to Length(ds.Rows)-1 do
|
||||
begin
|
||||
Buffer := ' <tr>' + crlf;
|
||||
// collect data:
|
||||
for j:=0 to Length(ds.Rows[i].Cells)-1 do
|
||||
begin
|
||||
data := ds.Rows[i].Cells[j].Text;
|
||||
if tofile and ds.Columns[j].IsBlob then begin
|
||||
header := copy(data, 0, 20);
|
||||
extension := '';
|
||||
if pos('JFIF', header) <> 0 then
|
||||
extension := 'jpg'
|
||||
else if StrCmpBegin('GIF', header) then
|
||||
extension := 'gif'
|
||||
else if StrCmpBegin('BM', header) then
|
||||
extension := 'bmp';
|
||||
if extension <> '' then begin
|
||||
blobfilename := 'rec'+inttostr(i)+'fld'+inttostr(j)+'.'+extension;
|
||||
AssignFile(bf, blobfilename);
|
||||
Rewrite(bf);
|
||||
Write(bf, data);
|
||||
CloseFile(bf);
|
||||
data := '<a href="'+blobfilename+'"><img border="0" src="'+blobfilename+'" alt="'+blobfilename+' ('+floattostr(length(data) div 1024)+' KB)" width="100" /></a>';
|
||||
end else begin
|
||||
if ConvertHTMLEntities then data := htmlentities(data);
|
||||
data := WideStringReplace(data, #10, #10+'<br>', [rfReplaceAll]);
|
||||
end;
|
||||
end else begin
|
||||
if ConvertHTMLEntities then
|
||||
data := htmlentities(data);
|
||||
data := WideStringReplace(data, #10, #10+'<br>', [rfReplaceAll]);
|
||||
end;
|
||||
if ds.Rows[i].Cells[j].IsNull then
|
||||
attribs := ' class="isnull"'
|
||||
else begin
|
||||
// Primary key field
|
||||
attribs := '';
|
||||
if ds.Columns[j].IsPriPart then
|
||||
attribs := ' class="pk"';
|
||||
end;
|
||||
Buffer := Buffer + ' <td'+attribs+'>' + data + '</td>' + crlf;
|
||||
end;
|
||||
buffer := buffer + ' </tr>' + crlf;
|
||||
cbuffer := cbuffer + buffer;
|
||||
end;
|
||||
// footer:
|
||||
buffer := '</table>' + crlf + crlf + '<p>' + crlf +
|
||||
'<em>generated ' + datetostr(now) + ' ' + timetostr(now) +
|
||||
' by <a href="'+APPDOMAIN+'">' + Generator + '</a></em></p>' + crlf + crlf +
|
||||
'</body></html>';
|
||||
cbuffer := cbuffer + buffer;
|
||||
if tofile then
|
||||
SaveUnicodeFile(filename, cbuffer)
|
||||
else
|
||||
CopyToClipboard(cbuffer);
|
||||
result := true;
|
||||
except
|
||||
on e: Exception do begin
|
||||
MessageDlg(e.Message, mtError, [mbOK], 0);
|
||||
result := false;
|
||||
end;
|
||||
Generator := APPNAME+' '+FullAppVersion;
|
||||
tmp := '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' + CRLF +
|
||||
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' + CRLF + CRLF +
|
||||
'<html>' + CRLF +
|
||||
'<head>' + CRLF +
|
||||
' <title>' + Title + '</title>' + CRLF +
|
||||
' <meta name="GENERATOR" content="'+ Generator + '">' + CRLF +
|
||||
' <style type="text/css">' + CRLF +
|
||||
' tr#header {background-color: ActiveCaption; color: CaptionText;}' + CRLF +
|
||||
' th, td {vertical-align: top; font-family: "'+Grid.Font.Name+'"; font-size: '+IntToStr(Grid.Font.Size)+'pt; padding: '+IntToStr(Grid.TextMargin-1)+'px; }' + CRLF +
|
||||
' table, td {border: 1px solid silver;}' + CRLF +
|
||||
' table {border-collapse: collapse;}' + CRLF +
|
||||
' </style>' + CRLF +
|
||||
'</head>' + CRLF + CRLF +
|
||||
'<body>' + CRLF + CRLF +
|
||||
'<table caption="' + Title + ' (' + inttostr(Grid.RootNodeCount) + ' rows)">' + CRLF +
|
||||
' <tr id="header">' + CRLF;
|
||||
for i:=0 to Grid.Header.Columns.Count-1 do begin
|
||||
if not (coVisible in Grid.Header.Columns[i].Options) then
|
||||
continue;
|
||||
tmp := tmp + ' <th style="width:'+IntToStr(Grid.Header.Columns[i].Width)+'px">' + Grid.Header.Columns[i].Text + '</th>' + CRLF;
|
||||
end;
|
||||
Screen.Cursor := crDefault;
|
||||
// open file:
|
||||
if tofile and FileExists(filename) then
|
||||
ShellExec( filename );
|
||||
tmp := tmp + ' </tr>' + CRLF;
|
||||
S.Write(tmp[1], Length(tmp) * Sizeof(WideChar));
|
||||
|
||||
Node := Grid.GetFirst;
|
||||
while Assigned(Node) do begin
|
||||
if (Node.Index+1) mod 100 = 0 then
|
||||
ExportStatusMsg(Node, Grid.RootNodeCount, S.Size);
|
||||
tmp := ' <tr>' + CRLF;
|
||||
// collect data:
|
||||
for i:=0 to Grid.Header.Columns.Count-1 do begin
|
||||
// Skip hidden key columns
|
||||
if not (coVisible in Grid.Header.Columns[i].Options) then
|
||||
Continue;
|
||||
Data := Grid.Text[Node, i];
|
||||
if ConvertHTMLEntities then
|
||||
Data := htmlentities(Data);
|
||||
Data := WideStringReplace(Data, #10, #10+'<br>', [rfReplaceAll]);
|
||||
if Grid.Header.Columns[i].Alignment = taRightJustify then
|
||||
Attribs := ' style="text-align: right;"'
|
||||
else
|
||||
Attribs := '';
|
||||
tmp := tmp + ' <td'+Attribs+'>' + Data + '</td>' + CRLF;
|
||||
end;
|
||||
tmp := tmp + ' </tr>' + CRLF;
|
||||
S.Write(tmp[1], Length(tmp) * Sizeof(WideChar));
|
||||
Node := Grid.GetNext(Node);
|
||||
end;
|
||||
// footer:
|
||||
tmp := '</table>' + CRLF + CRLF + '<p>' + CRLF +
|
||||
'<em>generated ' + DateToStr(now) + ' ' + TimeToStr(now) +
|
||||
' by <a href="'+APPDOMAIN+'">' + Generator + '</a></em></p>' + CRLF + CRLF +
|
||||
'</body></html>';
|
||||
S.Write(tmp[1], Length(tmp) * Sizeof(WideChar));
|
||||
Mainform.Showstatus(STATUS_MSG_READY);
|
||||
end;
|
||||
|
||||
|
||||
|
||||
{***
|
||||
Converts a TDataSet to CSV-values.
|
||||
If a filename is given, save CSV-data to disk, otherwise copy content to clipboard
|
||||
|
||||
@param TDataSet Object which holds data to export
|
||||
@param Grid Object which holds data to export
|
||||
@param string Field-separator
|
||||
@param string Field-encloser
|
||||
@param string Line-terminator
|
||||
@param string Filename to use for saving. If not given, copy to clipboard.
|
||||
@return boolean True on access, False in case of any error
|
||||
}
|
||||
function dataset2csv(ds: TGridResult; Separator, Encloser, Terminator: String; filename: String = ''): Boolean;
|
||||
procedure GridToCsv(Grid: TVirtualStringTree; Separator, Encloser, Terminator: String; S: TStream);
|
||||
var
|
||||
I, J : Integer;
|
||||
Buffer, cbuffer, val : WideString;
|
||||
tofile : Boolean;
|
||||
i: Integer;
|
||||
tmp, Data: WideString;
|
||||
Node: PVirtualNode;
|
||||
begin
|
||||
separator := esc2ascii(separator);
|
||||
encloser := esc2ascii(encloser);
|
||||
terminator := esc2ascii(terminator);
|
||||
tofile := filename <> '';
|
||||
|
||||
try
|
||||
Buffer := '';
|
||||
// collect fields:
|
||||
for j:=0 to Length(ds.Columns)-1 do begin
|
||||
if j > 0 then
|
||||
Buffer := Buffer + Separator;
|
||||
Buffer := Buffer + Encloser + ds.Columns[J].Name + Encloser;
|
||||
end;
|
||||
cbuffer := cbuffer + buffer;
|
||||
|
||||
// collect data:
|
||||
for i:=0 to Length(ds.Rows)-1 do
|
||||
begin
|
||||
Buffer := '';
|
||||
Buffer := Buffer + Terminator;
|
||||
for j:=0 to Length(ds.Rows[i].Cells)-1 do
|
||||
begin
|
||||
if j>0 then
|
||||
Buffer := Buffer + Separator;
|
||||
val := ds.Rows[i].Cells[j].Text;
|
||||
if ds.Columns[j].IsFloat then val := FloatStr(val);
|
||||
Buffer := Buffer + Encloser + val + Encloser;
|
||||
end;
|
||||
// write buffer:
|
||||
cbuffer := cbuffer + buffer;
|
||||
end;
|
||||
if tofile then
|
||||
SaveUnicodeFile(filename, cbuffer)
|
||||
else
|
||||
CopyToClipboard(cbuffer);
|
||||
result := true;
|
||||
except
|
||||
on e: Exception do begin
|
||||
MessageDlg(e.Message, mtError, [mbOK], 0);
|
||||
result := false;
|
||||
end;
|
||||
tmp := '';
|
||||
// Columns
|
||||
for i:=0 to Grid.Header.Columns.Count-1 do begin
|
||||
// Skip hidden key columns
|
||||
if not (coVisible in Grid.Header.Columns[i].Options) then
|
||||
Continue;
|
||||
if tmp <> '' then
|
||||
tmp := tmp + Separator;
|
||||
tmp := tmp + Encloser + Grid.Header.Columns[i].Text + Encloser;
|
||||
end;
|
||||
Screen.Cursor := crDefault;
|
||||
S.Write(tmp[1], Length(tmp) * Sizeof(WideChar));
|
||||
|
||||
// Data:
|
||||
Node := Grid.GetFirst;
|
||||
while Assigned(Node) do begin
|
||||
if (Node.Index+1) mod 100 = 0 then
|
||||
ExportStatusMsg(Node, Grid.RootNodeCount, S.Size);
|
||||
tmp := Terminator;
|
||||
for i:=0 to Grid.Header.Columns.Count-1 do begin
|
||||
// Skip hidden key columns
|
||||
if not (coVisible in Grid.Header.Columns[i].Options) then
|
||||
Continue;
|
||||
Data := Grid.Text[Node, i];
|
||||
// Unformat float values
|
||||
if Grid.Header.Columns[i].Alignment = taRightJustify then
|
||||
Data := FloatStr(Data);
|
||||
tmp := tmp + Encloser + Data + Encloser + Separator;
|
||||
end;
|
||||
S.Write(tmp[1], Length(tmp) * Sizeof(WideChar));
|
||||
Node := Grid.GetNext(Node);
|
||||
end;
|
||||
Mainform.showstatus(STATUS_MSG_READY);
|
||||
end;
|
||||
|
||||
|
||||
|
||||
{***
|
||||
Converts a TDataSet to XML.
|
||||
If a filename is given, save XML to disk, otherwise copy content to clipboard
|
||||
|
||||
@param TDataSet Object which holds data to export
|
||||
@param Grid Object which holds data to export
|
||||
@param string Text used as root-element
|
||||
@param string Filename to use for saving. If not given, copy to clipboard.
|
||||
@return boolean True on access, False in case of any error
|
||||
}
|
||||
function dataset2xml(ds: TGridResult; title: WideString; filename: String = ''): Boolean;
|
||||
procedure GridToXml(Grid: TVirtualStringTree; root: WideString; S: TStream);
|
||||
var
|
||||
I, J : Integer;
|
||||
Buffer, cbuffer, data : WideString;
|
||||
tofile : Boolean;
|
||||
i: Integer;
|
||||
tmp, Data: WideString;
|
||||
Node: PVirtualNode;
|
||||
begin
|
||||
try
|
||||
tofile := filename <> '';
|
||||
buffer := '<?xml version="1.0"?>' + crlf + crlf +
|
||||
'<'+title+'>' + crlf;
|
||||
cbuffer := buffer;
|
||||
tmp := '<?xml version="1.0"?>' + CRLF + CRLF +
|
||||
'<'+root+'>' + CRLF;
|
||||
S.Write(tmp[1], Length(tmp) * Sizeof(WideChar));
|
||||
|
||||
for i:=0 to Length(ds.Rows)-1 do
|
||||
begin
|
||||
Buffer := #9'<row>' + crlf;
|
||||
// collect data:
|
||||
for j:=0 to Length(ds.Columns)-1 do
|
||||
begin
|
||||
data := ds.Rows[i].Cells[j].Text;
|
||||
data := htmlentities(data);
|
||||
Buffer := Buffer + #9#9'<'+ds.Columns[j].Name+'>' + data + '</'+ds.Columns[j].Name+'>' + crlf;
|
||||
end;
|
||||
buffer := buffer + #9'</row>' + crlf;
|
||||
cbuffer := cbuffer + buffer;
|
||||
end;
|
||||
// footer:
|
||||
cbuffer := cbuffer + '</'+title+'>' + crlf;
|
||||
if tofile then
|
||||
SaveUnicodeFile(filename, cbuffer)
|
||||
else
|
||||
CopyToClipboard(cbuffer);
|
||||
result := true;
|
||||
except
|
||||
on e: Exception do begin
|
||||
MessageDlg(e.Message, mtError, [mbOK], 0);
|
||||
result := false;
|
||||
Node := Grid.GetFirst;
|
||||
while Assigned(Node) do begin
|
||||
if (Node.Index+1) mod 100 = 0 then
|
||||
ExportStatusMsg(Node, Grid.RootNodeCount, S.Size);
|
||||
tmp := #9'<row>' + CRLF;
|
||||
// Data:
|
||||
for i:=0 to Grid.Header.Columns.Count-1 do begin
|
||||
// Skip hidden key columns
|
||||
if not (coVisible in Grid.Header.Columns[i].Options) then
|
||||
Continue;
|
||||
Data := Grid.Text[Node, i];
|
||||
// Unformat float values
|
||||
if Grid.Header.Columns[i].Alignment = taRightJustify then
|
||||
Data := FloatStr(Data)
|
||||
else
|
||||
Data := htmlentities(Data);
|
||||
tmp := tmp + #9#9'<'+Grid.Header.Columns[i].Text+'>' + Data + '</'+Grid.Header.Columns[i].Text+'>' + CRLF;
|
||||
end;
|
||||
tmp := tmp + #9'</row>' + CRLF;
|
||||
S.Write(tmp[1], Length(tmp) * Sizeof(WideChar));
|
||||
Node := Grid.GetNext(Node);
|
||||
end;
|
||||
Screen.Cursor := crDefault;
|
||||
// footer:
|
||||
tmp := '</'+root+'>' + CRLF;
|
||||
S.Write(tmp[1], Length(tmp) * Sizeof(WideChar));
|
||||
Mainform.showstatus(STATUS_MSG_READY);
|
||||
end;
|
||||
|
||||
|
||||
@ -2283,17 +2230,21 @@ end;
|
||||
procedure SaveUnicodeFile(Filename: String; Text: WideString);
|
||||
var
|
||||
f: TFileStream;
|
||||
begin
|
||||
f := CreateUnicodeFileStream(Filename);
|
||||
f.WriteBuffer(Pointer(Text)^, Length(Text) * 2);
|
||||
f.Free;
|
||||
end;
|
||||
|
||||
|
||||
function CreateUnicodeFileStream(Filename: String): TFileStream;
|
||||
var
|
||||
header: array[0..1] of Byte;
|
||||
begin
|
||||
header[0] := $FF;
|
||||
header[1] := $FE;
|
||||
f := TFileStream.Create(Filename, fmCreate or fmOpenWrite);
|
||||
try
|
||||
f.WriteBuffer(header, 2);
|
||||
f.WriteBuffer(Pointer(Text)^, Length(Text) * 2);
|
||||
finally
|
||||
f.Free;
|
||||
end;
|
||||
Result := TFileStream.Create(Filename, fmCreate or fmOpenWrite);
|
||||
Result.WriteBuffer(header, 2);
|
||||
end;
|
||||
|
||||
|
||||
@ -2545,6 +2496,20 @@ begin
|
||||
CB.AsWideString := Value;
|
||||
end;
|
||||
|
||||
|
||||
procedure StreamToClipboard(S: TMemoryStream);
|
||||
var
|
||||
Content: WideString;
|
||||
begin
|
||||
SetLength(Content, S.Size);
|
||||
S.Position := 0;
|
||||
S.Read(Pointer(Content)^, S.Size);
|
||||
CopyToClipboard(Content);
|
||||
// Free memory
|
||||
SetString(Content, nil, 0);
|
||||
end;
|
||||
|
||||
|
||||
initialization
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user