mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-22 00:31:30 +00:00
88 lines
2.3 KiB
TypeScript
88 lines
2.3 KiB
TypeScript
/**
|
|
* Shared Excel → HTML table renderer.
|
|
*
|
|
* Converts a worksheet to a styled HTML table with:
|
|
* - Column letter headers (A, B, C…)
|
|
* - Row numbers
|
|
* - Proper empty cell handling
|
|
* - Sanitized output
|
|
*/
|
|
|
|
import type { WorkSheet } from 'xlsx';
|
|
|
|
/** Convert column index (0-based) to Excel-style letter (A, B, …, Z, AA, AB, …) */
|
|
const colLetter = (i: number): string => {
|
|
let s = '';
|
|
let n = i;
|
|
while (n >= 0) {
|
|
s = String.fromCharCode(65 + (n % 26)) + s;
|
|
n = Math.floor(n / 26) - 1;
|
|
}
|
|
return s;
|
|
};
|
|
|
|
/** Escape HTML entities */
|
|
const esc = (v: unknown): string => {
|
|
if (v === null || v === undefined || v === '') return ' ';
|
|
return String(v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
};
|
|
|
|
export interface ExcelTableResult {
|
|
html: string;
|
|
rowCount: number;
|
|
colCount: number;
|
|
}
|
|
|
|
/**
|
|
* Render a worksheet as an HTML table string.
|
|
* Uses sheet_to_json with header:1 for a raw 2D array.
|
|
*/
|
|
export async function excelToTable(worksheet: WorkSheet): Promise<ExcelTableResult> {
|
|
const XLSX = await import('xlsx');
|
|
const rows: unknown[][] = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: '' });
|
|
|
|
if (rows.length === 0) {
|
|
return {
|
|
html: '<table><tbody><tr><td> </td></tr></tbody></table>',
|
|
rowCount: 0,
|
|
colCount: 0
|
|
};
|
|
}
|
|
|
|
// Determine column count from the widest row
|
|
const colCount = rows.reduce((max, row) => Math.max(max, row.length), 0);
|
|
const rowCount = rows.length;
|
|
|
|
const parts: string[] = [];
|
|
parts.push('<table>');
|
|
|
|
// Column letter header row
|
|
parts.push('<thead><tr>');
|
|
parts.push('<th class="excel-row-num"></th>'); // corner cell
|
|
for (let c = 0; c < colCount; c++) {
|
|
parts.push(`<th class="excel-col-hdr">${colLetter(c)}</th>`);
|
|
}
|
|
parts.push('</tr></thead>');
|
|
|
|
// Data rows
|
|
parts.push('<tbody>');
|
|
for (let r = 0; r < rowCount; r++) {
|
|
const row = rows[r];
|
|
parts.push('<tr>');
|
|
parts.push(`<td class="excel-row-num">${r + 1}</td>`);
|
|
for (let c = 0; c < colCount; c++) {
|
|
const val = c < row.length ? row[c] : '';
|
|
const isNum = typeof val === 'number';
|
|
parts.push(`<td${isNum ? ' class="excel-num"' : ''}>${esc(val)}</td>`);
|
|
}
|
|
parts.push('</tr>');
|
|
}
|
|
parts.push('</tbody></table>');
|
|
|
|
const DOMPurify = (await import('dompurify')).default;
|
|
return {
|
|
html: DOMPurify.sanitize(parts.join('')),
|
|
rowCount,
|
|
colCount
|
|
};
|
|
}
|