| 612 | * preserved — quoting protects commas, which survive the trim. |
| 613 | */ |
| 614 | export function splitQuotedCommaList(input: string): string[] { |
| 615 | const fields: string[] = []; |
| 616 | let buf = ""; |
| 617 | let inQuotes = false; |
| 618 | |
| 619 | for (let i = 0; i < input.length; i++) { |
| 620 | const ch = input[i]; |
| 621 | |
| 622 | if (inQuotes) { |
| 623 | if (isDoubleQuote(ch)) { |
| 624 | if (isDoubleQuote(input[i + 1])) { |
| 625 | // Doubled quote -> one literal straight quote. |
| 626 | buf += '"'; |
| 627 | i++; |
| 628 | continue; |
| 629 | } |
| 630 | // Candidate close: valid only if followed by ws* then `,`/EOF. |
| 631 | let j = i + 1; |
| 632 | while (j < input.length && HORIZONTAL_WS.test(input[j])) j++; |
| 633 | if (j >= input.length || input[j] === ",") { |
| 634 | inQuotes = false; |
| 635 | i = j - 1; // skip the trailing whitespace up to the delimiter |
| 636 | continue; |
| 637 | } |
| 638 | // Quote closed by other text -> not real quoting; keep legacy. |
| 639 | return input.split(","); |
| 640 | } |
| 641 | buf += ch; // commas (and everything else) are literal inside quotes |
| 642 | continue; |
| 643 | } |
| 644 | |
| 645 | if (ch === ",") { |
| 646 | fields.push(buf); |
| 647 | buf = ""; |
| 648 | continue; |
| 649 | } |
| 650 | if (isDoubleQuote(ch) && buf.trim() === "") { |
| 651 | // Opening quote: drop any leading whitespace already buffered. |
| 652 | inQuotes = true; |
| 653 | buf = ""; |
| 654 | continue; |
| 655 | } |
| 656 | buf += ch; |
| 657 | } |
| 658 | |
| 659 | if (inQuotes) return input.split(","); // unterminated quote -> legacy |
| 660 | fields.push(buf); |
| 661 | return fields; |
| 662 | } |
| 663 | |
| 664 | /** |
| 665 | * Strip a single surrounding double-quote pair from one value, applying the same |