From 694a99f13ad42a2e6eeb3b95f57e6fadc439c42c Mon Sep 17 00:00:00 2001 From: Ansgar Becker Date: Fri, 31 Aug 2012 09:54:01 +0000 Subject: [PATCH] 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 --- source/exportgrid.dfm | 31 ++++++++------ source/exportgrid.pas | 99 ++++++++++++++++++++++++++++--------------- source/helpers.pas | 12 ++---- 3 files changed, 86 insertions(+), 56 deletions(-) diff --git a/source/exportgrid.dfm b/source/exportgrid.dfm index ed033107..c5920f12 100644 --- a/source/exportgrid.dfm +++ b/source/exportgrid.dfm @@ -48,7 +48,7 @@ object frmExportGrid: TfrmExportGrid object grpFormat: TRadioGroup Left = 8 Top = 112 - Width = 161 + Width = 137 Height = 252 Anchors = [akLeft, akTop, akBottom] Caption = 'Output format' @@ -64,12 +64,12 @@ object frmExportGrid: TfrmExportGrid 'Wiki markup' 'PHP Array') TabOrder = 2 - OnClick = ValidateControls + OnClick = grpFormatClick end object grpSelection: TRadioGroup - Left = 175 + Left = 151 Top = 112 - Width = 200 + Width = 224 Height = 66 Anchors = [akLeft, akTop, akRight] Caption = 'Row selection' @@ -148,15 +148,15 @@ object frmExportGrid: TfrmExportGrid end end object grpOptions: TGroupBox - Left = 175 + Left = 151 Top = 184 - Width = 200 + Width = 224 Height = 180 Anchors = [akLeft, akTop, akRight, akBottom] Caption = 'Options' TabOrder = 5 DesignSize = ( - 200 + 224 180) object lblSeparator: TLabel Left = 6 @@ -179,13 +179,13 @@ object frmExportGrid: TfrmExportGrid Height = 13 Caption = 'Line terminator:' end - object chkColumnHeader: TCheckBox + object chkIncludeColumnNames: TCheckBox Left = 8 Top = 18 - Width = 177 + Width = 201 Height = 17 Anchors = [akLeft, akTop, akRight] - Caption = 'Column names in first row' + Caption = 'Include column names' Checked = True State = cbChecked TabOrder = 0 @@ -193,8 +193,9 @@ object frmExportGrid: TfrmExportGrid object editSeparator: TButtonedEdit Left = 106 Top = 93 - Width = 80 + Width = 103 Height = 21 + Anchors = [akLeft, akTop, akRight] Images = MainForm.ImageListMain RightButton.DisabledImageIndex = 107 RightButton.ImageIndex = 108 @@ -207,8 +208,9 @@ object frmExportGrid: TfrmExportGrid object editEncloser: TButtonedEdit Left = 106 Top = 119 - Width = 80 + Width = 103 Height = 21 + Anchors = [akLeft, akTop, akRight] Images = MainForm.ImageListMain RightButton.DisabledImageIndex = 107 RightButton.ImageIndex = 108 @@ -220,8 +222,9 @@ object frmExportGrid: TfrmExportGrid object editTerminator: TButtonedEdit Left = 106 Top = 145 - Width = 80 + Width = 103 Height = 21 + Anchors = [akLeft, akTop, akRight] Images = MainForm.ImageListMain RightButton.DisabledImageIndex = 107 RightButton.ImageIndex = 108 @@ -234,7 +237,7 @@ object frmExportGrid: TfrmExportGrid object chkIncludeAutoIncrement: TCheckBox Left = 8 Top = 41 - Width = 177 + Width = 201 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Include auto increment column' diff --git a/source/exportgrid.pas b/source/exportgrid.pas index 27c621e9..9d2122f9 100644 --- a/source/exportgrid.pas +++ b/source/exportgrid.pas @@ -19,7 +19,7 @@ type radioOutputFile: TRadioButton; editFilename: TButtonedEdit; grpOptions: TGroupBox; - chkColumnHeader: TCheckBox; + chkIncludeColumnNames: TCheckBox; editSeparator: TButtonedEdit; editEncloser: TButtonedEdit; editTerminator: TButtonedEdit; @@ -60,12 +60,15 @@ type procedure ValidateControls(Sender: TObject); procedure btnOKClick(Sender: TObject); procedure FormShow(Sender: TObject); + procedure grpFormatClick(Sender: TObject); private { Private declarations } FCSVEditor: TButtonedEdit; FCSVSeparator, FCSVEncloser, FCSVTerminator: String; FGrid: TVirtualStringTree; FRecentFiles: TStringList; + const FormatToFileExtension: Array[TGridExportFormat] of String = + (('csv'), ('csv'), ('html'), ('xml'), ('sql'), ('sql'), ('LaTeX'), ('wiki'), ('php')); procedure SaveDialogTypeChange(Sender: TObject); function GetExportFormat: TGridExportFormat; procedure SetExportFormat(Value: TGridExportFormat); @@ -97,7 +100,7 @@ begin comboEncoding.ItemIndex := AppSettings.ReadInt(asGridExportEncoding); grpFormat.ItemIndex := AppSettings.ReadInt(asGridExportFormat); grpSelection.ItemIndex := AppSettings.ReadInt(asGridExportSelection); - chkColumnHeader.Checked := AppSettings.ReadBool(asGridExportColumnNames); + chkIncludeColumnNames.Checked := AppSettings.ReadBool(asGridExportColumnNames); chkIncludeQuery.Checked := AppSettings.ReadBool(asGridExportIncludeQuery); FCSVSeparator := AppSettings.ReadString(asGridExportSeparator); FCSVEncloser := AppSettings.ReadString(asGridExportEncloser); @@ -117,7 +120,7 @@ begin AppSettings.WriteInt(asGridExportEncoding, comboEncoding.ItemIndex); AppSettings.WriteInt(asGridExportFormat, grpFormat.ItemIndex); AppSettings.WriteInt(asGridExportSelection, grpSelection.ItemIndex); - AppSettings.WriteBool(asGridExportColumnNames, chkColumnHeader.Checked); + AppSettings.WriteBool(asGridExportColumnNames, chkIncludeColumnNames.Checked); AppSettings.WriteBool(asGridExportIncludeAutoInc, chkIncludeAutoIncrement.Checked); AppSettings.WriteBool(asGridExportIncludeQuery, chkIncludeQuery.Checked); AppSettings.WriteString(asGridExportSeparator, FCSVSeparator); @@ -172,8 +175,7 @@ begin end; end; - chkColumnHeader.Enabled := ExportFormat <> efXML; - chkIncludeQuery.Enabled := ExportFormat = efHTML; + chkIncludeQuery.Enabled := ExportFormat in [efHTML, efXML]; Enable := ExportFormat = efCSV; lblSeparator.Enabled := Enable; editSeparator.Enabled := Enable; @@ -191,6 +193,8 @@ begin editFilename.Font.Color := clGrayText; comboEncoding.Enabled := radioOutputFile.Checked; lblEncoding.Enabled := radioOutputFile.Checked; + if ExportFormat = efExcel then + comboEncoding.ItemIndex := comboEncoding.Items.IndexOf('ANSI'); end; @@ -206,20 +210,39 @@ begin 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; var ext: String; + efrm: TGridExportFormat; begin // Set format by file extension ext := LowerCase(Copy(ExtractFileExt(editFilename.Text), 2, 10)); - if (ext = 'csv') and (not (ExportFormat in [efExcel, efCSV])) - then ExportFormat := efCSV - else if ext = 'html' then ExportFormat := efHTML - else if ext = 'xml' then ExportFormat := efXML - else if ext = 'sql' then ExportFormat := efSQLInsert - else if ext = 'latex' then ExportFormat := efLaTeX - else if ext = 'wiki' then ExportFormat := efWiki - else if ext = 'php' then ExportFormat := efPHPArray; + for efrm :=Low(TGridExportFormat) to High(TGridExportFormat) do begin + if ext = FormatToFileExtension[ExportFormat] then + break; + if ext = FormatToFileExtension[efrm] then begin + ExportFormat := efrm; + break; + end; + end; end; @@ -347,12 +370,12 @@ begin // Set default file-extension of saved file and options on the dialog to show Dialog := Sender as TSaveDialog; case Dialog.FilterIndex of - 1: Dialog.DefaultExt := 'csv'; - 2: Dialog.DefaultExt := 'html'; - 3: Dialog.DefaultExt := 'xml'; - 4: Dialog.DefaultExt := 'sql'; - 5: Dialog.DefaultExt := 'LaTeX'; - 6: Dialog.DefaultExt := 'wiki'; + 1: Dialog.DefaultExt := FormatToFileExtension[efCSV]; + 2: Dialog.DefaultExt := FormatToFileExtension[efHTML]; + 3: Dialog.DefaultExt := FormatToFileExtension[efXML]; + 4: Dialog.DefaultExt := FormatToFileExtension[efSQLInsert]; + 5: Dialog.DefaultExt := FormatToFileExtension[efLaTeX]; + 6: Dialog.DefaultExt := FormatToFileExtension[efWiki]; end; end; @@ -465,7 +488,7 @@ begin if chkIncludeQuery.Checked then Header := Header + '

' + GridData.SQL + '

' + CRLF + CRLF; Header := Header + ' ' + CRLF; - if chkColumnHeader.Checked then begin + if chkIncludeColumnNames.Checked then begin Header := Header + ' ' + CRLF + ' ' + CRLF; @@ -487,7 +510,7 @@ begin Separator := GridData.Connection.UnescapeString(editSeparator.Text); Encloser := GridData.Connection.UnescapeString(editEncloser.Text); Terminator := GridData.Connection.UnescapeString(editTerminator.Text); - if chkColumnHeader.Checked then begin + if chkIncludeColumnNames.Checked then begin Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin // Alter column name in header if data is not raw. @@ -507,8 +530,12 @@ begin end; efXML: begin - Header := '' + CRLF + CRLF + - '
' + CRLF; + // Imitate mysqldump's XML style + Header := '' + CRLF + CRLF; + if chkIncludeQuery.Checked then + Header := Header + '' + CRLF + else + Header := Header + '' + CRLF; end; efLaTeX: begin @@ -524,7 +551,7 @@ begin Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Header := Header + '}' + CRLF; - if chkColumnHeader.Checked then begin + if chkIncludeColumnNames.Checked then begin Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin if Col <> ExcludeCol then @@ -540,7 +567,7 @@ begin Separator := ' || '; Encloser := ''; Terminator := ' ||'+CRLF; - if chkColumnHeader.Checked then begin + if chkIncludeColumnNames.Checked then begin Header := '|| '; Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin @@ -587,7 +614,7 @@ begin else tmp := 'REPLACE'; tmp := tmp + ' INTO '+GridData.Connection.QuoteIdent(Tablename); - if chkColumnHeader.Checked then begin + if chkIncludeColumnNames.Checked then begin tmp := tmp + ' ('; Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin @@ -623,7 +650,7 @@ begin case ExportFormat of efHTML: begin // Escape HTML control characters in data. - Data := htmlentities(Data); + Data := HTMLSpecialChars(Data); tmp := tmp + ' ' + CRLF; end; @@ -639,13 +666,15 @@ begin efXML: begin // Print cell start tag. - tmp := tmp + #9#9'<' + Grid.Header.Columns[Col].Text; + tmp := tmp + #9#9'' + CRLF + tmp := tmp + ' xsi:nil="true" />' + CRLF else begin if (GridData.DataType(Col).Category in [dtcBinary, dtcSpatial]) and (not Mainform.actBlobAsText.Checked) then tmp := tmp + ' format="hex"'; - tmp := tmp + '>' + htmlentities(Data) + '' + CRLF; + tmp := tmp + '>' + HTMLSpecialChars(Data) + '' + CRLF; end; end; @@ -664,7 +693,7 @@ begin Data := 'NULL' else if not (GridData.DataType(Col).Category in [dtcInteger, dtcReal]) then Data := esc(Data); - if chkColumnHeader.Checked then + if chkIncludeColumnNames.Checked then tmp := tmp + #9#9 + '''' + Grid.Header.Columns[Col].Text + ''' => ' + Data + ','+CRLF else tmp := tmp + #9#9 + Data + ','+CRLF; @@ -711,8 +740,12 @@ begin ' ' + CRLF + '' + CRLF; end; - efXML: - tmp := '
' + Data + '
' + CRLF; + efXML: begin + if chkIncludeQuery.Checked then + tmp := '' + CRLF + else + tmp := '' + CRLF; + end; efLaTeX: tmp := '\end{tabular}' + CRLF; efPHPArray: begin diff --git a/source/helpers.pas b/source/helpers.pas index aab9e232..543b2f39 100644 --- a/source/helpers.pas +++ b/source/helpers.pas @@ -242,7 +242,7 @@ type function sstr(str: String; len: Integer) : String; function encrypt(str: String): String; function decrypt(str: String): String; - function htmlentities(str: String): String; + function HTMLSpecialChars(str: String): String; function BestTableName(Data: TDBQuery): String; function EncodeURL(const Src: String): String; procedure StreamWrite(S: TStream; Text: String = ''); @@ -477,15 +477,9 @@ begin end; - -{*** - Convert HTML-characters to their corresponding entities - - @param string Text used for search+replace - @return string Text with entities -} -function htmlentities(str: String) : String; +function HTMLSpecialChars(str: String) : String; begin + // Convert critical HTML-characters to entities. Used in grid export. result := StringReplace(str, '&', '&', [rfReplaceAll]); result := StringReplace(result, '<', '<', [rfReplaceAll]); result := StringReplace(result, '>', '>', [rfReplaceAll]);