Compare commits
2 Commits
ab17c0b044
...
79ddec7d4d
| Author | SHA1 | Date | |
|---|---|---|---|
| 79ddec7d4d | |||
| 9d054e6817 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@techniker-me/csv",
|
"name": "@techniker-me/csv",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"module": "src/index.ts",
|
"module": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
||||||
@@ -39,5 +39,6 @@
|
|||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"typescript": "5.9.2",
|
"typescript": "5.9.2",
|
||||||
"typescript-eslint": "8.40.0"
|
"typescript-eslint": "8.40.0"
|
||||||
}
|
},
|
||||||
|
"files": ["dist"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export type {
|
|||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export { CsvDialect } from "./dialect";
|
export { CsvDialect } from "./dialect";
|
||||||
|
export { CsvRowTokenizer } from "./parser/CsvRowTokenizer";
|
||||||
export { CsvParser } from "./parser/CsvParser";
|
export { CsvParser } from "./parser/CsvParser";
|
||||||
|
|
||||||
export { StringChunkSource } from "./sources/StringSource";
|
export { StringChunkSource } from "./sources/StringSource";
|
||||||
|
|||||||
106
src/parser/CsvParser.ts
Normal file
106
src/parser/CsvParser.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { CsvDialect } from "../dialect";
|
||||||
|
import type {
|
||||||
|
CsvParseResult,
|
||||||
|
CsvParseResultArray,
|
||||||
|
CsvParseResultObject,
|
||||||
|
ICsvDialect,
|
||||||
|
IChunkSource,
|
||||||
|
ParseOptions,
|
||||||
|
RowArray,
|
||||||
|
RowObject,
|
||||||
|
} from "../types";
|
||||||
|
import { CsvRowTokenizer } from "./CsvRowTokenizer";
|
||||||
|
|
||||||
|
export interface ICsvParser {
|
||||||
|
parseFromString(input: string, options?: ParseOptions): Promise<CsvParseResult>;
|
||||||
|
parseFromSource(source: IChunkSource, options?: ParseOptions): Promise<CsvParseResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CsvParser implements ICsvParser {
|
||||||
|
public async parseFromString(input: string, options?: ParseOptions): Promise<CsvParseResult> {
|
||||||
|
const sourceModule = await import("../sources/StringSource");
|
||||||
|
const source = new sourceModule.StringChunkSource(input);
|
||||||
|
return this.parseFromSource(source, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async parseFromSource(source: IChunkSource, options?: ParseOptions): Promise<CsvParseResult> {
|
||||||
|
const dialect = this.createDialect(options);
|
||||||
|
const tokenizer = new CsvRowTokenizer(dialect);
|
||||||
|
|
||||||
|
for await (const chunk of source.chunks()) {
|
||||||
|
tokenizer.pushChunk(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = tokenizer.drain();
|
||||||
|
const sanitizedRows = this.postProcessRows(rows, options);
|
||||||
|
|
||||||
|
if ((options?.output ?? "array") === "object") {
|
||||||
|
return this.mapToObjects(sanitizedRows, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: CsvParseResultArray = {
|
||||||
|
headers: options?.hasHeader ? sanitizedRows[0] : undefined,
|
||||||
|
rows: options?.hasHeader ? sanitizedRows.slice(1) : sanitizedRows,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createDialect(options?: ParseOptions): ICsvDialect {
|
||||||
|
const dialect = new CsvDialect(options?.dialect);
|
||||||
|
return dialect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private postProcessRows(rows: RowArray[], options?: ParseOptions): RowArray[] {
|
||||||
|
const validate = options?.validateRowLength ?? true;
|
||||||
|
const skipEmpty = options?.skipEmptyLines ?? true;
|
||||||
|
|
||||||
|
const filtered = rows.filter((row) => {
|
||||||
|
if (!skipEmpty) return true;
|
||||||
|
return row.length > 1 || (row.length === 1 && row[0] !== "");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!validate || filtered.length === 0) return filtered;
|
||||||
|
|
||||||
|
const targetLength = filtered[0]!.length;
|
||||||
|
for (const row of filtered) {
|
||||||
|
if (row.length !== targetLength) {
|
||||||
|
if (row.length < targetLength) {
|
||||||
|
while (row.length < targetLength) row.push("");
|
||||||
|
} else {
|
||||||
|
row.length = targetLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToObjects(rows: RowArray[], options?: ParseOptions): CsvParseResultObject {
|
||||||
|
const hasHeader = options?.hasHeader ?? true;
|
||||||
|
const headers = hasHeader ? rows[0] ?? [] : this.generateHeaders(rows[0]?.length ?? 0);
|
||||||
|
const rowStartIndex = hasHeader ? 1 : 0;
|
||||||
|
const objects: RowObject[] = [];
|
||||||
|
|
||||||
|
for (let index = rowStartIndex; index < rows.length; index += 1) {
|
||||||
|
const row = rows[index]!;
|
||||||
|
const obj: RowObject = {};
|
||||||
|
for (let c = 0; c < headers.length; c += 1) {
|
||||||
|
const key = headers[c] ?? `col${c}`;
|
||||||
|
obj[key] = row[c] ?? "";
|
||||||
|
}
|
||||||
|
objects.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { headers, rows: objects };
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateHeaders(length: number): string[] {
|
||||||
|
const headers: string[] = [];
|
||||||
|
for (let i = 0; i < length; i += 1) {
|
||||||
|
headers.push(`col${i}`);
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user