Skip to content
uses System.Win.ComObj, ExcelXP, ActiveX; procedure Split(Delimiter: char; Str: string; ListOfStrings: TStrings) ; begin ListOfStrings.Clear; ListOfStrings.Delimiter := Delimiter; ListOfStrings.StrictDelimiter := True; // Requires D2006 or newer. ListOfStrings.DelimitedText := Str; end; procedure ExportCSVToExcel(CSV: TStringlist; separator: char; DestName: string; TrimAll: boolean; Freeze: boolean; AutoFilter: boolean); var ovExcelApp: OleVariant; ovExcelWorkbook: OleVariant; ovWS: OleVariant; ovCell: OleVariant; i, j: int64; columns: TStringList; str: string; extension: string; const xlWorkbookDefault = 51; // required for XLSX - originally from Excel97.pas xlAnd = $00000001; // required for AutoFilter - originally from ExcelXP.pas begin try CoInitialize(nil); ovExcelApp := CreateOleObject('Excel.Application'); // If Excel isnt installed will raise an exception try ovExcelWorkbook := ovExcelApp.WorkBooks.Add; ovWS := ovExcelWorkbook.Worksheets.Item[1]; // Go to first worksheet ovWS.Activate; ovWS.Select; columns:=TStringList.Create; try i:=1; while i<=csv.Count do begin columns.Clear; str:=csv.Strings[i-1]; Split(separator, str, columns); j:=1; while j<=columns.Count do begin ovCell:=ovWS.cells[i,j]; ovCell.numberformat:='@'; // Sets cell format as TEXT if TrimAll then begin ovCell.Formula := trim(columns.Strings[j-1]); end else begin ovCell.Formula := columns.Strings[j-1]; end; inc(j); end; inc(i); end; //ovWS.Range['A1','A1'].EntireColumn.AutoFit; i:=1; while i<=columns.Count do begin ovWS.cells[1,i].ColumnWidth := 40; inc(i); end; finally columns.Free; end; if AutoFilter then // Apply AutoFilter at top row headers begin ovWS.Cells.AutoFilter(1, EmptyParam, xlAnd, EmptyParam, True); // xlAnd uses ExcelXP end; if Freeze then // Freeze top row headers begin ovExcelApp.ActiveSheet.Rows[2].Select; // 2nd row must be selected ovExcelApp.ActiveWindow.FreezePanes := True; end; ovExcelApp.ActiveSheet.Cells[1,1].Select; extension:=extractfileext(lowercase(DestName)); if extension='.xlsx' then ovWS.SaveAs(DestName, xlWorkbookDefault) // XLSX else ovWS.SaveAs(DestName, 1, '', '', False, False); // XLS finally ovExcelWorkbook.Close; ovWS := Unassigned; ovExcelWorkbook := Unassigned; ovExcelApp := Unassigned; CoUninitialize; end; except on E : Exception do begin Application.MessageBox(PChar(E.Message),PChar(tr('WARNING')),MB_ICONWARNING); end; end; end;
Example software: CSV to XLS Converter
pivogol 21 / 21 / 8 Регистрация: 07.01.2009 Сообщений: 556 |
||||
1 |
||||
31.01.2016, 17:09. Показов 3242. Ответов 2 Метки нет (Все метки)
Подскажите, пожалуйста, как в Excel сохранить открытый csv-файл как xls?
0 |
2981 / 2169 / 502 Регистрация: 11.09.2009 Сообщений: 8,086 |
|
31.01.2016, 17:30 |
2 |
открывается корректно, но сохраняется неправильно То есть в Excel выглядит нормально, правильно «разложенный» по ячейкам?
0 |
pivogol 21 / 21 / 8 Регистрация: 07.01.2009 Сообщений: 556 |
||||
31.01.2016, 22:19 [ТС] |
3 |
|||
Да, открылся csv-файл чётко, разложено по столбцам. Добавлено через 4 часа 45 минут
0 |
I have a large amount of data to insert into an worksheet of an existing Excel workbook. The Excel workbook will have other worksheets containing calculations and a pivot tables. The data may have as many as 60,000 rows and more than 30 columns. This solution must work for both Excel 2003 and Excel 2007.
Using the Excel OLE object is way too slow so we are attempting to load the data from a CSV file. We have come up with a method to load the data by placing the data onto the clipboard and then pasting it into the worksheet. I feel this is a quite a kludge. Is there another way to programmatically load a CSV file into a worksheet? Or perhaps a different solution altogether?
Update: We got slammed with another task before we could fully investigate the answers. We should be able to get back to this in a couple of weeks. I’ll be sure to update again when we get back to this task.
Thanks for all of the answers to date!
asked Mar 18, 2009 at 1:48
mreithmreith
2994 silver badges15 bronze badges
XLSReadWrite is a component that can read and write excel files from Delphi. It’s fast and it has support for Excel 2003 and 2007. You can create new excel files as well as open existing ones and add/modify them.
Also you do not need to have Excel installed to be able to use it.
See http://www.axolot.com/components/xlsrwii20.htm
answered Mar 18, 2009 at 12:34
ajobajob
2271 gold badge4 silver badges9 bronze badges
1
Any chance you can drop the requirement for this to work with Office 2003? I would have recommended the Open XML Format SDK. It lets you bind managed code assemblies to spreadsheet documents that can handle events such as Open or Close, and read and write to cells in the document, among other things. Alternatively, you can use it to manipulate XSLX documents from an application. Quite slick, actually.
Since this won’t work for you, how about writing a macro that pulls in the CSV file when the spreadsheet is loaded?
answered Mar 18, 2009 at 2:03
cdonnercdonner
36.8k22 gold badges105 silver badges150 bronze badges
1
you can load the csv into listview or usin OLEDB provider to load it on DBGrid, then export it into xls file format using TMxExport component from Max Components:
Max Components
answered Mar 18, 2009 at 2:02
DelsDels
2,3559 gold badges39 silver badges59 bronze badges
1
Have you tried linking the csv file directly into the worksheet.
Go to Data -> Import External Data -> Import Data
change the file type to ‘Text Files’
You can then refresh the worksheet when the csv is update.
NOTE: I have not done this with the volume of data you have indicated, so YMMV
answered Mar 18, 2009 at 2:11
Craig TCraig T
2,7615 gold badges25 silver badges33 bronze badges
1
Actually there is a way that is quite fast, pretty old tech (nowdays) but is probably the fastest.
It’s ADO or for earlier versions DAO (note not ADO.NET)
You can read a CSV file using ADO and the JET Engine to get the data into a ADO recordset, then an Excel Range Object has a CopyFromRecordSet method that will copy (very fast) from the ADO (or DAO) recordset.
http://msdn.microsoft.com/en-us/library/aa165427(office.10).aspx
answered Mar 18, 2009 at 3:20
Tim JarvisTim Jarvis
18.3k9 gold badges54 silver badges91 bronze badges
1
You can try to use Tab Separated Values instead of CSV — than you just paste this into Excel
answered Mar 19, 2009 at 11:45
Jk.Jk.
4431 gold badge3 silver badges8 bronze badges
2
Find here a modified compilable version of your code which creates valid BIFF2 files; the demo runs with both Lazarus and Delphi. Before running, please update the path of the destination file at the end of the main procedure in memstream.SaveToFile(…).
-
program project1;
-
{$IFDEF FPC}
-
{$mode objfpc}{$H+}
-
{$ENDIF}
-
uses
-
Classes, SysUtils;
-
const
-
BIFF_EOF = $000A;
-
XLS_SHEET = $0010;
-
//—
-
BOF_BIFF2 = $0009;
-
BIFF2_SHEETVIEWSETTINGS_WINDOW2 = $003E;
-
LABEL_BIFF2 = $0004;
-
DIMENSIONS_BIFF2 = $0000;
-
//—
-
BOF_BIFF5 = $0809;
-
LABEL_BIFF5 = $0204;
-
DIMENSIONS_BIFF5 = $0200;
-
type
-
TmyDynStringArray = array of string;
-
TmyDynStringStringArray = array of TmyDynStringArray;
-
type
-
TXLSWriter = class(Tobject)
-
private
-
procedure WriteBIFFRecordHeader(ARecordID: Word; ARecordSize: Word);
-
procedure WriteByte(B: byte);
-
procedure WriteWord(W: word);
-
protected
-
function ConvertToCodePage(AText: String): ansistring;
-
procedure WriteBOF;
-
procedure WriteEOF;
-
procedure WriteDimension;
-
procedure WriteCodePage;
-
public
-
Version: Byte;
-
DataStream: TMemoryStream;
-
MaxCols: Word;
-
MaxRows: Word;
-
procedure CellStr(ARow, ACol: Word; const AValue: String);
-
constructor Create;
-
destructor Destroy; override;
-
end;
-
constructor TXLSWriter.Create;
-
begin
-
inherited Create;
-
DataStream := TMemoryStream.Create;
-
DataStream.Size := 0;
-
MaxCols := 256;
-
MaxRows := 65535;
-
end;
-
destructor TXLSWriter.Destroy;
-
begin
-
if DataStream <> nil then
-
DataStream.Free;
-
inherited;
-
end;
-
procedure TXLSWriter.WriteBIFFRecordHeader(ARecordID, ARecordSize: word);
-
begin
-
WriteWord(ARecordID);
-
WriteWord(ARecordSize);
-
end;
-
procedure TXLSWriter.WriteBOF;
-
begin
-
(*
-
https://www.openoffice.org/sc/excelfileformat.pdf#page=135&zoom=auto,104.9,771.1
-
— 5.8 BOF – Beginning of File
-
*)
-
WriteBIFFRecordHeader(BOF_BIFF2, 4);
-
WriteWord(BOF_BIFF2);
-
WriteWord(XLS_SHEET);
-
end;
-
procedure TXLSWriter.WriteDimension;
-
begin
-
(*
-
https://www.openoffice.org/sc/excelfileformat.pdf#page=160&zoom=auto,85.6,581.5
-
— 5.35 DIMENSIon
-
*)
-
WriteBIFFRecordHeader(DIMENSIONS_BIFF2, 8);
-
WriteWord(0); // min cols
-
WriteWord(MaxRows); // max rows
-
WriteWord(0); // min rowss
-
WriteWord(MaxCols); // max cols
-
end;
-
function TXLSWriter.ConvertString(AText: String): ansistring;
-
begin
-
{$IFDEF FPC}
-
Result := UTF8ToAnsi(AText);
-
{$ELSE}
-
Result := AnsiString(AText);
-
{$ENDIF}
-
end;
-
procedure TXLSWriter.CellStr(ARow, ACol: Word; const AValue: String);
-
var
-
TmpBytesLen: Cardinal;
-
TmpSizeOfFieldLen: Byte;
-
TmpStrExcel: AnsiString;
-
begin
-
(*
-
See:
-
— https://www.openoffice.org/sc/excelfileformat.pdf
-
«2.5.2 Byte Strings (BIFF2-BIFF5)» states all srings are byte strings
-
Strings to not have to include #0 terminator per protocol
-
*)
-
if AValue = » then
-
exit;
-
TmpStrExcel := ConvertToCodePage(AValue); // To do for Delphi: Convert unicodestring to ansistring
-
TmpBytesLen := Length(TmpStrExcel);
-
//—
-
TmpSizeOfFieldLen := 1;
-
if (TmpBytesLen > 255) then
-
begin
-
TmpBytesLen := 255;
-
end
-
;
-
//—
-
WriteBIFFRecordHeader(LABEL_BIFF2, 2 + 2 + 3 + TmpSizeOfFieldLen + TmpBytesLen);
-
//—
-
WriteWord(ARow);
-
WriteWord(ACol);
-
//—
-
WriteByte(0);
-
WriteByte(0);
-
WriteByte(0);
-
//—
-
WriteByte(Byte(TmpBytesLen));
-
//—
-
DataStream.Write(TmpStrExcel[1], TmpBytesLen);
-
end;
-
procedure TXLSWriter.WriteByte(B: Byte);
-
begin
-
DataStream.Write(B, 1);
-
end;
-
procedure TXLSWriter.WriteWord(W: word);
-
begin
-
DataStream.Write(W, 2);
-
end;
-
procedure TXLSWriter.WriteEOF;
-
begin
-
(*
-
— https://www.openoffice.org/sc/excelfileformat.pdf#page=161&zoom=auto,113.9,540.2
-
5.37 — EOF — End of File
-
*)
-
WriteBIFFRecordHeader(BIFF_EOF, 0);
-
end;
-
procedure TXLSWriter.WriteCodePage;
-
var
-
W: Word;
-
begin
-
(*
-
https://www.openoffice.org/sc/excelfileformat.pdf#page=145&zoom=auto,113.9,771.1
-
— 5.17 CODEPAGE
-
*)
-
WriteBIFFRecordHeader($0042, 2); // $0042 = OPCODE CODEPAGE
-
W := 1252;
-
WriteWord(W);
-
end;
-
function BuildXLSFromStrArrs(const AStrArrs: TmyDynStringStringArray): TMemoryStream;
-
var
-
MR: Integer;
-
MC: Integer;
-
vxR: Integer;
-
vxC: Integer;
-
vxL: Word;
-
vxS: String;
-
vXLS: TXLSWriter;
-
begin
-
MR := Length(AStrArrs);
-
if MR > 0 then
-
MC := Length(AStrArrs[0])
-
else
-
MC := 0;
-
;
-
//—
-
//
-
//—
-
vXLS := TXLSWriter.Create;
-
vXLS.MaxRows := MR;
-
vXLS.MaxCols := MC;
-
//—
-
//
-
//—
-
vXLS.WriteBOF;
-
//vXLS.WriteCodePage; // Not absolutely necessary if you use strings of your code page
-
vXLS.WriteDimension;
-
//—
-
// TODO: Looking a specificaion, I may need to implement a row structure here
-
// — wp: not absolutely necessary, only if you want to change row heights and add row formats
-
//—
-
for vxC := 0 to MC—1 do
-
begin
-
for vxR := 0 to MR—1 do
-
begin
-
vxS := AStrArrs[vxR,vxC];
-
vXLS.CellStr(vxR,vxC,vxS)
-
end;
-
end;
-
vXLS.WriteEOF;
-
vXLS.DataStream.Position := 0;
-
Result := TMemoryStream.Create;
-
Result.CopyFrom(vXLS.DataStream, vXLS.DataStream.Size);
-
Result.Position := 0;
-
vXLS.Free;
-
end;
-
var
-
memstream: TMemoryStream;
-
AStrArrs: TmyDynStringStringArray;
-
begin
-
SetLength(AStrArrs, 3);
-
SetLength(AStrArrs[0], 2);
-
SetLength(AStrArrs[1], 2);
-
SetLength(AStrArrs[2], 2);
-
AStrArrs[0, 0] := ‘ÄÖÜ’;
-
AStrArrs[1, 0] := ‘B’;
-
AStrArrs[2, 0] := ‘C’;
-
AStrArrs[0, 1] := ‘D’;
-
AStrArrs[1, 1] := ‘E’;
-
AStrArrs[2, 1] := ‘F’;
-
memstream := BuildXLSFromStrArrs(AStrArrs);
-
memstream.SaveToFile(‘d:test.xls’);
-
memstream.Free;
-
end.
The problem in your code was that you did not write valid BIFF records consisting of a header and data. A BIFF record header consists of the recordID and the size of the following data, you only wrote the recordID. An important detail is also that strings in the BIFF2 file are ansistrings, but Lazarus and Delphi don’t use these strings by default any more. Therefore you must convert the strings to ansistrings -> function ConvertString():ansistring. An explicit CODEPAGE record in not required if you only write characters of your system codepage (which seems to be 1252).
The next time when you post questions I would appreciate if you could provide a compilable small project — this way it would be easier for me to get started. Pack the pas, lfm, lpr, and lpi files of a Lazarus project into a single zip which you can upload here as an attachment (no binary or other compiler-generated files, please).
Введение
CSV-файл
– простейший по организации файл-таблица, который понимает Microsoft
Excel. CSV – Como Separated Value – данные, разделенные запятой. Это обычный
текстовый файл, в котором каждая строка олицетворяет ряд таблицы, а разделение
этого ряда на колонки осуществляется путем разделения значений специальным
разделителем. Обычно роль этого разделителя, судя из названия, играет запятая.
Однако, встречается и другой разделитель – точка с запятой. Даже в Microsoft не
определились, с каким разделителем должен работать
Excel. При сохранении какой-либо таблицы в формате
CSV, в качестве разделителя используется точка с запятой. Но, при открытии
такого файла с помощью Microsoft Excel, нужно быть очень внимательным, так как, не
знаю, шутка это или нет, существует, как Вы знаете два способа открытия файла:
- В главном меню программы выбрать пункт «Открыть» из меню «файл».
- Найти на жестком диске нужный файл и два раза кликнуть на нем мышкой.
Так вот, при открытии
CSV файла первым способом,
Excel использует точку с
запятой в качестве разделителя, а при открытии вторым способом
Excel использует запятую в
качестве разделителя. Правда в Microsoft Excel XP эта проблема(?) решена, и используется
только точка с запятой, не смотря на название файла.
Работа с CSV файлами в Delphi
Для работы с
CSV-файлами в
Delphi Вам не понадобятся
различные сторонние компоненты. Как я уже говорил,
CSV-файл – это обычный
текстовый файл. Следовательно, для работы с ним мы будем использовать
стандартный тип TextFile определенный в модуле system.
Давайте напишем процедуру для загрузки
CSV файла в таблицу
TStringGrid. Это стандартный компонент Delphi для работы со строковыми таблицами. Находится он на странице
Additional в палитре компонентов Delphi.
Вот код этой процедуры:
procedure LoadCSVFile (FileName: String; separator: char); var f: TextFile; s1, s2: string; i, j: integer; begin i := 0; AssignFile (f, FileName); Reset(f); while not eof(f) do begin readln (f, s1); i := i + 1; j := 0; while pos(separator, s1)<>0 do begin s2 := copy(s1,1,pos(separator, s1)-1); j := j + 1; delete (s1, 1, pos(separator, S1)); StringGrid1.Cells[j-1, i-1] := s2; end; if pos (separator, s1)=0 then begin j := j + 1; StringGrid1.Cells[j-1, i-1] := s1; end; StringGrid1.ColCount := j; StringGRid1.RowCount := i+1; end; CloseFile(f); end;
Теперь разберем этот код.
procedure LoadCSVFile (FileName: String; separator: char);
Это заголовок нашей процедуры. В качестве
параметров мы передаем ей имя файла и разделитель данных.
AssignFile (f, FileName); Reset(f);
Здесь мы ассоциируем с файловой переменной
f имя файла и открываем его для
чтения.
while not eof(f) do begin readln (f, s1);
Пока не достигнут конец файла, читаем из него
очередную строку.
Далее нам остается только разделить строку на
много строк, используя в качестве разделителя символ
separator, и записать
эти строки в таблицу, учитывая то, что нужно увеличить число рядов на 1. Это
делается следующим кодом:
while pos(separator, s1)<>0 do begin s2 := copy(s1,1,pos(separator, s1)-1); j := j + 1; delete (s1, 1, pos(separator, S1)); StringGrid1.Cells[j-1, i-1] := s2; end; if pos (separator, s1)=0 then begin j := j + 1; StringGrid1.Cells[j-1, i-1] := s1; end; StringGrid1.ColCount := j; StringGRid1.RowCount := i+1;
Ну вот, пожалуй, и все что я хотел написать.