Sign in Join
Wiki copytemporary

CSV: what changed

Revision 2 to 3. 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
18-`FirstName,Surname,DOB,Remarks`\
19-`Jim,Weston,19560818,"Also known as ""The Butcher"""`\
20-`Alice,Cooper,19760312,""`
18+```
19+FirstName,Surname,DOB,Remarks
20+Jim,Weston,19560818,"Also known as ""The Butcher"""
21+Alice,Cooper,19760312,""
22+```
2123
2224Apparently a header line is used here, as is quoting using double quotes.
2325
2426Spreadsheet 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).
2527
2628## CSV and SDF
2729
2830Delphi (and Free Pascal) have a very similar (but not identical) format, SDF. See [[SDF]] for more details.
2931
3032## Implementations
3133
3234### CsvDocument
3335
3436[[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]].
3537
3638### DelimitedText
3739
3840TStringList 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.
3941
4042Tips: when reading CSV data, set the *StrictDelimiter* property to true.
4143
4244When 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*
4345
4446#### SDF format
4547
4648See [[SDF|SDF format]]
4749
4850### SDFDataset
4951
5052Free 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.
5153
5254As 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.
5355
5456#### TSdfDataset and TFixedDataset sample files
5557
5658Sample SDF file
5759
5860
5961Below 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:
6062
61-`ID,NAMEEN,NAMEPT,HEIGHT,WIDTH,PINS,DRAWINGCODE`\
62-`1,resistor,resistor,1,1,1,LINE`\
63-`2,capacitor,capacitor,1,1,1,LINE`\
64-`3,transistor npn,transistor npn`
63+```
64+ID,NAMEEN,NAMEPT,HEIGHT,WIDTH,PINS,DRAWINGCODE
65+1,resistor,resistor,1,1,1,LINE
66+2,capacitor,capacitor,1,1,1,LINE
67+3,transistor npn,transistor npn
68+```
6569
6670- Sample TFixedDataset file
6771
6872
6973Each record occupies a fixed amount of space, and if the field is smaller, spaces should be used to fill the remaining size.
7074
71-`Name = 15 chars; Surname = 15 chars; e_mail = 20 chars;`\
75+```
76+Name = 15 chars; Surname = 15 chars; e_mail = 20 chars;
77+```
78+
7279`Piet           Pompies                  piet@pompies.net`
7380
7481#### Using the datasets: example code
7582
7683Sometimes 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:
7784
7885- The lines in the database can have a maximum size of about 300. A fix is being researched.
7986- It is necessary to add the field definitions. Some datasets are able to fill this information alone from the database file.
8087- One should set FirstLineAsSchema to true, to indicate that the first line includes the field names and positions.
8188- 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.
8289
8390``` pascal
8491uses sdfdata, db;
8592
8693constructor TComponentsDatabase.Create;
8794var
8895 FDataset: TSdfDataset;
8996begin
9097 inherited Create;
9198
9299 FDataset := TSdfDataset.Create(nil);
93100 FDataset.FileName := vConfigurations.ComponentsDBFile;
94101 FDataset.FileMustExist := false; //don't require existing csv file
95102
96103 // Not necessary with TSdfDataset:
97104 // FDataset.TableName := STR_DB_COMPONENTS_TABLE;
98105 // FDataset.PrimaryKey := STR_DB_COMPONENTS_ID;
99106
100107 // Adds field definitions
101108 FDataset.FieldDefs.Add('ID', ftString);
102109 //schema addition is needed so that sdfdataset writes out the csv header correctly:
103110 FDataset.Schema.Add('ID');
104111 FDataset.FieldDefs.Add('NAMEEN', ftString);
105112 FDataset.Schema.Add('NAMEEN');
106113 FDataset.FieldDefs.Add('NAMEPT', ftString);
107114 FDataset.Schema.Add('NAMEPT');
108115 FDataset.FieldDefs.Add('HEIGHT', ftString);
109116 FDataset.Schema.Add('HEIGHT');
110117 FDataset.FieldDefs.Add('WIDTH', ftString);
111118 FDataset.Schema.Add('WIDTH');
112119 FDataset.FieldDefs.Add('PINS', ftString);
113120 FDataset.Schema.Add('PINS');
114121 FDataset.FieldDefs.Add('DRAWINGCODE', ftString);
115122 FDataset.Schema.Add('DRAWINGCODE');
116123
117124 // Necessary for TSdfDataset
118125 FDataset.Delimiter := ',';
119126 FDataset.FirstLineAsSchema := True;
120127
121128 FDataset.Active := True;
122129
123130 // Sets the initial record
124131 CurrentRecNo := 1;
125132 FDataset.First;
126133end;
127134```
128135
129136When 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.
130137
131138If 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.
132139
133140Example code:
134141
135142``` pascal
136143{@@
137144 Moves to the desired record using TDataset.Next and TDataset.Prior
138145 This avoids using TDataset.RecNo which doesn't navigate reliably in any dataset.
139146
140147 @param AID Indicates the record number. The first record has number 1
141148}
142149procedure TComponentsDatabase.GoToRec(AID: Integer);
143150begin
144151 // We are before the desired record, move forward
145152 if CurrentRecNo < AID then
146153 begin
147154 while (not FDataset.EOF) and (CurrentRecNo < AID) do
148155 begin
149156 FDataset.Next;
150157 FDataset.CursorPosChanged;
151158 Inc(CurrentRecNo);
152159 end;
153160 end
154161 // We are after the desired record, move back
155162 else if CurrentRecNo > AID then
156163 begin
157164 while (CurrentRecNo >= 1) and (CurrentRecNo > AID) do
158165 begin
159166 FDataset.Prior;
160167 FDataset.CursorPosChanged;
161168 Dec(CurrentRecNo);
162169 end;
163170 end;
164171end;
165172```
166173
167174### Data Export
168175
169176Free 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]].
170177
171178### Jan's CSV components
172179
173180See [[JCSV (Jans CSV Components)]].
174181
175182### ZMSQL
176183
177184[[ZMSQL]] stores data in semicolon-delimited files (using SDF?).
178185
179186---
180187*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