csvreadwrite.pp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. {
  2. CSV Parser, Builder classes.
  3. Version 0.5 2014-10-25
  4. Copyright (C) 2010-2014 Vladimir Zhirov <[email protected]>
  5. Contributors:
  6. Luiz Americo Pereira Camara
  7. Mattias Gaertner
  8. Reinier Olislagers
  9. This library is free software; you can redistribute it and/or modify it
  10. under the terms of the GNU Library General Public License as published by
  11. the Free Software Foundation; either version 2 of the License, or (at your
  12. option) any later version with the following modification:
  13. As a special exception, the copyright holders of this library give you
  14. permission to link this library with independent modules to produce an
  15. executable, regardless of the license terms of these independent modules,and
  16. to copy and distribute the resulting executable under terms of your choice,
  17. provided that you also meet, for each linked independent module, the terms
  18. and conditions of the license of that module. An independent module is a
  19. module which is not derived from or based on this library. If you modify
  20. this library, you may extend this exception to your version of the library,
  21. but you are not obligated to do so. If you do not wish to do so, delete this
  22. exception statement from your version.
  23. This program is distributed in the hope that it will be useful, but WITHOUT
  24. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  25. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  26. for more details.
  27. You should have received a copy of the GNU Library General Public License
  28. along with this library; if not, write to the Free Software Foundation,
  29. Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  30. }
  31. {$IFNDEF FPC_DOTTEDUNITS}
  32. unit csvreadwrite;
  33. {$ENDIF FPC_DOTTEDUNITS}
  34. {$mode objfpc}
  35. {$H+}
  36. interface
  37. {$IFDEF FPC_DOTTEDUNITS}
  38. uses
  39. System.Classes, System.SysUtils, System.StrUtils;
  40. {$ELSE FPC_DOTTEDUNITS}
  41. uses
  42. Classes, SysUtils, strutils;
  43. {$ENDIF FPC_DOTTEDUNITS}
  44. Type
  45. TCSVChar = Char;
  46. { TCSVHandler }
  47. TCSVHandler = class(TPersistent)
  48. private
  49. function GetDelimiter: TCSVChar;
  50. function GetLineEnding: String;
  51. function GetQuoteChar: TCSVChar;
  52. procedure SetDelimiter(const AValue: TCSVChar);
  53. procedure SetLineEnding(AValue: String);
  54. procedure SetQuoteChar(const AValue: TCSVChar);
  55. procedure UpdateCachedChars;
  56. protected
  57. // special chars
  58. FDelimiter: AnsiChar;
  59. FQuoteChar: AnsiChar;
  60. FLineEnding: AnsiString;
  61. // cached values to speed up special chars operations
  62. FSpecialChars: TSysCharSet;
  63. FDoubleQuote: AnsiString;
  64. // parser settings
  65. FIgnoreOuterWhitespace: Boolean;
  66. // builder settings
  67. FQuoteOuterWhitespace: Boolean;
  68. // document settings
  69. FEqualColCountPerRow: Boolean;
  70. public
  71. constructor Create; virtual;
  72. procedure Assign(ASource: TPersistent); override;
  73. procedure AssignCSVProperties(ASource: TCSVHandler);
  74. // Delimiter that separates the field, e.g. comma, semicolon, tab
  75. property Delimiter: TCSVChar read GetDelimiter write SetDelimiter;
  76. // Character used to quote "problematic" data
  77. // (e.g. with delimiters or spaces in them)
  78. // A common quotechar is "
  79. property QuoteChar: TCSVChar read GetQuoteChar write SetQuoteChar;
  80. // String at the end of the line of data (e.g. CRLF)
  81. property LineEnding: String read GetLineEnding write SetLineEnding;
  82. // Ignore whitespace between delimiters and field data
  83. property IgnoreOuterWhitespace: Boolean read FIgnoreOuterWhitespace write FIgnoreOuterWhitespace;
  84. // Use quotes when outer whitespace is found
  85. property QuoteOuterWhitespace: Boolean read FQuoteOuterWhitespace write FQuoteOuterWhitespace;
  86. // When reading and writing: make sure every line has the same column count, create empty cells in the end of row if required
  87. property EqualColCountPerRow: Boolean read FEqualColCountPerRow write FEqualColCountPerRow;
  88. end;
  89. // Sequential input from CSV stream
  90. { TCSVParser }
  91. TCSVByteOrderMark = (bomNone, bomUTF8, bomUTF16LE, bomUTF16BE);
  92. TCSVParser = class(TCSVHandler)
  93. private
  94. FFreeStream: Boolean;
  95. // fields
  96. FSourceStream: TStream;
  97. FStrStreamWrapper: TStringStream;
  98. FBOM: TCSVByteOrderMark;
  99. FDetectBOM: Boolean;
  100. // parser state
  101. EndOfFile: Boolean;
  102. EndOfLine: Boolean;
  103. FCurrentChar: AnsiChar;
  104. FCurrentRow: Integer;
  105. FCurrentCol: Integer;
  106. FMaxColCount: Integer;
  107. // output buffers
  108. FCellBuffer: RawByteString;
  109. FWhitespaceBuffer: RawByteString;
  110. procedure ClearOutput;
  111. function GetCurrentCell: String;
  112. // basic parsing
  113. procedure SkipEndOfLine;
  114. procedure SkipDelimiter;
  115. procedure SkipWhitespace;
  116. procedure NextChar;
  117. // complex parsing
  118. procedure ParseCell;
  119. procedure ParseQuotedValue;
  120. // simple parsing
  121. procedure ParseValue;
  122. public
  123. constructor Create; override;
  124. destructor Destroy; override;
  125. // Source data stream
  126. procedure SetSource(AStream: TStream); overload;
  127. // Source data string.
  128. procedure SetSource(const AString: String); overload;
  129. // Rewind to beginning of data
  130. procedure ResetParser;
  131. // Read next cell data; return false if end of file reached
  132. function ParseNextCell: Boolean;
  133. // Current row (0 based)
  134. property CurrentRow: Integer read FCurrentRow;
  135. // Current column (0 based); -1 if invalid/before beginning of file
  136. property CurrentCol: Integer read FCurrentCol;
  137. // Data in current cell
  138. property CurrentCellText: String read GetCurrentCell;
  139. // The maximum number of columns found in the stream:
  140. property MaxColCount: Integer read FMaxColCount;
  141. // Does the parser own the stream ? If true, a previous stream is freed when set or when parser is destroyed.
  142. Property FreeStream : Boolean Read FFreeStream Write FFreeStream;
  143. // Return BOM found in file
  144. property BOM: TCSVByteOrderMark read FBOM;
  145. // Detect whether a BOM marker is present. If set to True, then BOM can be used to see what BOM marker there was.
  146. property DetectBOM: Boolean read FDetectBOM write FDetectBOM default false;
  147. end;
  148. // Sequential output to CSV stream
  149. TCSVBuilder = class(TCSVHandler)
  150. private
  151. FOutputStream: TStream;
  152. FDefaultOutput: TMemoryStream;
  153. FNeedLeadingDelimiter: Boolean;
  154. function GetDefaultOutputAsString: String;
  155. protected
  156. procedure AppendStringToStream(const AString: String; AStream: TStream);
  157. function QuoteCSVString(const AValue: String): String;
  158. public
  159. constructor Create; override;
  160. destructor Destroy; override;
  161. // Set output/destination stream.
  162. // If not called, output is sent to DefaultOutput
  163. procedure SetOutput(AStream: TStream);
  164. // If using default stream, reset output to beginning.
  165. // If using user-defined stream, user should reposition stream himself
  166. procedure ResetBuilder;
  167. // Add a cell to the output with data AValue
  168. procedure AppendCell(const AValue: String);
  169. // Write end of row to the output, starting a new row
  170. procedure AppendRow;
  171. // Default output as memorystream (if output not set using SetOutput)
  172. property DefaultOutput: TMemoryStream read FDefaultOutput;
  173. // Default output in string format (if output not set using SetOutput)
  174. property DefaultOutputAsString: String read GetDefaultOutputAsString;
  175. end;
  176. function ChangeLineEndings(const AString, ALineEnding: String): String;
  177. implementation
  178. const
  179. CsvCharSize = SizeOf(TCSVChar);
  180. CR = #13;
  181. LF = #10;
  182. HTAB = #9;
  183. SPACE = #32;
  184. WhitespaceChars = [HTAB, SPACE];
  185. LineEndingChars = [CR, LF];
  186. Procedure AppendStr(Var Dest : RawByteString; Src : RawByteString); inline;
  187. begin
  188. Dest:=Dest+Src;
  189. end;
  190. procedure RemoveTrailingChars(VAR S: RawByteString; const CSet: TSysCharset);
  191. VAR I,J: LONGINT;
  192. Begin
  193. I:=Length(S);
  194. IF (I>0) Then
  195. Begin
  196. J:=I;
  197. While (j>0) and (S[J] IN CSet) DO DEC(J);
  198. IF J<>I Then
  199. SetLength(S,J);
  200. End;
  201. End;
  202. // The following implementation of ChangeLineEndings function originates from
  203. // Lazarus CodeTools library by Mattias Gaertner. It was explicitly allowed
  204. // by Mattias to relicense it under modified LGPL and include into CsvDocument.
  205. function ChangeLineEndings(const AString, ALineEnding: String): String;
  206. var
  207. I: Integer;
  208. Src: PChar;
  209. Dest: PChar;
  210. DestLength: Integer;
  211. EndingLength: Integer;
  212. EndPos: PChar;
  213. begin
  214. if AString = '' then
  215. Exit(AString);
  216. EndingLength := Length(ALineEnding);
  217. DestLength := Length(AString);
  218. Src := PChar(AString);
  219. EndPos := Src;
  220. Inc(EndPos,DestLength);
  221. while Src < EndPos do
  222. begin
  223. if (Src^ = CR) then
  224. begin
  225. Inc(Src);
  226. if (Src^ = LF) then
  227. begin
  228. Inc(Src);
  229. Inc(DestLength, EndingLength - 2);
  230. end else
  231. Inc(DestLength, EndingLength - 1);
  232. end else
  233. begin
  234. if (Src^ = LF) then
  235. Inc(DestLength, EndingLength - 1);
  236. Inc(Src);
  237. end;
  238. end;
  239. SetLength(Result, DestLength);
  240. Src := PChar(AString);
  241. Dest := PChar(Result);
  242. EndPos := Dest + DestLength;
  243. while (Dest < EndPos) do
  244. begin
  245. if Src^ in LineEndingChars then
  246. begin
  247. for I := 1 to EndingLength do
  248. begin
  249. Dest^ := ALineEnding[I];
  250. Inc(Dest);
  251. end;
  252. if (Src^ = CR) and (Src[1] = LF) then
  253. Inc(Src, 2)
  254. else
  255. Inc(Src);
  256. end else
  257. begin
  258. Dest^ := Src^;
  259. Inc(Src);
  260. Inc(Dest);
  261. end;
  262. end;
  263. end;
  264. { TCSVHandler }
  265. function TCSVHandler.GetDelimiter: TCSVChar;
  266. begin
  267. Result:=FDelimiter;
  268. end;
  269. function TCSVHandler.GetLineEnding: String;
  270. begin
  271. Result:=UTF8Decode(FLineEnding);
  272. end;
  273. function TCSVHandler.GetQuoteChar: TCSVChar;
  274. begin
  275. Result:=FQuoteChar;
  276. end;
  277. procedure TCSVHandler.SetDelimiter(const AValue: TCSVChar);
  278. begin
  279. if FDelimiter <> AValue then
  280. begin
  281. FDelimiter := AValue;
  282. UpdateCachedChars;
  283. end;
  284. end;
  285. procedure TCSVHandler.SetLineEnding(AValue: String);
  286. begin
  287. FLineEnding:=UTF8ENcode(AValue)
  288. end;
  289. procedure TCSVHandler.SetQuoteChar(const AValue: TCSVChar);
  290. begin
  291. if FQuoteChar <> AValue then
  292. begin
  293. FQuoteChar := AValue;
  294. UpdateCachedChars;
  295. end;
  296. end;
  297. procedure TCSVHandler.UpdateCachedChars;
  298. begin
  299. FDoubleQuote := FQuoteChar + FQuoteChar;
  300. FSpecialChars := [CR, LF, FDelimiter, FQuoteChar];
  301. end;
  302. constructor TCSVHandler.Create;
  303. begin
  304. inherited Create;
  305. FDelimiter := ',';
  306. FQuoteChar := '"';
  307. FLineEnding := sLineBreak;
  308. FIgnoreOuterWhitespace := False;
  309. FQuoteOuterWhitespace := True;
  310. FEqualColCountPerRow := True;
  311. UpdateCachedChars;
  312. end;
  313. procedure TCSVHandler.Assign(ASource: TPersistent);
  314. begin
  315. if (ASource is TCSVHandler) then
  316. AssignCSVProperties(ASource as TCSVHandler)
  317. else
  318. inherited Assign(ASource);
  319. end;
  320. procedure TCSVHandler.AssignCSVProperties(ASource: TCSVHandler);
  321. begin
  322. FDelimiter := ASource.FDelimiter;
  323. FQuoteChar := ASource.FQuoteChar;
  324. FLineEnding := ASource.FLineEnding;
  325. FIgnoreOuterWhitespace := ASource.FIgnoreOuterWhitespace;
  326. FQuoteOuterWhitespace := ASource.FQuoteOuterWhitespace;
  327. FEqualColCountPerRow := ASource.FEqualColCountPerRow;
  328. UpdateCachedChars;
  329. end;
  330. { TCSVParser }
  331. procedure TCSVParser.ClearOutput;
  332. begin
  333. FCellBuffer := '';
  334. FWhitespaceBuffer := '';
  335. FCurrentRow := 0;
  336. FCurrentCol := -1;
  337. FMaxColCount := 0;
  338. end;
  339. function TCSVParser.GetCurrentCell: String;
  340. begin
  341. Result:=FCellBuffer
  342. end;
  343. procedure TCSVParser.SkipEndOfLine;
  344. begin
  345. // treat LF+CR as two linebreaks, not one
  346. if (FCurrentChar = CR) then
  347. NextChar;
  348. if (FCurrentChar = LF) then
  349. NextChar;
  350. end;
  351. procedure TCSVParser.SkipDelimiter;
  352. begin
  353. if FCurrentChar = FDelimiter then
  354. NextChar;
  355. end;
  356. procedure TCSVParser.SkipWhitespace;
  357. begin
  358. while FCurrentChar = SPACE do
  359. NextChar;
  360. end;
  361. procedure TCSVParser.NextChar;
  362. begin
  363. if FSourceStream.Read(FCurrentChar, SizeOf(FCurrentChar)) < SizeOf(FCurrentChar) then
  364. begin
  365. FCurrentChar := #0;
  366. EndOfFile := True;
  367. end;
  368. EndOfLine := FCurrentChar in LineEndingChars;
  369. end;
  370. procedure TCSVParser.ParseCell;
  371. begin
  372. FCellBuffer := '';
  373. if FIgnoreOuterWhitespace then
  374. SkipWhitespace;
  375. if FCurrentChar = FQuoteChar then
  376. ParseQuotedValue
  377. else
  378. ParseValue;
  379. end;
  380. procedure TCSVParser.ParseQuotedValue;
  381. var
  382. QuotationEnd: Boolean;
  383. begin
  384. NextChar; // skip opening quotation AnsiChar
  385. repeat
  386. // read value up to next quotation AnsiChar
  387. while not ((FCurrentChar = FQuoteChar) or EndOfFile) do
  388. begin
  389. if EndOfLine then
  390. begin
  391. AppendStr(FCellBuffer, FLineEnding);
  392. SkipEndOfLine;
  393. end else
  394. begin
  395. AppendStr(FCellBuffer, FCurrentChar);
  396. NextChar;
  397. end;
  398. end;
  399. // skip quotation AnsiChar (closing or escaping)
  400. if not EndOfFile then
  401. NextChar;
  402. // check if it was escaping
  403. if FCurrentChar = FQuoteChar then
  404. begin
  405. AppendStr(FCellBuffer, FCurrentChar);
  406. QuotationEnd := False;
  407. NextChar;
  408. end else
  409. QuotationEnd := True;
  410. until QuotationEnd;
  411. // read the rest of the value until separator or new line
  412. ParseValue;
  413. end;
  414. procedure TCSVParser.ParseValue;
  415. begin
  416. while not ((FCurrentChar = FDelimiter) or EndOfLine or EndOfFile or (FCurrentChar = FQuoteChar)) do
  417. begin
  418. AppendStr(FCellBuffer, FCurrentChar);
  419. NextChar;
  420. end;
  421. if FCurrentChar = FQuoteChar then
  422. ParseQuotedValue;
  423. // merge whitespace buffer
  424. if FIgnoreOuterWhitespace then
  425. RemoveTrailingChars(FWhitespaceBuffer, WhitespaceChars);
  426. AppendStr(FWhitespaceBuffer,FCellBuffer);
  427. FWhitespaceBuffer := '';
  428. end;
  429. constructor TCSVParser.Create;
  430. begin
  431. inherited Create;
  432. ClearOutput;
  433. FStrStreamWrapper := nil;
  434. EndOfFile := True;
  435. end;
  436. destructor TCSVParser.Destroy;
  437. begin
  438. if FFreeStream and (FSourceStream<>FStrStreamWrapper) then
  439. FreeAndNil(FSourceStream);
  440. FreeAndNil(FStrStreamWrapper);
  441. inherited Destroy;
  442. end;
  443. procedure TCSVParser.SetSource(AStream: TStream);
  444. begin
  445. If FSourceStream=AStream then exit;
  446. if FFreeStream and (FSourceStream<>FStrStreamWrapper) then
  447. FreeAndNil(FSourceStream);
  448. FSourceStream := AStream;
  449. ResetParser;
  450. end;
  451. procedure TCSVParser.SetSource(const AString: String); overload;
  452. begin
  453. FreeAndNil(FStrStreamWrapper);
  454. FStrStreamWrapper := TStringStream.Create(AString);
  455. SetSource(FStrStreamWrapper);
  456. end;
  457. procedure TCSVParser.ResetParser;
  458. var
  459. b: packed array[0..2] of byte;
  460. n: Integer;
  461. begin
  462. B[0]:=0; B[1]:=0; B[2]:=0;
  463. ClearOutput;
  464. FSourceStream.Seek(0, soFromBeginning);
  465. if FDetectBOM then
  466. begin
  467. if FSourceStream.Read(b[0], 3)<3 then
  468. begin
  469. n:=0;
  470. FBOM:=bomNone;
  471. end
  472. else if (b[0] = $EF) and (b[1] = $BB) and (b[2] = $BF) then begin
  473. FBOM := bomUTF8;
  474. n := 3;
  475. end else
  476. if (b[0] = $FE) and (b[1] = $FF) then begin
  477. FBOM := bomUTF16BE;
  478. n := 2;
  479. end else
  480. if (b[0] = $FF) and (b[1] = $FE) then begin
  481. FBOM := bomUTF16LE;
  482. n := 2;
  483. end else begin
  484. FBOM := bomNone;
  485. n := 0;
  486. end;
  487. FSourceStream.Seek(n, soFromBeginning);
  488. end;
  489. EndOfFile := False;
  490. NextChar;
  491. end;
  492. // Parses next cell; returns True if there are more cells in the input stream.
  493. function TCSVParser.ParseNextCell: Boolean;
  494. var
  495. LineColCount: Integer;
  496. begin
  497. if EndOfLine or EndOfFile then
  498. begin
  499. // Having read the previous line, adjust column count if necessary:
  500. LineColCount := FCurrentCol + 1;
  501. if LineColCount > FMaxColCount then
  502. FMaxColCount := LineColCount;
  503. end;
  504. if EndOfFile then
  505. Exit(False);
  506. // Handle line ending
  507. if EndOfLine then
  508. begin
  509. SkipEndOfLine;
  510. if EndOfFile then
  511. Exit(False);
  512. FCurrentCol := 0;
  513. Inc(FCurrentRow);
  514. end else
  515. Inc(FCurrentCol);
  516. // Skipping a delimiter should be immediately followed by parsing a cell
  517. // without checking for line break first, otherwise we miss last empty cell.
  518. // But 0th cell does not start with delimiter unlike other cells, so
  519. // the following check is required not to miss the first empty cell:
  520. if FCurrentCol > 0 then
  521. SkipDelimiter;
  522. ParseCell;
  523. Result := True;
  524. end;
  525. { TCSVBuilder }
  526. function TCSVBuilder.GetDefaultOutputAsString: String;
  527. var
  528. StreamSize: Integer;
  529. begin
  530. Result := '';
  531. StreamSize := FDefaultOutput.Size;
  532. if StreamSize > 0 then
  533. begin
  534. SetLength(Result, StreamSize);
  535. FDefaultOutput.Position:=0;
  536. FDefaultOutput.ReadBuffer(Result[1], StreamSize);
  537. end;
  538. end;
  539. procedure TCSVBuilder.AppendStringToStream(const AString: String; AStream: TStream);
  540. var
  541. StrLen: Integer;
  542. S : AnsiString;
  543. begin
  544. S:=aString;
  545. StrLen := Length(S);
  546. if StrLen > 0 then
  547. AStream.WriteBuffer(S[1], StrLen);
  548. end;
  549. function TCSVBuilder.QuoteCSVString(const AValue: String): String;
  550. var
  551. I: Integer;
  552. ValueLen: Integer;
  553. NeedQuotation: Boolean;
  554. S : String;
  555. begin
  556. ValueLen := Length(AValue);
  557. NeedQuotation := (AValue <> '') and FQuoteOuterWhitespace
  558. and ((AValue[1] in WhitespaceChars) or (AValue[ValueLen] in WhitespaceChars));
  559. if not NeedQuotation then
  560. for I := 1 to ValueLen do
  561. begin
  562. if AValue[I] in FSpecialChars then
  563. begin
  564. NeedQuotation := True;
  565. Break;
  566. end;
  567. end;
  568. if NeedQuotation then
  569. begin
  570. // double existing quotes
  571. Result := FDoubleQuote;
  572. S:=StringReplace(AValue, FQuoteChar, FDoubleQuote, [rfReplaceAll]);
  573. Insert(S,Result, 2);
  574. end else
  575. Result := AValue;
  576. end;
  577. constructor TCSVBuilder.Create;
  578. begin
  579. inherited Create;
  580. FDefaultOutput := TMemoryStream.Create;
  581. FOutputStream := FDefaultOutput;
  582. end;
  583. destructor TCSVBuilder.Destroy;
  584. begin
  585. FreeAndNil(FDefaultOutput);
  586. inherited Destroy;
  587. end;
  588. procedure TCSVBuilder.SetOutput(AStream: TStream);
  589. begin
  590. if Assigned(AStream) then
  591. FOutputStream := AStream
  592. else
  593. FOutputStream := FDefaultOutput;
  594. ResetBuilder;
  595. end;
  596. procedure TCSVBuilder.ResetBuilder;
  597. begin
  598. if FOutputStream = FDefaultOutput then
  599. FDefaultOutput.Clear;
  600. // Do not clear external FOutputStream because it may be pipe stream
  601. // or something else that does not support size and position.
  602. // To clear external output is up to the user of TCSVBuilder.
  603. FNeedLeadingDelimiter := False;
  604. end;
  605. procedure TCSVBuilder.AppendCell(const AValue: String);
  606. var
  607. CellValue: String;
  608. begin
  609. if FNeedLeadingDelimiter then
  610. FOutputStream.WriteBuffer(FDelimiter, SizeOf(FDelimiter));
  611. CellValue := ChangeLineEndings(AValue, FLineEnding);
  612. CellValue := QuoteCSVString(CellValue);
  613. AppendStringToStream(CellValue, FOutputStream);
  614. FNeedLeadingDelimiter := True;
  615. end;
  616. procedure TCSVBuilder.AppendRow;
  617. begin
  618. AppendStringToStream(FLineEnding, FOutputStream);
  619. FNeedLeadingDelimiter := False;
  620. end;
  621. end.