Sign in Join
Wiki copytemporary

CSV: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Overview
22
33CSV means Comma-Separated Values, and is a popular file format that is unfortunately not completely standardized.
44
55It is a text based file format with
66
77- data fields separated by commas (or, in variants, other characters like tabs and semicolons)
88- there may or not be a header line that lists the field names
99- field data containing delimiters may be prohibited or enclosed by quotes (most commonly the double quote character)
1010- line endings (#13 and/or \#10) may or may not be allowed in field data
1111
1212[RFC4180](http://www.rfc-editor.org/rfc/rfc4180.txt) tries to codify and standardize existing practice; it makes sense to conform to this standard when writing CSV data (and accept all RFC4180 data when reading).
1313
1414A different, Creativyst/Excel, CSV pseudo-format specification can be found [here](http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm#FileFormat).
1515
1616A sample CSV snippet:
1717
1818`FirstName,Surname,DOB,Remarks`\
1919`Jim,Weston,19560818,"Also known as ""The Butcher"""`\
2020`Alice,Cooper,19760312,""`
2121
2222Apparently a header line is used here, as is quoting using double quotes.
2323
2424Spreadsheet packages such as Microsoft Excel and OpenOffice/LibreOffice Calc are able to export to and import from this format. However, as Microsoft Excel may interpret some fields such as date fields differently depending on a user's OS locale, it may pay to find alternative ways of transferring data (e.g. using the [[FPSpreadsheet]] code).
2525
2626## CSV and SDF
2727
2828Delphi (and Free Pascal) have a very similar (but not identical) format, SDF. See [[SDF]] for more details.
2929
3030## Implementations
3131
3232### CsvDocument
3333
3434[[CsvDocument]] is a robust implementation of both the RFC 4180 CSV format and the alternative Creativyst/Excel CSV format. It offers both line-based and document-based access. Recommended for use with FPC/Lazarus. See [[CsvDocument]].
3535
3636### DelimitedText
3737
3838TStringList offers the DelimitedText property. This parses a line of text out into separate fields. Note, however, that DelimitedText is supposed to be in SDF, a Delphi-specific format that is very much like CSV, but does not conform with RFC4180 completely.
3939
4040Tips: when reading CSV data, set the *StrictDelimiter* property to true.
4141
4242When writing out CSV data, set *StrictDelimiter* to false and output the *DelimitedText* property. One oddity is that e.g. tab characters are removed when writing out data using *StrictDelimiter:=false*
4343
4444#### SDF format
4545
4646See [[SDF|SDF format]]
4747
4848### SDFDataset
4949
50-Free Pascal offers the [[TSdfDataSet]], which <s>stores data in SDF format.</s> Note: FPC 2.6.x and earlier store sdfdataset data in a format that is not completely CSV or completely SDF compatible.
50+Free Pascal offers the [[TSdfDataSet]], which stores data in SDF format. Note: FPC 2.6.x and earlier store sdfdataset data in a format that is not completely CSV or completely SDF compatible.
5151
5252As indicated, [[SDF]] differs from [[CSV]]. Depending on the flavour of CSV, this format may be close enough to what a reading application expects to function. If you need an RFC4180 compliant CSV format, use [[TCSVDataSet]] instead.
5353
5454#### TSdfDataset and TFixedDataset sample files
5555
5656Sample SDF file
5757
5858
5959Below is a sample database for TSdfDataset. Note that the first line has the names of the fields and that we are using commas as separators:
6060
6161`ID,NAMEEN,NAMEPT,HEIGHT,WIDTH,PINS,DRAWINGCODE`\
6262`1,resistor,resistor,1,1,1,LINE`\
6363`2,capacitor,capacitor,1,1,1,LINE`\
6464`3,transistor npn,transistor npn`
6565
6666- Sample TFixedDataset file
6767
6868
6969Each record occupies a fixed amount of space, and if the field is smaller, spaces should be used to fill the remaining size.
7070
7171`Name = 15 chars; Surname = 15 chars; e_mail = 20 chars;`\
7272`Piet           Pompies                  piet@pompies.net`
73-
74-</pre>
7573
7674#### Using the datasets: example code
7775
7876Sometimes it is useful to create the dataset and work with it completely in code, and the following code will do exactly this. Note some peculiarities of TSdfDataset/TFixedDataset:
7977
8078- The lines in the database can have a maximum size of about 300. A fix is being researched.
8179- It is necessary to add the field definitions. Some datasets are able to fill this information alone from the database file.
8280- One should set FirstLineAsSchema to true, to indicate that the first line includes the field names and positions.
8381- The Delimiter property holds the separator for the fields. It will not be possible to use this char in strings in the database. Similarly it will currently not be possible to have line endings in the database because they mark the change between records. It's possible to overcome this by substituting the needed comma or line ending with another rarely used char, like \#. When showing the data on screen all \# chars could be converted to line endings and the inverse when storing data back to the database. The ReplaceString routine is useful here.
8482
8583``` pascal
8684uses sdfdata, db;
8785
8886constructor TComponentsDatabase.Create;
8987var
9088 FDataset: TSdfDataset;
9189begin
9290 inherited Create;
9391
9492 FDataset := TSdfDataset.Create(nil);
9593 FDataset.FileName := vConfigurations.ComponentsDBFile;
9694 FDataset.FileMustExist := false; //don't require existing csv file
9795
9896 // Not necessary with TSdfDataset:
9997 // FDataset.TableName := STR_DB_COMPONENTS_TABLE;
10098 // FDataset.PrimaryKey := STR_DB_COMPONENTS_ID;
10199
102100 // Adds field definitions
103101 FDataset.FieldDefs.Add('ID', ftString);
104102 //schema addition is needed so that sdfdataset writes out the csv header correctly:
105103 FDataset.Schema.Add('ID');
106104 FDataset.FieldDefs.Add('NAMEEN', ftString);
107105 FDataset.Schema.Add('NAMEEN');
108106 FDataset.FieldDefs.Add('NAMEPT', ftString);
109107 FDataset.Schema.Add('NAMEPT');
110108 FDataset.FieldDefs.Add('HEIGHT', ftString);
111109 FDataset.Schema.Add('HEIGHT');
112110 FDataset.FieldDefs.Add('WIDTH', ftString);
113111 FDataset.Schema.Add('WIDTH');
114112 FDataset.FieldDefs.Add('PINS', ftString);
115113 FDataset.Schema.Add('PINS');
116114 FDataset.FieldDefs.Add('DRAWINGCODE', ftString);
117115 FDataset.Schema.Add('DRAWINGCODE');
118116
119117 // Necessary for TSdfDataset
120118 FDataset.Delimiter := ',';
121119 FDataset.FirstLineAsSchema := True;
122120
123121 FDataset.Active := True;
124122
125123 // Sets the initial record
126124 CurrentRecNo := 1;
127125 FDataset.First;
128126end;
129127```
130128
131129When using [[TSdfDataSet]] directly be aware that RecNo, although it is implemented, does not work as a way to move through the dataset whether reading or writing records. The standard navigation routines like First, Next, Prior and Last work as expected, so you need to use them rather than RecNo.
132130
133131If you are used to using absolute record numbers to navigate around a database you can implement your own version of RecNo. Declare a global longint variable called CurrentRecNo which will hold the current RecNo value. Remember that this variable will have the same convention as RecNo, so the first record has number 1 (it is not zero-based). After activating the database initialize the database to the first record with TSdfDataset.First and set CurrentRecNo := 1.
134132
135133Example code:
136134
137135``` pascal
138136{@@
139137 Moves to the desired record using TDataset.Next and TDataset.Prior
140138 This avoids using TDataset.RecNo which doesn't navigate reliably in any dataset.
141139
142140 @param AID Indicates the record number. The first record has number 1
143141}
144142procedure TComponentsDatabase.GoToRec(AID: Integer);
145143begin
146144 // We are before the desired record, move forward
147145 if CurrentRecNo < AID then
148146 begin
149147 while (not FDataset.EOF) and (CurrentRecNo < AID) do
150148 begin
151149 FDataset.Next;
152150 FDataset.CursorPosChanged;
153151 Inc(CurrentRecNo);
154152 end;
155153 end
156154 // We are after the desired record, move back
157155 else if CurrentRecNo > AID then
158156 begin
159157 while (CurrentRecNo >= 1) and (CurrentRecNo > AID) do
160158 begin
161159 FDataset.Prior;
162160 FDataset.CursorPosChanged;
163161 Dec(CurrentRecNo);
164162 end;
165163 end;
166164end;
167165```
168166
169167### Data Export
170168
171169Free Pascal/Lazarus database export functionality (e.g. TCSVExporter on the Data Export tab - requires installation of the [[lazdbexport]] package) offers CSV export functionality for [[TDataSet|datasets]].
172170
173171### Jan's CSV components
174172
175173See [[JCSV (Jans CSV Components)]].
176174
177175### ZMSQL
178176
179177[[ZMSQL]] stores data in semicolon-delimited files (using SDF?).
180178
181179---
182180*From the Free Pascal wiki, page "CSV", as it stood on 2020-07-18 after a change by FTurtle, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history