Tweak grid export dialog with some minor enhancements:

- Auto select ANSI encoding for Excel output, see http://en.wikipedia.org/wiki/Comma-separated_values#Application_support
- Auto modify file extension when selecting format
- Support "Include query" checkbox in XML format, see http://www.heidisql.com/forum.php?t=10853#p11082
- Imitate mysqldump's XML style, see http://dev.mysql.com/doc/refman/5.5/en/mysqldump.html#option_mysqldump_xml
This commit is contained in:
Ansgar Becker
2012-08-31 09:54:01 +00:00
parent 17600be9c1
commit 694a99f13a
3 changed files with 86 additions and 56 deletions

View File

@ -48,7 +48,7 @@ object frmExportGrid: TfrmExportGrid
object grpFormat: TRadioGroup object grpFormat: TRadioGroup
Left = 8 Left = 8
Top = 112 Top = 112
Width = 161 Width = 137
Height = 252 Height = 252
Anchors = [akLeft, akTop, akBottom] Anchors = [akLeft, akTop, akBottom]
Caption = 'Output format' Caption = 'Output format'
@ -64,12 +64,12 @@ object frmExportGrid: TfrmExportGrid
'Wiki markup' 'Wiki markup'
'PHP Array') 'PHP Array')
TabOrder = 2 TabOrder = 2
OnClick = ValidateControls OnClick = grpFormatClick
end end
object grpSelection: TRadioGroup object grpSelection: TRadioGroup
Left = 175 Left = 151
Top = 112 Top = 112
Width = 200 Width = 224
Height = 66 Height = 66
Anchors = [akLeft, akTop, akRight] Anchors = [akLeft, akTop, akRight]
Caption = 'Row selection' Caption = 'Row selection'
@ -148,15 +148,15 @@ object frmExportGrid: TfrmExportGrid
end end
end end
object grpOptions: TGroupBox object grpOptions: TGroupBox
Left = 175 Left = 151
Top = 184 Top = 184
Width = 200 Width = 224
Height = 180 Height = 180
Anchors = [akLeft, akTop, akRight, akBottom] Anchors = [akLeft, akTop, akRight, akBottom]
Caption = 'Options' Caption = 'Options'
TabOrder = 5 TabOrder = 5
DesignSize = ( DesignSize = (
200 224
180) 180)
object lblSeparator: TLabel object lblSeparator: TLabel
Left = 6 Left = 6
@ -179,13 +179,13 @@ object frmExportGrid: TfrmExportGrid
Height = 13 Height = 13
Caption = 'Line terminator:' Caption = 'Line terminator:'
end end
object chkColumnHeader: TCheckBox object chkIncludeColumnNames: TCheckBox
Left = 8 Left = 8
Top = 18 Top = 18
Width = 177 Width = 201
Height = 17 Height = 17
Anchors = [akLeft, akTop, akRight] Anchors = [akLeft, akTop, akRight]
Caption = 'Column names in first row' Caption = 'Include column names'
Checked = True Checked = True
State = cbChecked State = cbChecked
TabOrder = 0 TabOrder = 0
@ -193,8 +193,9 @@ object frmExportGrid: TfrmExportGrid
object editSeparator: TButtonedEdit object editSeparator: TButtonedEdit
Left = 106 Left = 106
Top = 93 Top = 93
Width = 80 Width = 103
Height = 21 Height = 21
Anchors = [akLeft, akTop, akRight]
Images = MainForm.ImageListMain Images = MainForm.ImageListMain
RightButton.DisabledImageIndex = 107 RightButton.DisabledImageIndex = 107
RightButton.ImageIndex = 108 RightButton.ImageIndex = 108
@ -207,8 +208,9 @@ object frmExportGrid: TfrmExportGrid
object editEncloser: TButtonedEdit object editEncloser: TButtonedEdit
Left = 106 Left = 106
Top = 119 Top = 119
Width = 80 Width = 103
Height = 21 Height = 21
Anchors = [akLeft, akTop, akRight]
Images = MainForm.ImageListMain Images = MainForm.ImageListMain
RightButton.DisabledImageIndex = 107 RightButton.DisabledImageIndex = 107
RightButton.ImageIndex = 108 RightButton.ImageIndex = 108
@ -220,8 +222,9 @@ object frmExportGrid: TfrmExportGrid
object editTerminator: TButtonedEdit object editTerminator: TButtonedEdit
Left = 106 Left = 106
Top = 145 Top = 145
Width = 80 Width = 103
Height = 21 Height = 21
Anchors = [akLeft, akTop, akRight]
Images = MainForm.ImageListMain Images = MainForm.ImageListMain
RightButton.DisabledImageIndex = 107 RightButton.DisabledImageIndex = 107
RightButton.ImageIndex = 108 RightButton.ImageIndex = 108
@ -234,7 +237,7 @@ object frmExportGrid: TfrmExportGrid
object chkIncludeAutoIncrement: TCheckBox object chkIncludeAutoIncrement: TCheckBox
Left = 8 Left = 8
Top = 41 Top = 41
Width = 177 Width = 201
Height = 17 Height = 17
Anchors = [akLeft, akTop, akRight] Anchors = [akLeft, akTop, akRight]
Caption = 'Include auto increment column' Caption = 'Include auto increment column'

View File

@ -19,7 +19,7 @@ type
radioOutputFile: TRadioButton; radioOutputFile: TRadioButton;
editFilename: TButtonedEdit; editFilename: TButtonedEdit;
grpOptions: TGroupBox; grpOptions: TGroupBox;
chkColumnHeader: TCheckBox; chkIncludeColumnNames: TCheckBox;
editSeparator: TButtonedEdit; editSeparator: TButtonedEdit;
editEncloser: TButtonedEdit; editEncloser: TButtonedEdit;
editTerminator: TButtonedEdit; editTerminator: TButtonedEdit;
@ -60,12 +60,15 @@ type
procedure ValidateControls(Sender: TObject); procedure ValidateControls(Sender: TObject);
procedure btnOKClick(Sender: TObject); procedure btnOKClick(Sender: TObject);
procedure FormShow(Sender: TObject); procedure FormShow(Sender: TObject);
procedure grpFormatClick(Sender: TObject);
private private
{ Private declarations } { Private declarations }
FCSVEditor: TButtonedEdit; FCSVEditor: TButtonedEdit;
FCSVSeparator, FCSVEncloser, FCSVTerminator: String; FCSVSeparator, FCSVEncloser, FCSVTerminator: String;
FGrid: TVirtualStringTree; FGrid: TVirtualStringTree;
FRecentFiles: TStringList; FRecentFiles: TStringList;
const FormatToFileExtension: Array[TGridExportFormat] of String =
(('csv'), ('csv'), ('html'), ('xml'), ('sql'), ('sql'), ('LaTeX'), ('wiki'), ('php'));
procedure SaveDialogTypeChange(Sender: TObject); procedure SaveDialogTypeChange(Sender: TObject);
function GetExportFormat: TGridExportFormat; function GetExportFormat: TGridExportFormat;
procedure SetExportFormat(Value: TGridExportFormat); procedure SetExportFormat(Value: TGridExportFormat);
@ -97,7 +100,7 @@ begin
comboEncoding.ItemIndex := AppSettings.ReadInt(asGridExportEncoding); comboEncoding.ItemIndex := AppSettings.ReadInt(asGridExportEncoding);
grpFormat.ItemIndex := AppSettings.ReadInt(asGridExportFormat); grpFormat.ItemIndex := AppSettings.ReadInt(asGridExportFormat);
grpSelection.ItemIndex := AppSettings.ReadInt(asGridExportSelection); grpSelection.ItemIndex := AppSettings.ReadInt(asGridExportSelection);
chkColumnHeader.Checked := AppSettings.ReadBool(asGridExportColumnNames); chkIncludeColumnNames.Checked := AppSettings.ReadBool(asGridExportColumnNames);
chkIncludeQuery.Checked := AppSettings.ReadBool(asGridExportIncludeQuery); chkIncludeQuery.Checked := AppSettings.ReadBool(asGridExportIncludeQuery);
FCSVSeparator := AppSettings.ReadString(asGridExportSeparator); FCSVSeparator := AppSettings.ReadString(asGridExportSeparator);
FCSVEncloser := AppSettings.ReadString(asGridExportEncloser); FCSVEncloser := AppSettings.ReadString(asGridExportEncloser);
@ -117,7 +120,7 @@ begin
AppSettings.WriteInt(asGridExportEncoding, comboEncoding.ItemIndex); AppSettings.WriteInt(asGridExportEncoding, comboEncoding.ItemIndex);
AppSettings.WriteInt(asGridExportFormat, grpFormat.ItemIndex); AppSettings.WriteInt(asGridExportFormat, grpFormat.ItemIndex);
AppSettings.WriteInt(asGridExportSelection, grpSelection.ItemIndex); AppSettings.WriteInt(asGridExportSelection, grpSelection.ItemIndex);
AppSettings.WriteBool(asGridExportColumnNames, chkColumnHeader.Checked); AppSettings.WriteBool(asGridExportColumnNames, chkIncludeColumnNames.Checked);
AppSettings.WriteBool(asGridExportIncludeAutoInc, chkIncludeAutoIncrement.Checked); AppSettings.WriteBool(asGridExportIncludeAutoInc, chkIncludeAutoIncrement.Checked);
AppSettings.WriteBool(asGridExportIncludeQuery, chkIncludeQuery.Checked); AppSettings.WriteBool(asGridExportIncludeQuery, chkIncludeQuery.Checked);
AppSettings.WriteString(asGridExportSeparator, FCSVSeparator); AppSettings.WriteString(asGridExportSeparator, FCSVSeparator);
@ -172,8 +175,7 @@ begin
end; end;
end; end;
chkColumnHeader.Enabled := ExportFormat <> efXML; chkIncludeQuery.Enabled := ExportFormat in [efHTML, efXML];
chkIncludeQuery.Enabled := ExportFormat = efHTML;
Enable := ExportFormat = efCSV; Enable := ExportFormat = efCSV;
lblSeparator.Enabled := Enable; lblSeparator.Enabled := Enable;
editSeparator.Enabled := Enable; editSeparator.Enabled := Enable;
@ -191,6 +193,8 @@ begin
editFilename.Font.Color := clGrayText; editFilename.Font.Color := clGrayText;
comboEncoding.Enabled := radioOutputFile.Checked; comboEncoding.Enabled := radioOutputFile.Checked;
lblEncoding.Enabled := radioOutputFile.Checked; lblEncoding.Enabled := radioOutputFile.Checked;
if ExportFormat = efExcel then
comboEncoding.ItemIndex := comboEncoding.Items.IndexOf('ANSI');
end; end;
@ -206,20 +210,39 @@ begin
end; end;
procedure TfrmExportGrid.grpFormatClick(Sender: TObject);
var
Filename, Extension: String;
begin
// Auto-modify file extension when selecting export format
// Be careful about triggering editFilename.OnChange event, as we may have come here from that event!
if radioOutputFile.Checked then begin
Filename := editFilename.Text;
Extension := ExtractFileExt(Filename);
Filename := Copy(Filename, 1, Length(Filename)-Length(Extension)) + '.' + FormatToFileExtension[ExportFormat];
if CompareText(Filename, editFilename.Text) <> 0 then begin
editFilename.Text := Filename;
ValidateControls(Sender);
end;
end;
end;
procedure TfrmExportGrid.SetExportFormatByFilename; procedure TfrmExportGrid.SetExportFormatByFilename;
var var
ext: String; ext: String;
efrm: TGridExportFormat;
begin begin
// Set format by file extension // Set format by file extension
ext := LowerCase(Copy(ExtractFileExt(editFilename.Text), 2, 10)); ext := LowerCase(Copy(ExtractFileExt(editFilename.Text), 2, 10));
if (ext = 'csv') and (not (ExportFormat in [efExcel, efCSV])) for efrm :=Low(TGridExportFormat) to High(TGridExportFormat) do begin
then ExportFormat := efCSV if ext = FormatToFileExtension[ExportFormat] then
else if ext = 'html' then ExportFormat := efHTML break;
else if ext = 'xml' then ExportFormat := efXML if ext = FormatToFileExtension[efrm] then begin
else if ext = 'sql' then ExportFormat := efSQLInsert ExportFormat := efrm;
else if ext = 'latex' then ExportFormat := efLaTeX break;
else if ext = 'wiki' then ExportFormat := efWiki end;
else if ext = 'php' then ExportFormat := efPHPArray; end;
end; end;
@ -347,12 +370,12 @@ begin
// Set default file-extension of saved file and options on the dialog to show // Set default file-extension of saved file and options on the dialog to show
Dialog := Sender as TSaveDialog; Dialog := Sender as TSaveDialog;
case Dialog.FilterIndex of case Dialog.FilterIndex of
1: Dialog.DefaultExt := 'csv'; 1: Dialog.DefaultExt := FormatToFileExtension[efCSV];
2: Dialog.DefaultExt := 'html'; 2: Dialog.DefaultExt := FormatToFileExtension[efHTML];
3: Dialog.DefaultExt := 'xml'; 3: Dialog.DefaultExt := FormatToFileExtension[efXML];
4: Dialog.DefaultExt := 'sql'; 4: Dialog.DefaultExt := FormatToFileExtension[efSQLInsert];
5: Dialog.DefaultExt := 'LaTeX'; 5: Dialog.DefaultExt := FormatToFileExtension[efLaTeX];
6: Dialog.DefaultExt := 'wiki'; 6: Dialog.DefaultExt := FormatToFileExtension[efWiki];
end; end;
end; end;
@ -465,7 +488,7 @@ begin
if chkIncludeQuery.Checked then if chkIncludeQuery.Checked then
Header := Header + '<p style="font-family: monospace; white-space: pre;">' + GridData.SQL + '</p>' + CRLF + CRLF; Header := Header + '<p style="font-family: monospace; white-space: pre;">' + GridData.SQL + '</p>' + CRLF + CRLF;
Header := Header + ' <table caption="' + TableName + ' (' + inttostr(NodeCount) + ' rows)">' + CRLF; Header := Header + ' <table caption="' + TableName + ' (' + inttostr(NodeCount) + ' rows)">' + CRLF;
if chkColumnHeader.Checked then begin if chkIncludeColumnNames.Checked then begin
Header := Header + Header := Header +
' <thead>' + CRLF + ' <thead>' + CRLF +
' <tr>' + CRLF; ' <tr>' + CRLF;
@ -487,7 +510,7 @@ begin
Separator := GridData.Connection.UnescapeString(editSeparator.Text); Separator := GridData.Connection.UnescapeString(editSeparator.Text);
Encloser := GridData.Connection.UnescapeString(editEncloser.Text); Encloser := GridData.Connection.UnescapeString(editEncloser.Text);
Terminator := GridData.Connection.UnescapeString(editTerminator.Text); Terminator := GridData.Connection.UnescapeString(editTerminator.Text);
if chkColumnHeader.Checked then begin if chkIncludeColumnNames.Checked then begin
Col := Grid.Header.Columns.GetFirstVisibleColumn; Col := Grid.Header.Columns.GetFirstVisibleColumn;
while Col > NoColumn do begin while Col > NoColumn do begin
// Alter column name in header if data is not raw. // Alter column name in header if data is not raw.
@ -507,8 +530,12 @@ begin
end; end;
efXML: begin efXML: begin
Header := '<?xml version="1.0" encoding="'+MainForm.GetCharsetByEncoding(Encoding)+'"?>' + CRLF + CRLF + // Imitate mysqldump's XML style
'<table name="'+TableName+'">' + CRLF; Header := '<?xml version="1.0" encoding="'+MainForm.GetCharsetByEncoding(Encoding)+'"?>' + CRLF + CRLF;
if chkIncludeQuery.Checked then
Header := Header + '<resultset statement="'+HTMLSpecialChars(GridData.SQL)+'" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' + CRLF
else
Header := Header + '<table_data name="'+HTMLSpecialChars(TableName)+'">' + CRLF;
end; end;
efLaTeX: begin efLaTeX: begin
@ -524,7 +551,7 @@ begin
Col := Grid.Header.Columns.GetNextVisibleColumn(Col); Col := Grid.Header.Columns.GetNextVisibleColumn(Col);
end; end;
Header := Header + '}' + CRLF; Header := Header + '}' + CRLF;
if chkColumnHeader.Checked then begin if chkIncludeColumnNames.Checked then begin
Col := Grid.Header.Columns.GetFirstVisibleColumn; Col := Grid.Header.Columns.GetFirstVisibleColumn;
while Col > NoColumn do begin while Col > NoColumn do begin
if Col <> ExcludeCol then if Col <> ExcludeCol then
@ -540,7 +567,7 @@ begin
Separator := ' || '; Separator := ' || ';
Encloser := ''; Encloser := '';
Terminator := ' ||'+CRLF; Terminator := ' ||'+CRLF;
if chkColumnHeader.Checked then begin if chkIncludeColumnNames.Checked then begin
Header := '|| '; Header := '|| ';
Col := Grid.Header.Columns.GetFirstVisibleColumn; Col := Grid.Header.Columns.GetFirstVisibleColumn;
while Col > NoColumn do begin while Col > NoColumn do begin
@ -587,7 +614,7 @@ begin
else else
tmp := 'REPLACE'; tmp := 'REPLACE';
tmp := tmp + ' INTO '+GridData.Connection.QuoteIdent(Tablename); tmp := tmp + ' INTO '+GridData.Connection.QuoteIdent(Tablename);
if chkColumnHeader.Checked then begin if chkIncludeColumnNames.Checked then begin
tmp := tmp + ' ('; tmp := tmp + ' (';
Col := Grid.Header.Columns.GetFirstVisibleColumn; Col := Grid.Header.Columns.GetFirstVisibleColumn;
while Col > NoColumn do begin while Col > NoColumn do begin
@ -623,7 +650,7 @@ begin
case ExportFormat of case ExportFormat of
efHTML: begin efHTML: begin
// Escape HTML control characters in data. // Escape HTML control characters in data.
Data := htmlentities(Data); Data := HTMLSpecialChars(Data);
tmp := tmp + ' <td class="col' + IntToStr(Col) + '">' + Data + '</td>' + CRLF; tmp := tmp + ' <td class="col' + IntToStr(Col) + '">' + Data + '</td>' + CRLF;
end; end;
@ -639,13 +666,15 @@ begin
efXML: begin efXML: begin
// Print cell start tag. // Print cell start tag.
tmp := tmp + #9#9'<' + Grid.Header.Columns[Col].Text; tmp := tmp + #9#9'<field';
if chkIncludeColumnNames.Checked then
tmp := tmp + ' name="' + HTMLSpecialChars(Grid.Header.Columns[Col].Text) + '"';
if GridData.IsNull(Col) then if GridData.IsNull(Col) then
tmp := tmp + ' isnull="true" />' + CRLF tmp := tmp + ' xsi:nil="true" />' + CRLF
else begin else begin
if (GridData.DataType(Col).Category in [dtcBinary, dtcSpatial]) and (not Mainform.actBlobAsText.Checked) then if (GridData.DataType(Col).Category in [dtcBinary, dtcSpatial]) and (not Mainform.actBlobAsText.Checked) then
tmp := tmp + ' format="hex"'; tmp := tmp + ' format="hex"';
tmp := tmp + '>' + htmlentities(Data) + '</' + Grid.Header.Columns[Col].Text + '>' + CRLF; tmp := tmp + '>' + HTMLSpecialChars(Data) + '</field>' + CRLF;
end; end;
end; end;
@ -664,7 +693,7 @@ begin
Data := 'NULL' Data := 'NULL'
else if not (GridData.DataType(Col).Category in [dtcInteger, dtcReal]) then else if not (GridData.DataType(Col).Category in [dtcInteger, dtcReal]) then
Data := esc(Data); Data := esc(Data);
if chkColumnHeader.Checked then if chkIncludeColumnNames.Checked then
tmp := tmp + #9#9 + '''' + Grid.Header.Columns[Col].Text + ''' => ' + Data + ','+CRLF tmp := tmp + #9#9 + '''' + Grid.Header.Columns[Col].Text + ''' => ' + Data + ','+CRLF
else else
tmp := tmp + #9#9 + Data + ','+CRLF; tmp := tmp + #9#9 + Data + ','+CRLF;
@ -711,8 +740,12 @@ begin
' </body>' + CRLF + ' </body>' + CRLF +
'</html>' + CRLF; '</html>' + CRLF;
end; end;
efXML: efXML: begin
tmp := '</table>' + CRLF; if chkIncludeQuery.Checked then
tmp := '</resultset>' + CRLF
else
tmp := '</table_data>' + CRLF;
end;
efLaTeX: efLaTeX:
tmp := '\end{tabular}' + CRLF; tmp := '\end{tabular}' + CRLF;
efPHPArray: begin efPHPArray: begin

View File

@ -242,7 +242,7 @@ type
function sstr(str: String; len: Integer) : String; function sstr(str: String; len: Integer) : String;
function encrypt(str: String): String; function encrypt(str: String): String;
function decrypt(str: String): String; function decrypt(str: String): String;
function htmlentities(str: String): String; function HTMLSpecialChars(str: String): String;
function BestTableName(Data: TDBQuery): String; function BestTableName(Data: TDBQuery): String;
function EncodeURL(const Src: String): String; function EncodeURL(const Src: String): String;
procedure StreamWrite(S: TStream; Text: String = ''); procedure StreamWrite(S: TStream; Text: String = '');
@ -477,15 +477,9 @@ begin
end; end;
function HTMLSpecialChars(str: String) : String;
{***
Convert HTML-characters to their corresponding entities
@param string Text used for search+replace
@return string Text with entities
}
function htmlentities(str: String) : String;
begin begin
// Convert critical HTML-characters to entities. Used in grid export.
result := StringReplace(str, '&', '&amp;', [rfReplaceAll]); result := StringReplace(str, '&', '&amp;', [rfReplaceAll]);
result := StringReplace(result, '<', '&lt;', [rfReplaceAll]); result := StringReplace(result, '<', '&lt;', [rfReplaceAll]);
result := StringReplace(result, '>', '&gt;', [rfReplaceAll]); result := StringReplace(result, '>', '&gt;', [rfReplaceAll]);