@ztimson/utils 0.23.15 → 0.23.16

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/csv.d.ts CHANGED
@@ -1,9 +1,16 @@
1
+ /**
2
+ * Parse a CSV string into an array of objects
3
+ *
4
+ * @param csv String with CSV
5
+ * @param hasHeaders First line of CSV contains headers
6
+ * @return {T[]} Array of parsed objects
7
+ */
1
8
  export declare function fromCsv<T = any>(csv: string, hasHeaders?: boolean): T[];
2
9
  /**
3
- * Convert an object to a CSV string
10
+ * Convert an array of objects to a CSV string
4
11
  *
5
12
  * @param {any[]} target Array of objects to create CSV from
6
13
  * @param {boolean} flatten Should nested object be flattened or treated as values
7
14
  * @return {string} CSV string
8
15
  */
9
- export declare function toCsv(target: any[], flatten?: boolean): string;
16
+ export declare function toCsv(target: any, flatten?: boolean): string;
package/dist/index.cjs CHANGED
@@ -688,24 +688,29 @@ ${opts.message || this.desc}`;
688
688
  return /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(email);
689
689
  }
690
690
  function fromCsv(csv, hasHeaders = true) {
691
- const row = csv.split("\n");
691
+ function parseLine(line) {
692
+ const columns = [];
693
+ let current = "", inQuotes = false;
694
+ for (let i = 0; i < line.length; i++) {
695
+ const char = line[i];
696
+ const nextChar = line[i + 1];
697
+ if (char === '"') {
698
+ if (inQuotes && nextChar === '"') {
699
+ current += '"';
700
+ i++;
701
+ } else inQuotes = !inQuotes;
702
+ } else if (char === "," && !inQuotes) {
703
+ columns.push(current);
704
+ current = "";
705
+ } else current += char;
706
+ }
707
+ columns.push(current);
708
+ return columns.map((col) => col.replace(/^"|"$/g, "").replace(/""/g, '"'));
709
+ }
710
+ const row = csv.split(/\r?\n/);
692
711
  let headers = hasHeaders ? row.splice(0, 1)[0] : null;
693
- if (headers) headers = headers.match(/(?:[^,"']+|"[^"]*"|'[^']*')+/g);
712
+ if (headers) headers = headers.match(/(?:[^,"']+|"(?:[^"]|"")*"|'(?:[^']|'')*')+/g);
694
713
  return row.map((r2) => {
695
- function parseLine(line) {
696
- const parts = line.split(","), columns = [];
697
- let quoted = false;
698
- for (const p2 of parts) {
699
- if (quoted) columns[columns.length - 1] = columns.at(-1) + "," + p2;
700
- else columns.push(p2);
701
- if (/[^"]"$/g.test(p2)) {
702
- quoted = false;
703
- } else if (/^"[^"]/g.test(p2)) {
704
- quoted = true;
705
- }
706
- }
707
- return columns;
708
- }
709
714
  const props = parseLine(r2);
710
715
  const h = headers || Array(props.length).fill(null).map((r22, i) => {
711
716
  let letter = "";
@@ -721,12 +726,16 @@ ${opts.message || this.desc}`;
721
726
  });
722
727
  }
723
728
  function toCsv(target, flatten = true) {
724
- const headers = new ASet(target.reduce((acc, row) => [...acc, ...Object.keys(flatten ? flattenObj(row) : row)], []));
729
+ const t = makeArray(target);
730
+ const headers = new ASet(t.reduce((acc, row) => [...acc, ...Object.keys(flatten ? flattenObj(row) : row)], []));
725
731
  return [
726
732
  headers.join(","),
727
- ...target.map((row) => headers.map((h) => {
733
+ ...t.map((row) => headers.map((h) => {
728
734
  const value2 = dotNotation(row, h);
729
- return typeof value2 == "object" && value2 != null ? '"' + JSONSanitize(value2).replaceAll('"', '""') + '"' : value2;
735
+ if (value2 == null) return "";
736
+ if (typeof value2 == "object") return `"${JSONSanitize(value2).replaceAll("`", '""')}"`;
737
+ if (typeof value2 == "string" && /[\n"]/g.test(value2)) return `"${value2.replaceAll('"', '""')}"`;
738
+ return value2;
730
739
  }).join(","))
731
740
  ].join("\n");
732
741
  }