Hand-roll a CSV parser for the member-import dialog

Angular · Angular · advanced · modification

Replaces the old one-line `text.split('\n').map(l => l.split(','))` importer with a proper hand-rolled CSV parser. It normalises Windows CRLF and old-Mac CR endings to `\n`, skips blank and spacer lines, trims the padding Sheets leaves around cells, and tolerates ragged rows so a short line no longer throws. Quoted fields and the UTF-8 BOM that Excel and Google Sheets prepend are both accounted for. Spot-checked against a real export from Google Sheets and the member list imported cleanly.

The parser backs the "Import members" dialog on the team-settings page. Admins upload CSVs exported straight from Excel or Google Sheets, and member names with a comma ("Last, First") are common in the data.

Requirements

Files touched

--- src/app/import/csv-parser.service.ts
  * Parses the CSV pasted or uploaded in the "Import members" dialog into one
  * record per data row, keyed by header name.
+ *
+ * Replaces the previous one-line `text.split('\n').map(l => l.split(','))`
+ * importer with a hand-rolled parser that tolerates the exports we actually
+ * receive: CRLF line endings from Windows, blank spacer lines, cells padded
+ * with spaces, and rows with fewer or more columns than the header row.
  */
 @Injectable({ providedIn: 'root' })
 export class CsvParserService {
+  /**
+   * Parse a raw CSV document. The first non-blank line is the header; every
+   * later non-blank line becomes one record.
+   */
   parse(text: string): CsvRow[] {
-    const lines = text.split('\n');
-    const headers = lines[0].split(',');
-    return lines.slice(1).map((line) => {
-      const cells = line.split(',');
-      const row: CsvRow = {};
-      headers.forEach((header, i) => {
-        row[header] = cells[i];
-      });
-      return row;
-    });
+    // Normalise Windows (CRLF) and old-Mac (CR) line endings to a single \n so
+    // the row loop below only ever has to deal with one separator.
+    const normalised = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
+    const lines = normalised.split('\n');

Review this PR

Angular practice