mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 00:25:46 -06:00
173 lines
4.0 KiB
TypeScript
173 lines
4.0 KiB
TypeScript
import { read, utils, WorkSheet, WorkBook, Range, ColInfo, CellObject, ExcelDataType } from 'xlsx';
|
|
|
|
import { DataFrame, FieldType } from '@grafana/data';
|
|
|
|
export function readSpreadsheet(file: ArrayBuffer): DataFrame[] {
|
|
return workBookToFrames(read(file, { type: 'buffer' }));
|
|
}
|
|
|
|
export function workBookToFrames(wb: WorkBook): DataFrame[] {
|
|
return wb.SheetNames.map((name) => workSheetToFrame(wb.Sheets[name], name));
|
|
}
|
|
|
|
export function workSheetToFrame(sheet: WorkSheet, name?: string): DataFrame {
|
|
const columns = sheetAsColumns(sheet);
|
|
if (!columns?.length) {
|
|
return {
|
|
fields: [],
|
|
name: name,
|
|
length: 0,
|
|
};
|
|
}
|
|
|
|
return {
|
|
fields: columns.map((c, idx) => {
|
|
let type = FieldType.string;
|
|
let values: unknown[] = [];
|
|
switch (c.type ?? 's') {
|
|
case 'b':
|
|
type = FieldType.boolean;
|
|
values = c.data.map((v) => (v?.v == null ? v?.v : Boolean(v.v)));
|
|
break;
|
|
|
|
case 'n':
|
|
type = FieldType.number;
|
|
values = c.data.map((v) => (v?.v == null ? v?.v : +v.v));
|
|
break;
|
|
|
|
case 'd':
|
|
type = FieldType.time;
|
|
values = c.data.map((v) => (v?.v == null ? v?.v : +v.v)); // ???
|
|
break;
|
|
|
|
default:
|
|
type = FieldType.string;
|
|
values = c.data.map((v) => (v?.v == null ? v?.v : utils.format_cell(v)));
|
|
break;
|
|
}
|
|
|
|
return {
|
|
name: c.name,
|
|
config: {}, // TODO? we could apply decimal formatting from worksheet
|
|
type,
|
|
values,
|
|
};
|
|
}),
|
|
name: name,
|
|
length: columns[0].data.length,
|
|
};
|
|
}
|
|
|
|
interface ColumnData {
|
|
index: number;
|
|
name: string;
|
|
info?: ColInfo;
|
|
data: CellObject[];
|
|
type?: ExcelDataType;
|
|
}
|
|
|
|
function sheetAsColumns(sheet: WorkSheet): ColumnData[] | null {
|
|
const r = sheet['!ref'];
|
|
if (!r) {
|
|
return null;
|
|
}
|
|
const columnInfo = sheet['!cols'];
|
|
const cols: ColumnData[] = [];
|
|
const range = safe_decode_range(r);
|
|
const types = new Set<ExcelDataType>();
|
|
let firstRowIsHeader = true;
|
|
|
|
for (let c = range.s.c; c <= range.e.c; ++c) {
|
|
types.clear();
|
|
const info = columnInfo?.[c] ?? {};
|
|
if (info.hidden) {
|
|
continue; // skip the column
|
|
}
|
|
const field: ColumnData = {
|
|
index: c,
|
|
name: utils.encode_col(c),
|
|
data: [],
|
|
info,
|
|
};
|
|
const pfix = utils.encode_col(c);
|
|
for (let r = range.s.r; r <= range.e.r; ++r) {
|
|
const cell = sheet[pfix + utils.encode_row(r)];
|
|
if (cell) {
|
|
if (field.data.length) {
|
|
types.add(cell.t);
|
|
} else if (cell.t !== 's') {
|
|
firstRowIsHeader = false;
|
|
}
|
|
}
|
|
field.data.push(cell);
|
|
}
|
|
cols.push(field);
|
|
if (types.size === 1) {
|
|
field.type = Array.from(types)[0];
|
|
}
|
|
}
|
|
|
|
if (firstRowIsHeader) {
|
|
return cols.map((c) => {
|
|
const first = c.data[0];
|
|
if (first?.v) {
|
|
c.name = utils.format_cell(first);
|
|
}
|
|
c.data = c.data.slice(1);
|
|
return c;
|
|
});
|
|
}
|
|
return cols;
|
|
}
|
|
|
|
/**
|
|
* Copied from Apache 2 licensed sheetjs:
|
|
* https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/xlsx.flow.js#L4338
|
|
*/
|
|
function safe_decode_range(range: string): Range {
|
|
let o = { s: { c: 0, r: 0 }, e: { c: 0, r: 0 } };
|
|
let idx = 0,
|
|
i = 0,
|
|
cc = 0;
|
|
let len = range.length;
|
|
for (idx = 0; i < len; ++i) {
|
|
if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) {
|
|
break;
|
|
}
|
|
idx = 26 * idx + cc;
|
|
}
|
|
o.s.c = --idx;
|
|
|
|
for (idx = 0; i < len; ++i) {
|
|
if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) {
|
|
break;
|
|
}
|
|
idx = 10 * idx + cc;
|
|
}
|
|
o.s.r = --idx;
|
|
|
|
if (i === len || cc !== 10) {
|
|
o.e.c = o.s.c;
|
|
o.e.r = o.s.r;
|
|
return o;
|
|
}
|
|
++i;
|
|
|
|
for (idx = 0; i !== len; ++i) {
|
|
if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) {
|
|
break;
|
|
}
|
|
idx = 26 * idx + cc;
|
|
}
|
|
o.e.c = --idx;
|
|
|
|
for (idx = 0; i !== len; ++i) {
|
|
if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) {
|
|
break;
|
|
}
|
|
idx = 10 * idx + cc;
|
|
}
|
|
o.e.r = --idx;
|
|
return o;
|
|
}
|