@mui/x-data-grid
Version:
The Community plan edition of the Data Grid components (MUI X).
144 lines • 4.74 kB
JavaScript
import { warnOnce } from '@mui/x-internals/warning';
import { GRID_CHECKBOX_SELECTION_COL_DEF } from "../../../../colDef/index.js";
function sanitizeCellValue(value, csvOptions) {
if (value === null || value === undefined) {
return '';
}
const valueStr = typeof value === 'string' ? value : `${value}`;
if (csvOptions.shouldAppendQuotes || csvOptions.escapeFormulas) {
const escapedValue = valueStr.replace(/"/g, '""');
if (csvOptions.escapeFormulas) {
// See https://owasp.org/www-community/attacks/CSV_Injection
if (['=', '+', '-', '@', '\t', '\r'].includes(escapedValue[0])) {
return `"'${escapedValue}"`;
}
}
// Make sure value containing delimiter or line break won't be split into multiple cells
if ([csvOptions.delimiter, '\n', '\r', '"'].some(delimiter => valueStr.includes(delimiter))) {
return `"${escapedValue}"`;
}
return escapedValue;
}
return valueStr;
}
export const serializeCellValue = (cellParams, options) => {
const {
csvOptions,
ignoreValueFormatter
} = options;
let value;
if (ignoreValueFormatter) {
const columnType = cellParams.colDef.type;
if (columnType === 'number') {
value = String(cellParams.value);
} else if (columnType === 'date' || columnType === 'dateTime') {
value = cellParams.value?.toISOString();
} else if (typeof cellParams.value?.toString === 'function') {
value = cellParams.value.toString();
} else {
value = cellParams.value;
}
} else {
value = cellParams.formattedValue;
}
return sanitizeCellValue(value, csvOptions);
};
class CSVRow {
constructor(options) {
this.options = void 0;
this.rowString = '';
this.isEmpty = true;
this.options = options;
}
addValue(value) {
if (!this.isEmpty) {
this.rowString += this.options.csvOptions.delimiter;
}
if (typeof this.options.sanitizeCellValue === 'function') {
this.rowString += this.options.sanitizeCellValue(value, this.options.csvOptions);
} else {
this.rowString += value;
}
this.isEmpty = false;
}
getRowString() {
return this.rowString;
}
}
const serializeRow = ({
id,
columns,
getCellParams,
csvOptions,
ignoreValueFormatter
}) => {
const row = new CSVRow({
csvOptions
});
columns.forEach(column => {
const cellParams = getCellParams(id, column.field);
if (process.env.NODE_ENV !== 'production') {
if (String(cellParams.formattedValue) === '[object Object]') {
warnOnce(['MUI X: When the value of a field is an object or a `renderCell` is provided, the CSV export might not display the value correctly.', 'You can provide a `valueFormatter` with a string representation to be used.']);
}
}
row.addValue(serializeCellValue(cellParams, {
ignoreValueFormatter,
csvOptions
}));
});
return row.getRowString();
};
export function buildCSV(options) {
const {
columns,
rowIds,
csvOptions,
ignoreValueFormatter,
apiRef
} = options;
const CSVBody = rowIds.reduce((acc, id) => `${acc}${serializeRow({
id,
columns,
getCellParams: apiRef.current.getCellParams,
ignoreValueFormatter,
csvOptions
})}\r\n`, '').trim();
if (!csvOptions.includeHeaders) {
return CSVBody;
}
const filteredColumns = columns.filter(column => column.field !== GRID_CHECKBOX_SELECTION_COL_DEF.field);
const headerRows = [];
if (csvOptions.includeColumnGroupsHeaders) {
const columnGroupLookup = apiRef.current.getAllGroupDetails();
let maxColumnGroupsDepth = 0;
const columnGroupPathsLookup = filteredColumns.reduce((acc, column) => {
const columnGroupPath = apiRef.current.getColumnGroupPath(column.field);
acc[column.field] = columnGroupPath;
maxColumnGroupsDepth = Math.max(maxColumnGroupsDepth, columnGroupPath.length);
return acc;
}, {});
for (let i = 0; i < maxColumnGroupsDepth; i += 1) {
const headerGroupRow = new CSVRow({
csvOptions,
sanitizeCellValue
});
headerRows.push(headerGroupRow);
filteredColumns.forEach(column => {
const columnGroupId = (columnGroupPathsLookup[column.field] || [])[i];
const columnGroup = columnGroupLookup[columnGroupId];
headerGroupRow.addValue(columnGroup ? columnGroup.headerName || columnGroup.groupId : '');
});
}
}
const mainHeaderRow = new CSVRow({
csvOptions,
sanitizeCellValue
});
filteredColumns.forEach(column => {
mainHeaderRow.addValue(column.headerName || column.field);
});
headerRows.push(mainHeaderRow);
const CSVHead = `${headerRows.map(row => row.getRowString()).join('\r\n')}\r\n`;
return `${CSVHead}${CSVBody}`.trim();
}