@jxsuite/parser 1.6.0 → 1.7.0

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.js CHANGED
@@ -116,6 +116,101 @@ function parseCSV(csv) {
116
116
  }
117
117
  return rows;
118
118
  }
119
+ function scanRows(csv) {
120
+ const rows = [];
121
+ let start = 0;
122
+ let inQuotes = false;
123
+ for (let i = 0;i < csv.length; i++) {
124
+ const ch = csv[i];
125
+ if (ch === '"') {
126
+ if (inQuotes && csv[i + 1] === '"') {
127
+ i += 1;
128
+ } else {
129
+ inQuotes = !inQuotes;
130
+ }
131
+ } else if ((ch === `
132
+ ` || ch === "\r" && csv[i + 1] === `
133
+ `) && !inQuotes) {
134
+ rows.push({ end: i, start });
135
+ if (ch === "\r") {
136
+ i += 1;
137
+ }
138
+ start = i + 1;
139
+ }
140
+ }
141
+ if (csv.slice(start).trim()) {
142
+ rows.push({ end: csv.length, start });
143
+ }
144
+ return rows;
145
+ }
146
+ function scanRowFields(csv, row, span) {
147
+ const line = csv.slice(span.start, span.end);
148
+ const fields = [];
149
+ let fieldStart = 0;
150
+ let value = "";
151
+ let inQuotes = false;
152
+ for (let i = 0;i < line.length; i++) {
153
+ const ch = line[i];
154
+ if (ch === '"') {
155
+ if (inQuotes && line[i + 1] === '"') {
156
+ value += '"';
157
+ i += 1;
158
+ } else {
159
+ inQuotes = !inQuotes;
160
+ }
161
+ } else if (ch === "," && !inQuotes) {
162
+ fields.push({
163
+ end: span.start + i,
164
+ row,
165
+ start: span.start + fieldStart,
166
+ value: value.trim()
167
+ });
168
+ value = "";
169
+ fieldStart = i + 1;
170
+ } else {
171
+ value += ch;
172
+ }
173
+ }
174
+ fields.push({ end: span.end, row, start: span.start + fieldStart, value: value.trim() });
175
+ return fields;
176
+ }
177
+ function scanFields(csv) {
178
+ return scanRows(csv).flatMap((span, row) => scanRowFields(csv, row, span));
179
+ }
180
+ function encodeField(raw, value) {
181
+ const lead = raw.slice(0, raw.length - raw.trimStart().length);
182
+ const trail = raw.slice(raw.trimEnd().length);
183
+ const core = raw.trim();
184
+ const wasQuoted = core.length >= 2 && core.startsWith('"') && core.endsWith('"');
185
+ const mustQuote = /["\r\n,]/.test(value) || value.trim() !== value;
186
+ return lead + (wasQuoted || mustQuote ? `"${value.replaceAll('"', '""')}"` : value) + trail;
187
+ }
188
+ function rewriteCSV(source, edits) {
189
+ const replacements = new Map;
190
+ for (const edit of edits) {
191
+ if (edit.from !== edit.to) {
192
+ replacements.set(edit.from, edit.to);
193
+ }
194
+ }
195
+ if (replacements.size === 0) {
196
+ return source;
197
+ }
198
+ let out = "";
199
+ let cursor = 0;
200
+ for (const field of scanFields(source)) {
201
+ if (field.row === 0) {
202
+ continue;
203
+ }
204
+ const to = replacements.get(field.value);
205
+ if (to === undefined) {
206
+ continue;
207
+ }
208
+ out += source.slice(cursor, field.start);
209
+ out += encodeField(source.slice(field.start, field.end), to);
210
+ cursor = field.end;
211
+ }
212
+ return out + source.slice(cursor);
213
+ }
119
214
  function coerceCSVRows(rows, schema, idField) {
120
215
  return rows.map((row, i) => {
121
216
  const data = { ...row };
@@ -154,6 +249,9 @@ class Csv {
154
249
  static parse(source, options = {}) {
155
250
  return coerceCSVRows(parseCSV(source), options.schema, options.idField);
156
251
  }
252
+ static rewrite(source, edits = []) {
253
+ return rewriteCSV(source, edits);
254
+ }
157
255
  static async discover(source, options = {}) {
158
256
  const { existsSync, readdirSync } = await import("node:fs");
159
257
  const { resolve, extname } = await import("node:path");
@@ -191,5 +289,6 @@ class Csv {
191
289
  export {
192
290
  Csv,
193
291
  coerceCSVRows,
194
- parseCSV
292
+ parseCSV,
293
+ rewriteCSV
195
294
  };