unit exportgrid; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Menus, ComCtrls, VirtualTrees, SynExportHTML, gnugettext, ActnList, extra_controls; type TGridExportFormat = (efExcel, efCSV, efHTML, efXML, efSQLInsert, efSQLReplace, efSQLDeleteInsert, efLaTeX, efWiki, efPHPArray, efMarkDown, efJSON); TfrmExportGrid = class(TFormWithSizeGrip) btnOK: TButton; btnCancel: TButton; grpFormat: TRadioGroup; grpSelection: TRadioGroup; grpOutput: TGroupBox; radioOutputCopyToClipboard: TRadioButton; radioOutputFile: TRadioButton; editFilename: TButtonedEdit; grpOptions: TGroupBox; chkIncludeColumnNames: TCheckBox; editSeparator: TButtonedEdit; editEncloser: TButtonedEdit; editTerminator: TButtonedEdit; lblSeparator: TLabel; lblEncloser: TLabel; lblTerminator: TLabel; popupCSVchar: TPopupMenu; menuCSVtab: TMenuItem; menuCSVunixlinebreak: TMenuItem; menuCSVmaclinebreak: TMenuItem; menuCSVwinlinebreak: TMenuItem; menuCSVnul: TMenuItem; menuCSVbackspace: TMenuItem; menuCSVcontrolz: TMenuItem; comboEncoding: TComboBox; lblEncoding: TLabel; popupRecentFiles: TPopupMenu; menuCSVsinglequote: TMenuItem; menuCSVdoublequote: TMenuItem; menuCSVcomma: TMenuItem; menuCSVsemicolon: TMenuItem; N1: TMenuItem; N2: TMenuItem; N3: TMenuItem; chkIncludeAutoIncrement: TCheckBox; chkIncludeQuery: TCheckBox; lblNull: TLabel; editNull: TButtonedEdit; btnSetClipboardDefaults: TButton; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure CalcSize(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure editFilenameRightButtonClick(Sender: TObject); procedure editFilenameChange(Sender: TObject); procedure popupRecentFilesPopup(Sender: TObject); procedure menuCSVClick(Sender: TObject); procedure editCSVRightButtonClick(Sender: TObject); procedure editCSVChange(Sender: TObject); procedure ValidateControls(Sender: TObject); procedure btnOKClick(Sender: TObject); procedure FormShow(Sender: TObject); procedure grpFormatClick(Sender: TObject); procedure btnSetClipboardDefaultsClick(Sender: TObject); procedure FormResize(Sender: TObject); private { Private declarations } FCSVEditor: TButtonedEdit; FCSVSeparator, FCSVEncloser, FCSVTerminator, FCSVNull: String; FGrid: TVirtualStringTree; FRecentFiles: TStringList; FHiddenCopyMode: Boolean; const FFormatToFileExtension: Array[TGridExportFormat] of String = (('csv'), ('csv'), ('html'), ('xml'), ('sql'), ('sql'), ('sql'), ('LaTeX'), ('wiki'), ('php'), ('md'), ('json')); const FFormatToDescription: Array[TGridExportFormat] of String = (('Excel CSV'), ('Delimited text'), ('HTML table'), ('XML'), ('SQL INSERTs'), ('SQL REPLACEs'), ('SQL DELETEs/INSERTs'), ('LaTeX'), ('Wiki markup'), ('PHP Array'), ('Markdown Here'), ('JSON')); procedure SaveDialogTypeChange(Sender: TObject); function GetExportFormat: TGridExportFormat; procedure SetExportFormat(Value: TGridExportFormat); procedure SetExportFormatByFilename; procedure SelectRecentFile(Sender: TObject); procedure PutFilenamePlaceholder(Sender: TObject); function EscapePHP(Text: String): String; public { Public declarations } property Grid: TVirtualStringTree read FGrid write FGrid; property ExportFormat: TGridExportFormat read GetExportFormat write SetExportFormat; end; implementation uses main, apphelpers, dbconnection, dbstructures; {$R *.dfm} procedure TfrmExportGrid.FormCreate(Sender: TObject); var FormatDesc: String; begin TranslateComponent(Self); Width := AppSettings.ReadInt(asGridExportWindowWidth); Height := AppSettings.ReadInt(asGridExportWindowHeight); editFilename.Text := AppSettings.ReadString(asGridExportFilename); FRecentFiles := Explode(DELIM, AppSettings.ReadString(asGridExportRecentFiles)); comboEncoding.Items.Assign(MainForm.FileEncodings); comboEncoding.Items.Delete(0); // Remove "Auto detect" comboEncoding.ItemIndex := AppSettings.ReadInt(asGridExportEncoding); grpFormat.Items.Clear; for FormatDesc in FFormatToDescription do grpFormat.Items.Add(FormatDesc); FHiddenCopyMode := Owner = MainForm.actCopyRows; if FHiddenCopyMode then begin radioOutputCopyToClipboard.Checked := True; grpFormat.ItemIndex := AppSettings.ReadInt(asGridExportClpFormat); grpSelection.ItemIndex := 0; // Always use selected cells in copy mode chkIncludeColumnNames.Checked := AppSettings.ReadBool(asGridExportClpColumnNames); chkIncludeAutoIncrement.Checked := AppSettings.ReadBool(asGridExportClpIncludeAutoInc); chkIncludeQuery.Checked := False; // Always off in copy mode FCSVSeparator := AppSettings.ReadString(asGridExportClpSeparator); FCSVEncloser := AppSettings.ReadString(asGridExportClpEncloser); FCSVTerminator := AppSettings.ReadString(asGridExportClpTerminator); FCSVNull := AppSettings.ReadString(asGridExportClpNull); end else begin radioOutputCopyToClipboard.Checked := AppSettings.ReadBool(asGridExportOutputCopy); radioOutputFile.Checked := AppSettings.ReadBool(asGridExportOutputFile); grpFormat.ItemIndex := AppSettings.ReadInt(asGridExportFormat); grpSelection.ItemIndex := AppSettings.ReadInt(asGridExportSelection); chkIncludeColumnNames.Checked := AppSettings.ReadBool(asGridExportColumnNames); chkIncludeAutoIncrement.Checked := AppSettings.ReadBool(asGridExportIncludeAutoInc); chkIncludeQuery.Checked := AppSettings.ReadBool(asGridExportIncludeQuery); FCSVSeparator := AppSettings.ReadString(asGridExportSeparator); FCSVEncloser := AppSettings.ReadString(asGridExportEncloser); FCSVTerminator := AppSettings.ReadString(asGridExportTerminator); FCSVNull := AppSettings.ReadString(asGridExportNull); end; ValidateControls(Sender); end; procedure TfrmExportGrid.FormDestroy(Sender: TObject); begin // Store settings if not FHiddenCopyMode then begin AppSettings.WriteInt(asGridExportWindowWidth, Round(Width / DpiScaleFactor(Self))); AppSettings.WriteInt(asGridExportWindowHeight, Round(Height / DpiScaleFactor(Self))); if ModalResult = mrOK then begin AppSettings.WriteBool(asGridExportOutputCopy, radioOutputCopyToClipboard.Checked); AppSettings.WriteBool(asGridExportOutputFile, radioOutputFile.Checked); AppSettings.WriteString(asGridExportFilename, editFilename.Text); AppSettings.WriteString(asGridExportRecentFiles, ImplodeStr(DELIM, FRecentFiles)); AppSettings.WriteInt(asGridExportEncoding, comboEncoding.ItemIndex); AppSettings.WriteInt(asGridExportFormat, grpFormat.ItemIndex); AppSettings.WriteInt(asGridExportSelection, grpSelection.ItemIndex); AppSettings.WriteBool(asGridExportColumnNames, chkIncludeColumnNames.Checked); AppSettings.WriteBool(asGridExportIncludeAutoInc, chkIncludeAutoIncrement.Checked); AppSettings.WriteBool(asGridExportIncludeQuery, chkIncludeQuery.Checked); AppSettings.WriteString(asGridExportSeparator, FCSVSeparator); AppSettings.WriteString(asGridExportEncloser, FCSVEncloser); AppSettings.WriteString(asGridExportTerminator, FCSVTerminator); AppSettings.WriteString(asGridExportNull, FCSVNull); end; end; end; procedure TfrmExportGrid.FormResize(Sender: TObject); begin grpFormat.Width := Width div 3; grpSelection.Left := grpFormat.Left + grpFormat.Width + 8; grpSelection.Width := Width - grpSelection.Left - 24; grpOptions.Left := grpSelection.Left; grpOptions.Width := grpSelection.Width; end; procedure TfrmExportGrid.FormShow(Sender: TObject); begin // Show dialog. Expect "Grid" property to be set now by the caller. chkIncludeAutoIncrement.OnClick := CalcSize; CalcSize(Sender); end; procedure TfrmExportGrid.FormClose(Sender: TObject; var Action: TCloseAction); begin // Destroy dialog - not cached Action := caFree; end; procedure TfrmExportGrid.ValidateControls(Sender: TObject); var Enable: Boolean; begin // Display the actually used control characters, even if they cannot be changed case ExportFormat of efExcel: begin // Tab for pasting, semicolon if comma is also the decimal separator, and comma for the rest // see http://en.wikipedia.org/wiki/Comma-separated_values if radioOutputCopyToClipboard.Checked then editSeparator.Text := '\t' else if FormatSettings.DecimalSeparator=',' then editSeparator.Text := ';' else editSeparator.Text := ','; editEncloser.Text := '"'; editTerminator.Text := '\r\n'; editNull.Text := FCSVNull; end; efCSV: begin editSeparator.Text := FCSVSeparator; editEncloser.Text := FCSVEncloser; editTerminator.Text := FCSVTerminator; editNull.Text := FCSVNull; end; efMarkDown: editNull.Text := FCSVNull; else begin editSeparator.Text := ''; editEncloser.Text := ''; editTerminator.Text := ''; editNull.Text := ''; end; end; chkIncludeQuery.Enabled := ExportFormat in [efHTML, efXML, efMarkDown, efJSON]; Enable := ExportFormat = efCSV; lblSeparator.Enabled := Enable; editSeparator.Enabled := Enable; editSeparator.RightButton.Enabled := Enable; lblEncloser.Enabled := Enable; editEncloser.Enabled := Enable; editEncloser.RightButton.Enabled := Enable; lblTerminator.Enabled := Enable; editTerminator.Enabled := Enable; editTerminator.RightButton.Enabled := Enable; lblNull.Enabled := ExportFormat in [efExcel, efCSV, efMarkDown]; editNull.Enabled := lblNull.Enabled; editNull.RightButton.Enabled := lblNull.Enabled; btnOK.Enabled := radioOutputCopyToClipboard.Checked or (radioOutputFile.Checked and (editFilename.Text <> '')); if radioOutputFile.Checked then editFilename.Font.Color := GetThemeColor(clWindowText) else editFilename.Font.Color := GetThemeColor(clGrayText); comboEncoding.Enabled := radioOutputFile.Checked; lblEncoding.Enabled := radioOutputFile.Checked; if ExportFormat = efExcel then comboEncoding.ItemIndex := comboEncoding.Items.IndexOf('ANSI'); end; function TfrmExportGrid.GetExportFormat: TGridExportFormat; begin Result := TGridExportFormat(grpFormat.ItemIndex); end; procedure TfrmExportGrid.SetExportFormat(Value: TGridExportFormat); begin grpFormat.ItemIndex := Integer(Value); end; procedure TfrmExportGrid.grpFormatClick(Sender: TObject); var Filename: 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 := ExtractFilePath(editFilename.Text) + ExtractBaseFileName(editFilename.Text) + '.' + FFormatToFileExtension[ExportFormat]; if CompareText(Filename, editFilename.Text) <> 0 then editFilename.Text := Filename; end; ValidateControls(Sender); end; procedure TfrmExportGrid.SetExportFormatByFilename; var ext: String; efrm: TGridExportFormat; begin // Set format by file extension ext := LowerCase(Copy(ExtractFileExt(editFilename.Text), 2, 10)); for efrm :=Low(TGridExportFormat) to High(TGridExportFormat) do begin if ext = FFormatToFileExtension[ExportFormat] then break; if ext = FFormatToFileExtension[efrm] then begin ExportFormat := efrm; break; end; end; end; procedure TfrmExportGrid.editFilenameChange(Sender: TObject); begin radioOutputFile.Checked := True; end; procedure TfrmExportGrid.editFilenameRightButtonClick(Sender: TObject); var Dialog: TSaveDialog; ef: TGridExportFormat; Filename: String; begin // Select file target Dialog := TSaveDialog.Create(Self); Filename := GetOutputFilename(editFilename.Text, MainForm.ActiveDbObj); Dialog.InitialDir := ExtractFilePath(Filename); Dialog.FileName := ExtractBaseFileName(Filename); Dialog.Filter := ''; for ef:=Low(TGridExportFormat) to High(TGridExportFormat) do Dialog.Filter := Dialog.Filter + FFormatToDescription[ef] + ' (*.'+FFormatToFileExtension[ef]+')|*.'+FFormatToFileExtension[ef]+'|'; Dialog.Filter := Dialog.Filter + _('All files')+' (*.*)|*.*'; Dialog.OnTypeChange := SaveDialogTypeChange; Dialog.FilterIndex := grpFormat.ItemIndex+1; Dialog.OnTypeChange(Dialog); if Dialog.Execute then begin editFilename.Text := Dialog.FileName; SetExportFormatByFilename; end; Dialog.Free; end; procedure TfrmExportGrid.popupRecentFilesPopup(Sender: TObject); var Filename: String; Menu: TPopupMenu; Item: TMenuItem; Placeholders: TStringList; i: Integer; begin // Clear and populate drop down menu with recent files and filename placeholders Menu := Sender as TPopupMenu; Menu.Items.Clear; for Filename in FRecentFiles do begin Item := TMenuItem.Create(Menu); Menu.Items.Add(Item); Item.Caption := Filename; Item.Hint := Filename; Item.OnClick := SelectRecentFile; Item.Checked := Filename = editFilename.Text; end; Item := TMenuItem.Create(Menu); Menu.Items.Add(Item); Item.Caption := '-'; Placeholders := GetOutputFilenamePlaceholders; for i:=0 to Placeholders.Count-1 do begin Item := TMenuItem.Create(Menu); Menu.Items.Add(Item); Item.Caption := '%' + Placeholders.Names[i] + ': ' + Placeholders.ValueFromIndex[i]; Item.Hint := '%' + Placeholders.Names[i]; Item.OnClick := PutFilenamePlaceholder; end; Placeholders.Free; end; procedure TfrmExportGrid.SelectRecentFile(Sender: TObject); begin // Select file from recently used files editFilename.Text := (Sender as TMenuItem).Hint; SetExportFormatByFilename; end; procedure TfrmExportGrid.PutFilenamePlaceholder(Sender: TObject); begin // Put filename placeholder editFilename.SelText := (Sender as TMenuItem).Hint; end; procedure TfrmExportGrid.btnSetClipboardDefaultsClick(Sender: TObject); begin // Store copy-to-clipboard settings AppSettings.ResetPath; AppSettings.WriteInt(asGridExportClpFormat, grpFormat.ItemIndex); AppSettings.WriteBool(asGridExportClpColumnNames, chkIncludeColumnNames.Checked); AppSettings.WriteBool(asGridExportClpIncludeAutoInc, chkIncludeAutoIncrement.Checked); AppSettings.WriteString(asGridExportClpSeparator, FCSVSeparator); AppSettings.WriteString(asGridExportClpEncloser, FCSVEncloser); AppSettings.WriteString(asGridExportClpTerminator, FCSVTerminator); AppSettings.WriteString(asGridExportClpNull, FCSVNull); MessageDialog(_('Clipboard settings changed.'), mtInformation, [mbOK]); end; procedure TfrmExportGrid.CalcSize(Sender: TObject); var GridData: TDBQuery; Node: PVirtualNode; Col, ExcludeCol: TColumnIndex; RowNum: PInt64; SelectionSize, AllSize: Int64; begin GridData := Mainform.GridResult(Grid); AllSize := 0; SelectionSize := 0; chkIncludeAutoIncrement.Enabled := GridData.AutoIncrementColumn > -1; ExcludeCol := -1; if chkIncludeAutoIncrement.Enabled and (not chkIncludeAutoIncrement.Checked) then ExcludeCol := GridData.AutoIncrementColumn; Node := GetNextNode(Grid, nil, False); while Assigned(Node) do begin RowNum := Grid.GetNodeData(Node); GridData.RecNo := RowNum^; Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin if Col <> ExcludeCol then begin Inc(AllSize, GridData.ColumnLengths(Col)); if vsSelected in Node.States then Inc(SelectionSize, GridData.ColumnLengths(Col)); end; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Node := GetNextNode(Grid, Node, False); end; grpSelection.Items[0] := f_('Selection (%s rows, %s)', [FormatNumber(Grid.SelectedCount), FormatByteNumber(SelectionSize)]); grpSelection.Items[1] := f_('Complete (%s rows, %s)', [FormatNumber(Grid.RootNodeCount), FormatByteNumber(AllSize)]); end; procedure TfrmExportGrid.editCSVChange(Sender: TObject); var Edit: TButtonedEdit; begin // Remember csv settings Edit := Sender as TButtonedEdit; case ExportFormat of efExcel, efMarkDown: begin if Edit = editNull then FCSVNull := Edit.Text; end; efCSV: begin if Edit = editSeparator then FCSVSeparator := Edit.Text else if Edit = editEncloser then FCSVEncloser := Edit.Text else if Edit = editTerminator then FCSVTerminator := Edit.Text else if Edit = editNull then FCSVNull := Edit.Text; end; end; end; procedure TfrmExportGrid.SaveDialogTypeChange(Sender: TObject); var Dialog: TSaveDialog; ef: TGridExportFormat; begin // Set default file-extension of saved file and options on the dialog to show Dialog := Sender as TSaveDialog; for ef:=Low(TGridExportFormat) to High(TGridExportFormat) do begin if Dialog.FilterIndex = Integer(ef)+1 then Dialog.DefaultExt := FFormatToFileExtension[ef]; end; end; procedure TfrmExportGrid.editCSVRightButtonClick(Sender: TObject); var p: TPoint; Item: TMenuItem; begin // Remember editor and prepare popup menu items FCSVEditor := Sender as TButtonedEdit; p := FCSVEditor.ClientToScreen(FCSVEditor.ClientRect.BottomRight); for Item in popupCSVchar.Items do begin Item.Checked := FCSVEditor.Text = Item.Hint; end; popupCSVchar.Popup(p.X-16, p.Y); end; procedure TfrmExportGrid.menuCSVClick(Sender: TObject); begin // Insert char from menu FCSVEditor.Text := TMenuItem(Sender).Hint; end; function TfrmExportGrid.EscapePHP(Text: String): String; begin // String escaping for PHP output. Incompatible to TDBConnection.EscapeString. Result := StringReplace(Text, '\', '\\', [rfReplaceAll]); Result := StringReplace(Result, #13, '\r', [rfReplaceAll]); Result := StringReplace(Result, #10, '\n', [rfReplaceAll]); Result := StringReplace(Result, #9, '\t', [rfReplaceAll]); Result := StringReplace(Result, '"', '\"', [rfReplaceAll]); Result := '"' + Result + '"'; end; procedure TfrmExportGrid.btnOKClick(Sender: TObject); var Col, ExcludeCol: TColumnIndex; Header, Data, tmp, Encloser, Separator, Terminator, TableName, Filename: String; Node: PVirtualNode; GridData: TDBQuery; SelectionOnly: Boolean; i: Integer; NodeCount: Cardinal; RowNum: PInt64; HTML: TStream; S: TStringStream; Exporter: TSynExporterHTML; Encoding: TEncoding; begin Filename := GetOutputFilename(editFilename.Text, MainForm.ActiveDbObj); // Confirmation dialog if file exists if radioOutputFile.Checked and FileExists(Filename) and (MessageDialog(_('File exists'), f_('Overwrite file %s?', [Filename]), mtConfirmation, [mbYes, mbCancel]) = mrCancel) then begin ModalResult := mrNone; Exit; end; try Screen.Cursor := crHourglass; SelectionOnly := grpSelection.ItemIndex = 0; Mainform.DataGridEnsureFullRows(Grid, SelectionOnly); GridData := Mainform.GridResult(Grid); if SelectionOnly then NodeCount := Grid.SelectedCount else NodeCount := Grid.RootNodeCount; MainForm.EnableProgress(NodeCount); TableName := BestTableName(GridData); ExcludeCol := NoColumn; if (not chkIncludeAutoIncrement.Checked) or (not chkIncludeAutoIncrement.Enabled) then ExcludeCol := GridData.AutoIncrementColumn; if radioOutputCopyToClipboard.Checked then Encoding := TEncoding.UTF8 else begin Encoding := MainForm.GetEncodingByName(comboEncoding.Text); // Add selected file to file list, and sort it onto the top of the list i := FRecentFiles.IndexOf(editFilename.Text); if i > -1 then FRecentFiles.Delete(i); FRecentFiles.Insert(0, editFilename.Text); for i:=FRecentFiles.Count-1 downto 10 do FRecentFiles.Delete(i); end; S := TStringStream.Create(Header, Encoding); Header := ''; case ExportFormat of efHTML: begin Header := '' + CRLF + CRLF + '' + CRLF + ' ' + CRLF + ' ' + TableName + '' + CRLF + ' ' + CRLF + ' ' + CRLF + ' ' + CRLF + ' ' + CRLF + CRLF + ' ' + CRLF + CRLF; if chkIncludeQuery.Checked then Header := Header + '

' + GridData.SQL + '

' + CRLF + CRLF; Header := Header + ' ' + CRLF; if chkIncludeColumnNames.Checked then begin Header := Header + ' ' + CRLF + ' ' + CRLF; Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin if Col <> ExcludeCol then Header := Header + ' ' + CRLF; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Header := Header + ' ' + CRLF + ' ' + CRLF; end; Header := Header + ' ' + CRLF; end; efExcel, efCSV: begin Separator := GridData.Connection.UnescapeString(editSeparator.Text); Encloser := GridData.Connection.UnescapeString(editEncloser.Text); Terminator := GridData.Connection.UnescapeString(editTerminator.Text); 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. if Col <> ExcludeCol then begin Data := Grid.Header.Columns[Col].Text; if (GridData.DataType(Col).Category in [dtcBinary, dtcSpatial]) and (not Mainform.actBlobAsText.Checked) then Data := 'HEX(' + Data + ')'; // Add header item. if Header <> '' then Header := Header + Separator; Header := Header + Encloser + Data + Encloser; end; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Header := Header + Terminator; end; end; efXML: begin // Imitate mysqldump's XML style Header := '' + CRLF + CRLF; if chkIncludeQuery.Checked then Header := Header + '' + CRLF else Header := Header + '' + CRLF; end; efLaTeX: begin Header := '\begin{tabular}'; Separator := ' & '; Encloser := ''; Terminator := '\\ '+CRLF; Header := Header + '{'; Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin if Col <> ExcludeCol then Header := Header + ' c '; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Header := Header + '}' + CRLF; if chkIncludeColumnNames.Checked then begin Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin if Col <> ExcludeCol then Header := Header + Grid.Header.Columns[Col].Text + Separator; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Delete(Header, Length(Header)-Length(Separator)+1, Length(Separator)); Header := Header + Terminator; end; end; efWiki: begin Separator := ' || '; Encloser := ''; Terminator := ' ||'+CRLF; if chkIncludeColumnNames.Checked then begin Header := '|| '; Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin if Col <> ExcludeCol then Header := Header + '*' + Grid.Header.Columns[Col].Text + '*' + Separator; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Delete(Header, Length(Header)-Length(Separator)+1, Length(Separator)); Header := Header + Terminator; end; end; efPHPArray: begin if radioOutputFile.Checked then Header := ' NoColumn do begin if Col <> ExcludeCol then begin if chkIncludeColumnNames.Checked then Header := Header + Grid.Header.Columns[Col].Text + Separator else Header := Header + Separator end; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Header := Header + Terminator; // Write an extra line with dashes below the heading, otherwise the table won't parse Header := Header + TrimLeft(Separator); Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin if Col <> ExcludeCol then begin Header := Header + '---'; if GridData.DataType(Col).Category in [dtcInteger, dtcReal] then Header := Header + ':'; Header := Header + Separator; end; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Header := Header + Terminator; end; efJSON: begin // JavaScript Object Notation Header := '{' + CRLF; if chkIncludeQuery.Checked then Header := Header + #9 + '"query": '+EscapePHP(GridData.SQL)+',' + CRLF else Header := Header + #9 + '"table": '+EscapePHP(TableName)+',' + CRLF ; Header := Header + #9 + '"rows":' + CRLF + #9 + '['; end; end; S.WriteString(Header); Node := GetNextNode(Grid, nil, SelectionOnly); while Assigned(Node) do begin // Update status once in a while. if (Node.Index+1) mod 100 = 0 then begin MainForm.ShowStatusMsg(f_('Exporting row %s of %s (%d%%, %s)', [FormatNumber(Node.Index+1), FormatNumber(NodeCount), Trunc((Node.Index+1) / NodeCount *100), FormatByteNumber(S.Size)] )); MainForm.ProgressStep; end; RowNum := Grid.GetNodeData(Node); GridData.RecNo := RowNum^; // Row preamble case ExportFormat of efHTML: tmp := ' ' + CRLF; efXML: tmp := #9'' + CRLF; efSQLInsert, efSQLReplace, efSQLDeleteInsert: begin tmp := ''; if ExportFormat = efSQLDeleteInsert then begin tmp := tmp + 'DELETE FROM ' + GridData.Connection.QuoteIdent(Tablename) + ' WHERE' + GridData.GetWhereClause + ';' + CRLF; end; if ExportFormat in [efSQLInsert, efSQLDeleteInsert] then tmp := tmp + 'INSERT' else tmp := tmp + 'REPLACE'; tmp := tmp + ' INTO '+GridData.Connection.QuoteIdent(Tablename); if chkIncludeColumnNames.Checked then begin tmp := tmp + ' ('; Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin if (Col <> ExcludeCol) and (not GridData.ColIsVirtual(Col)) then tmp := tmp + GridData.Connection.QuoteIdent(Grid.Header.Columns[Col].Text)+', '; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Delete(tmp, Length(tmp)-1, 2); tmp := tmp + ')'; end; tmp := tmp + ' VALUES ('; end; efWiki: tmp := TrimLeft(Separator); efPHPArray: tmp := #9 + 'array('+CRLF; efMarkDown: tmp := '| '; efJSON: begin if chkIncludeColumnNames.Checked then tmp := CRLF + #9#9 + '{' + CRLF else tmp := CRLF + #9#9 + '[' + CRLF end else tmp := ''; end; Col := Grid.Header.Columns.GetFirstVisibleColumn; while Col > NoColumn do begin if Col <> ExcludeCol then begin if (GridData.DataType(Col).Category in [dtcBinary, dtcSpatial]) and (not Mainform.actBlobAsText.Checked) then Data := GridData.HexValue(Col) else Data := GridData.Col(Col); // Keep formatted numeric values if (GridData.DataType(Col).Category in [dtcInteger, dtcReal]) and (ExportFormat in [efExcel, efHTML, efMarkDown]) then Data := FormatNumber(Data, False); case ExportFormat of efHTML: begin // Escape HTML control characters in data. Data := HTMLSpecialChars(Data); tmp := tmp + ' ' + CRLF; end; efExcel, efCSV, efLaTeX, efWiki, efMarkDown: begin // Escape encloser characters inside data per de-facto CSV. Data := StringReplace(Data, Encloser, Encloser+Encloser, [rfReplaceAll]); if GridData.IsNull(Col) and (ExportFormat in [efExcel, efCSV, efMarkDown]) then Data := editNull.Text else Data := Encloser + Data + Encloser; tmp := tmp + Data + Separator; end; efXML: begin // Print cell start tag. tmp := tmp + #9#9'' + CRLF else begin if (GridData.DataType(Col).Category in [dtcBinary, dtcSpatial]) and (not Mainform.actBlobAsText.Checked) then tmp := tmp + ' format="hex"'; tmp := tmp + '>' + HTMLSpecialChars(Data) + '' + CRLF; end; end; efSQLInsert, efSQLReplace, efSQLDeleteInsert: begin if GridData.ColIsVirtual(Col) then Data := '' else if GridData.IsNull(Col) then Data := 'NULL' else if (GridData.DataType(Col).Index = dtBit) and GridData.Connection.Parameters.IsMySQL then Data := 'b' + esc(Data) else if (GridData.DataType(Col).Category in [dtcText, dtcTemporal, dtcOther]) or ((GridData.DataType(Col).Category in [dtcBinary, dtcSpatial]) and Mainform.actBlobAsText.Checked) then Data := esc(Data) else if Data = '' then Data := esc(Data); if not Data.IsEmpty then tmp := tmp + Data + ', '; end; efPHPArray: begin if GridData.IsNull(Col) then Data := 'NULL' else case GridData.DataType(Col).Category of dtcInteger, dtcReal: begin // Remove zeropadding to avoid octal => integer conversion in PHP Data := FormatNumber(Data); Data := UnformatNumber(Data); end; else Data := EscapePHP(Data); end; if chkIncludeColumnNames.Checked then tmp := tmp + #9#9 + EscapePHP(Grid.Header.Columns[Col].Text) + ' => ' + Data + ','+CRLF else tmp := tmp + #9#9 + Data + ','+CRLF; end; efJSON: begin tmp := tmp + #9#9#9; if chkIncludeColumnNames.Checked then tmp := tmp + EscapePHP(Grid.Header.Columns[Col].Text) + ': '; if GridData.IsNull(Col) then tmp := tmp + 'null,' +CRLF else begin case GridData.DataType(Col).Category of dtcInteger, dtcReal: tmp := tmp + Data; else tmp := tmp + EscapePHP(Data) end; tmp := tmp + ',' + CRLF; end; end; end; end; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; // Row epilogue case ExportFormat of efHTML: tmp := tmp + ' ' + CRLF; efExcel, efCSV, efLaTeX, efWiki: begin Delete(tmp, Length(tmp)-Length(Separator)+1, Length(Separator)); tmp := tmp + Terminator; end; efXML: tmp := tmp + #9'' + CRLF; efSQLInsert, efSQLReplace, efSQLDeleteInsert: begin Delete(tmp, Length(tmp)-1, 2); tmp := tmp + ');' + CRLF; end; efPHPArray: tmp := tmp + #9 + '),' + CRLF; efMarkDown: tmp := tmp + Terminator; efJSON: begin Delete(tmp, length(tmp)-2,2); if chkIncludeColumnNames.Checked then tmp := tmp + #9#9 + '},' else tmp := tmp + #9#9 + '],'; end; end; S.WriteString(tmp); Node := GetNextNode(Grid, Node, SelectionOnly); end; // Footer case ExportFormat of efHTML: begin tmp := ' ' + CRLF + '
' + Grid.Header.Columns[Col].Text + '
' + Data + '
' + CRLF + CRLF + '

' + CRLF + ' generated ' + DateToStr(now) + ' ' + TimeToStr(now) + ' by ' + APPNAME + ' ' + Mainform.AppVersion + '' + CRLF + '

' + CRLF + CRLF + ' ' + CRLF + '' + CRLF; end; efXML: begin if chkIncludeQuery.Checked then tmp := '' + CRLF else tmp := '' + CRLF; end; efLaTeX: tmp := '\end{tabular}' + CRLF; efPHPArray: begin tmp := ');' + CRLF; if radioOutputFile.Checked then tmp := tmp + '?>'; end; efJSON: begin S.Size := S.Size - 1; tmp := CRLF + #9 + ']' + CRLF + '}'; end; else tmp := ''; end; S.WriteString(tmp); if radioOutputCopyToClipboard.Checked then begin HTML := nil; // SynEdit's exporter is slow on large strings, see issue #2903 if S.Size < 100*SIZE_KB then begin case ExportFormat of efSQLInsert, efSQLReplace, efSQLDeleteInsert: begin Exporter := TSynExporterHTML.Create(Self); Exporter.Highlighter := MainForm.SynSQLSynUsed; Exporter.ExportAll(Explode(CRLF, S.DataString)); HTML := TMemoryStream.Create; Exporter.SaveToStream(HTML); Exporter.Free; end; efHTML: HTML := S; end; end; StreamToClipboard(S, HTML, (ExportFormat=efHTML) and (HTML <> nil)); end else begin try S.SaveToFile(Filename); except on E:EFCreateError do begin // Keep form open if file cannot be created ModalResult := mrNone; MainForm.SetProgressState(pbsError); ErrorDialog(E.Message); end; end; end; Mainform.ShowStatusMsg(_('Freeing data...')); FreeAndNil(S); except // Whole export code wrapped here on E:EDatabaseError do begin Screen.Cursor := crDefault; ErrorDialog(E.Message); end else raise; end; Mainform.DisableProgress; Mainform.ShowStatusMsg; Screen.Cursor := crDefault; end; end.