From: Ardis Lu Date: Sun, 19 Jan 2025 07:52:59 +0000 (-0800) Subject: Refactor createCSVDenormalizationStream to CSVDenormalizer X-Git-Tag: v0.1.0~111 X-Git-Url: https://git.ardis.lu/?a=commitdiff_plain;h=d775f7e58200c5753d8cafdd02f044a2739f9da5;p=csvio Refactor createCSVDenormalizationStream to CSVDenormalizer --- diff --git a/README.md b/README.md index e11b450..8011519 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ const writableStream = new CSVWriter('./data/example-out.csv'); In addition to the core API, optional utilities are provided to address common CSV transformation tasks. -### `CSVNormalizer` and `createCSVDenormalizationStream` +### `CSVNormalizer` and `CSVDenormalizer` `TransformStream`s to parse CSVs that have been mangled by spreadsheet programs such as Excel. diff --git a/examples/README.md b/examples/README.md index c27eddd..e9fec20 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,7 +16,7 @@ node ./examples/ex1.js [Example 3: Chaining multiple transformations](./ex3.js) -[Example 4: Using `createCSVNormalizationStream` and `createCSVDenormalizationStream`](./ex4.js) +[Example 4: Using `CSVNormalizer` and `CSVDenormalizer`](./ex4.js) [Example 5: Handling an irregularly-shaped CSV file](./ex5.js) diff --git a/examples/ex4.js b/examples/ex4.js index 717b2fe..072edc5 100644 --- a/examples/ex4.js +++ b/examples/ex4.js @@ -1,4 +1,4 @@ -// ## Example 4: Using `CSVNormalizer` and `createCSVDenormalizationStream` +// ## Example 4: Using `CSVNormalizer` and `CSVDenormalizer` // // These functions fix common CSV data mangling caused by Excel. In `ex4-in.csv`: // - The "Index" column has been mangled into Excel's "Accounting" format @@ -6,9 +6,9 @@ // - The "Join Timestamp" column has been mangled into a number // - There are trailing empty rows after deleting excess data from the CSV // `CSVNormalizer` performs transformations to un-mangle the above using only a declaration of the header names and data types. -// `createCSVDenormalizationStream` converts the data stream back into a CSV row. +// `CSVDenormalizer` converts the data stream back into a CSV row. -import { CSVReader, CSVWriter, CSVNormalizer, createCSVDenormalizationStream } from '../src/index.js'; +import { CSVReader, CSVWriter, CSVNormalizer, CSVDenormalizer } from '../src/index.js'; // The order of items in `headers` is significant, it determines the order of the output CSV columns. // If an input column is not provided in `headers`, the column is removed from the output CSV. @@ -32,5 +32,5 @@ const headers = [ await new CSVReader(new URL('./data/ex4-in.csv', import.meta.url)) .pipeThrough(new CSVNormalizer(headers)) - .pipeThrough(createCSVDenormalizationStream()) + .pipeThrough(new CSVDenormalizer()) .pipeTo(new CSVWriter(new URL('./data/ex4-out.csv', import.meta.url))); \ No newline at end of file diff --git a/src/index.js b/src/index.js index b5c4138..22441a5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,2 @@ export { CSVReader, CSVTransformer, CSVWriter } from './core.js'; -export { CSVNormalizer, createCSVDenormalizationStream } from './normalization.js'; +export { CSVNormalizer, CSVDenormalizer } from './normalization.js'; diff --git a/src/normalization.js b/src/normalization.js index 6b78111..0c463aa 100644 --- a/src/normalization.js +++ b/src/normalization.js @@ -286,20 +286,21 @@ export class CSVNormalizer extends TransformStream { } /** - * Create a `TransformStream` to transform a `CSVNormalizerRow` into an array that can be converted to CSV data. - * - * @returns {TransformStream} A `TransformStream` where each chunk is one row of the CSV file. + * A `TransformStream` to transform a `CSVNormalizerRow` into an array that can be converted to CSV data. + * @extends TransformStream */ -export function createCSVDenormalizationStream() { - let firstRow = true; - return new TransformStream({ - transform(chunk, controller) { - const row = JSON.parse(chunk); - if (firstRow) { - firstRow = false; - controller.enqueue(JSON.stringify(row.map(f => f.displayName))); +export class CSVDenormalizer extends TransformStream { + constructor() { + let firstRow = true; + super({ + transform(chunk, controller) { + const row = JSON.parse(chunk); + if (firstRow) { + firstRow = false; + controller.enqueue(JSON.stringify(row.map(f => f.displayName))); + } + controller.enqueue(JSON.stringify(row.map(f => f.value))); } - controller.enqueue(JSON.stringify(row.map(f => f.value))); - } - }); + }); + } } diff --git a/test/normalization.test.js b/test/normalization.test.js index 34f734d..dba486a 100644 --- a/test/normalization.test.js +++ b/test/normalization.test.js @@ -3,7 +3,7 @@ import { deepStrictEqual } from 'node:assert/strict'; import { csvStreamEqual, csvStreamEqualWritable, createCSVMockStream } from './utils.js'; import { CSVReader } from '../src/core.js'; -import { toCamelCase, expandScientificNotation, fixExcelNumber, fixExcelBigInt, fixExcelDate, CSVNormalizer, createCSVDenormalizationStream } from '../src/normalization.js'; +import { toCamelCase, expandScientificNotation, fixExcelNumber, fixExcelBigInt, fixExcelDate, CSVNormalizer, CSVDenormalizer } from '../src/normalization.js'; suite('toCamelCase', { concurrency: true }, () => { test('does not modify strings already in camelCase', { concurrency: true }, () => { @@ -179,7 +179,7 @@ suite('CSVNormalizer', { concurrency: true }, () => { { name: 'columnA', type: 'string' }, { name: 'columnB', type: 'string' } ])) - .pipeThrough(createCSVDenormalizationStream()) + .pipeThrough(new CSVDenormalizer()) .pipeTo(csvStreamEqualWritable([ ['columnA', 'columnB'], ['a', 'b'] @@ -195,7 +195,7 @@ suite('CSVNormalizer', { concurrency: true }, () => { { name: 'columnB', type: 'string' }, { name: 'columnC', type: 'string' } ])) - .pipeThrough(createCSVDenormalizationStream()) + .pipeThrough(new CSVDenormalizer()) .pipeTo(csvStreamEqualWritable([ ['columnA', 'columnB', 'columnC'], ['a', 'b', 'c'] @@ -210,7 +210,7 @@ suite('CSVNormalizer', { concurrency: true }, () => { { name: 'columnA', type: 'string', displayName: 'Column A' }, { name: 'columnB', type: 'string', displayName: 'Column B' } ])) - .pipeThrough(createCSVDenormalizationStream()) + .pipeThrough(new CSVDenormalizer()) .pipeTo(csvStreamEqualWritable([ ['Column A', 'Column B'], ['a', 'b'] @@ -229,7 +229,7 @@ suite('CSVNormalizer', { concurrency: true }, () => { { name: 'columnA', type: 'string' }, { name: 'columnB', type: 'string' } ])) - .pipeThrough(createCSVDenormalizationStream()) + .pipeThrough(new CSVDenormalizer()) .pipeTo(csvStreamEqualWritable([ ['columnA', 'columnB'], ['a', 'b'] @@ -244,7 +244,7 @@ suite('CSVNormalizer', { concurrency: true }, () => { { name: 'columnA', type: 'abc' }, { name: 'columnB', type: 'def' } ])) - .pipeThrough(createCSVDenormalizationStream()) + .pipeThrough(new CSVDenormalizer()) .pipeTo(csvStreamEqualWritable([ ['columnA', 'columnB'], ['a', 'b'] @@ -252,7 +252,7 @@ suite('CSVNormalizer', { concurrency: true }, () => { }); }); -suite('CSVNormalizer and createCSVDenormalizationStream end-to-end', { concurrency: true }, () => { +suite('CSVNormalizer and CSVDenormalizer end-to-end', { concurrency: true }, () => { test('can normalize', { concurrency: true }, async () => { const headers = [ { name: 'stringCol', displayName: 'String Column', type: 'string', defaultValue: 'N/A' }, @@ -262,7 +262,7 @@ suite('CSVNormalizer and createCSVDenormalizationStream end-to-end', { concurren ] const stream = new CSVReader('./test/data/normalization.csv') .pipeThrough(new CSVNormalizer(headers)) - .pipeThrough(createCSVDenormalizationStream()); + .pipeThrough(new CSVDenormalizer()); await csvStreamEqual(stream, [ ['String Column', 'Number Column', 'BigInt Column', 'Date Column'], ['abc 👨‍👩‍👧‍👦', 123456789.123456, '1000000000000000000', '2024-01-01T00:00:00.000Z'],