csv-stream-lite 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/parser.d.ts CHANGED
@@ -9,8 +9,12 @@ export interface CsvEntityOptions {
9
9
  separator?: string;
10
10
  /** Character used to escape special characters. Defaults to '"' */
11
11
  escapeChar?: string;
12
+ /** Character used to quote fields. Defaults to escapeChar value */
13
+ quoteChar?: string;
12
14
  /** String used to denote new lines. Defaults to auto-detected '\r', '\n', or '\r\n' */
13
15
  newline?: string;
16
+ /** Whether to trim whitespace from fields. Defaults to false. NOTE: this option is not supported when streaming, as trimming requires buffering the entire field. */
17
+ trim?: boolean;
14
18
  }
15
19
  /**
16
20
  * Abstract base class for CSV entities that supports both synchronous and asynchronous parsing.
@@ -23,7 +27,9 @@ export declare abstract class CsvEntity<T, S = T> {
23
27
  byteBuffer: ByteBuffer;
24
28
  separator: string;
25
29
  escapeChar: string;
30
+ quoteChar: string;
26
31
  newline?: string;
32
+ trim: boolean;
27
33
  consumed: boolean;
28
34
  /**
29
35
  * Creates a new CSV entity.
package/dist/parser.js CHANGED
@@ -31,7 +31,9 @@ export class CsvEntity {
31
31
  byteBuffer;
32
32
  separator = ',';
33
33
  escapeChar = '"';
34
+ quoteChar = '"';
34
35
  newline;
36
+ trim = false;
35
37
  consumed = false;
36
38
  /**
37
39
  * Creates a new CSV entity.
@@ -50,6 +52,16 @@ export class CsvEntity {
50
52
  if (options?.escapeChar) {
51
53
  this.escapeChar = options.escapeChar;
52
54
  }
55
+ if (options?.quoteChar) {
56
+ this.quoteChar = options.quoteChar;
57
+ }
58
+ else if (options?.escapeChar) {
59
+ // Default quoteChar to escapeChar if only escapeChar is specified
60
+ this.quoteChar = options.escapeChar;
61
+ }
62
+ if (options?.trim !== undefined) {
63
+ this.trim = options.trim;
64
+ }
53
65
  if (options?.newline !== undefined) {
54
66
  const newline = options.newline;
55
67
  if (newline === '') {
@@ -61,6 +73,9 @@ export class CsvEntity {
61
73
  if (newline.includes(this.escapeChar)) {
62
74
  throw new Error('Invalid CSV newline: newline option must not contain the escape character.');
63
75
  }
76
+ if (newline.includes(this.quoteChar)) {
77
+ throw new Error('Invalid CSV newline: newline option must not contain the quote character.');
78
+ }
64
79
  this.newline = newline;
65
80
  }
66
81
  }
@@ -230,14 +245,14 @@ export class CsvCell extends CsvEntity {
230
245
  for (const part of this) {
231
246
  str += part;
232
247
  }
233
- return str;
248
+ return this.trim ? str.trim() : str;
234
249
  }
235
250
  async parseAsync() {
236
251
  let str = '';
237
252
  for await (const part of this) {
238
253
  str += part;
239
254
  }
240
- return str;
255
+ return this.trim ? str.trim() : str;
241
256
  }
242
257
  /**
243
258
  * Reads the cell value and transforms it using the provided function.
@@ -272,22 +287,23 @@ export class CsvCell extends CsvEntity {
272
287
  *streamImpl() {
273
288
  const separator = this.separator.charCodeAt(0);
274
289
  const escapeChar = this.escapeChar.charCodeAt(0);
290
+ const quoteChar = this.quoteChar.charCodeAt(0);
275
291
  let chunk = [];
276
292
  let hadData = false;
277
- let isEscaped = false;
293
+ let isQuoted = false;
278
294
  const next = this.byteBuffer.peek();
279
295
  if (next === null && this.byteBuffer.eof) {
280
296
  throw new Error('No more data to read');
281
297
  }
282
- if (next === escapeChar) {
283
- isEscaped = true;
284
- this.byteBuffer.expect(escapeChar); // consume opening quote
298
+ if (next === quoteChar) {
299
+ isQuoted = true;
300
+ this.byteBuffer.expect(quoteChar); // consume opening quote
285
301
  }
286
302
  while (this.byteBuffer.peek() !== null) {
287
303
  const next = this.byteBuffer.peek();
288
- if (isEscaped) {
289
- if (next === escapeChar) {
290
- // Possible end of quoted cell
304
+ if (isQuoted) {
305
+ if (next === escapeChar && escapeChar === quoteChar) {
306
+ // Standard CSV: quote char doubles as escape char
291
307
  const lookahead = this.byteBuffer.peek(1);
292
308
  if (lookahead === escapeChar) {
293
309
  // Escaped quote
@@ -301,6 +317,20 @@ export class CsvCell extends CsvEntity {
301
317
  break;
302
318
  }
303
319
  }
320
+ else if (next === quoteChar) {
321
+ // End of quoted cell when using separate quote/escape chars
322
+ break;
323
+ }
324
+ else if (next === escapeChar) {
325
+ // Handle escape character when different from quote char
326
+ const lookahead = this.byteBuffer.peek(1);
327
+ if (lookahead !== null) {
328
+ this.byteBuffer.expect(escapeChar); // consume escape char
329
+ const escapedChar = this.byteBuffer.next(); // consume escaped char
330
+ chunk.push(escapedChar);
331
+ continue;
332
+ }
333
+ }
304
334
  }
305
335
  else {
306
336
  if (next === separator || this.isAtLineEnd()) {
@@ -315,8 +345,8 @@ export class CsvCell extends CsvEntity {
315
345
  hadData = true;
316
346
  }
317
347
  }
318
- if (isEscaped) {
319
- this.byteBuffer.expect(escapeChar); // consume closing quote
348
+ if (isQuoted) {
349
+ this.byteBuffer.expect(quoteChar); // consume closing quote
320
350
  }
321
351
  if (this.byteBuffer.peek() === separator) {
322
352
  this.byteBuffer.expect(separator); // consume separator
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "csv-stream-lite",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "A lightweight, memory-efficient, zero-dependency streaming CSV parser and stringifier for JavaScript and TypeScript. It works in both Node.js and browser environments.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",