All files / webview/csv parser.ts

98.55% Statements 68/69
94.73% Branches 36/38
100% Functions 7/7
100% Lines 63/63

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106            66x 66x 66x 66x 66x 66x   66x 22604x   22604x 43x 6x 2x 2x   4x 4x     37x 37x     22561x 4x 4x 22557x 2352x 2352x 2352x 20205x 1x 1x 1x 1x 1x 1x 20204x 1143x 1143x 1143x 1143x 1143x   19061x 19061x         66x 64x 64x     66x       59x 200x 58x 200x 2x 2x 2x   198x         30x 3x   27x       7x 7x 7x 8x   7x                 57x 56x 56x 56x    
/**
 * 高性能 CSV 解析器/序列化器
 * 支持 RFC 4180 标准:引号转义、字段内换行、逗号处理
 */
 
export function parseCsv(text: string): string[][] {
  const rows: string[][] = [];
  const len = text.length;
  let i = 0;
  let row: string[] = [];
  let field = '';
  let inQuotes = false;
 
  while (i < len) {
    const ch = text[i];
 
    if (inQuotes) {
      if (ch === '"') {
        if (i + 1 < len && text[i + 1] === '"') {
          field += '"';
          i += 2;
        } else {
          inQuotes = false;
          i++;
        }
      } else {
        field += ch;
        i++;
      }
    } else {
      if (ch === '"') {
        inQuotes = true;
        i++;
      } else if (ch === ',') {
        row.push(field);
        field = '';
        i++;
      } else if (ch === '\r') {
        row.push(field);
        field = '';
        rows.push(row);
        row = [];
        i++;
        Eif (i < len && text[i] === '\n') i++;
      } else if (ch === '\n') {
        row.push(field);
        field = '';
        rows.push(row);
        row = [];
        i++;
      } else {
        field += ch;
        i++;
      }
    }
  }
 
  if (field || row.length > 0) {
    row.push(field);
    rows.push(row);
  }
 
  return rows;
}
 
export function normalizeRows(rows: string[][]): string[][] {
  if (rows.length === 0) return rows;
  const maxCols = Math.max(...rows.map(r => r.length));
  return rows.map(r => {
    if (r.length < maxCols) {
      const padded = [...r];
      while (padded.length < maxCols) padded.push('');
      return padded;
    }
    return r;
  });
}
 
function escapeField(field: string): string {
  if (field.includes(',') || field.includes('"') || field.includes('\n') || field.includes('\r')) {
    return '"' + field.replace(/"/g, '""') + '"';
  }
  return field;
}
 
export function serializeCsv(headers: string[], rows: string[][]): string {
  const lines: string[] = [];
  lines.push(headers.map(escapeField).join(','));
  for (const row of rows) {
    lines.push(row.map(escapeField).join(','));
  }
  return lines.join('\n');
}
 
export interface ParsedCsv {
  headers: string[];
  rows: string[][];
}
 
export function parseAndSplit(text: string): ParsedCsv {
  if (!text.trim()) return { headers: [], rows: [] };
  const allRows = normalizeRows(parseCsv(text));
  Iif (allRows.length === 0) return { headers: [], rows: [] };
  return { headers: allRows[0], rows: allRows.slice(1) };
}