Skip to content

Commit

Permalink
[DataGrid] Fix cell value formatting on copy (#12357)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Cherniavskyi <andrew@mui.com>
  • Loading branch information
sai6855 and cherniavskii committed Mar 18, 2024
1 parent 83de93f commit 5342203
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ export const useGridCellSelection = (
cellData = serializeCellValue(cellParams, {
delimiterCharacter: clipboardCopyCellDelimiter,
ignoreValueFormatter,
shouldAppendQuotes: true,
});
} else {
cellData = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export const useGridClipboard = (
includeHeaders: false,
// TODO: make it configurable
delimiter: clipboardCopyCellDelimiter,
shouldAppendQuotes: false,
});
} else {
const focusedCell = gridFocusCellSelector(apiRef);
Expand All @@ -105,6 +106,7 @@ export const useGridClipboard = (
textToCopy = serializeCellValue(cellParams, {
delimiterCharacter: clipboardCopyCellDelimiter,
ignoreValueFormatter,
shouldAppendQuotes: false,
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import type { GridStateColDef } from '../../../../models/colDef/gridColDef';
import type { GridApiCommunity } from '../../../../models/api/gridApiCommunity';
import { buildWarning } from '../../../../utils/warning';

function sanitizeCellValue(value: any, delimiterCharacter: string) {
function sanitizeCellValue(value: any, delimiterCharacter: string, shouldAppendQuotes: boolean) {
if (typeof value === 'string') {
// Make sure value containing delimiter or line break won't be split into multiple rows
if ([delimiterCharacter, '\n', '\r', '"'].some((delimiter) => value.includes(delimiter))) {
return `"${value.replace(/"/g, '""')}"`;
if (shouldAppendQuotes) {
return `"${value.replace(/"/g, '""')}"`;
}
return `${value.replace(/"/g, '""')}`;
}

return value;
Expand All @@ -20,9 +23,13 @@ function sanitizeCellValue(value: any, delimiterCharacter: string) {

export const serializeCellValue = (
cellParams: GridCellParams,
options: { delimiterCharacter: string; ignoreValueFormatter: boolean },
options: {
delimiterCharacter: string;
ignoreValueFormatter: boolean;
shouldAppendQuotes: boolean;
},
) => {
const { delimiterCharacter, ignoreValueFormatter } = options;
const { delimiterCharacter, ignoreValueFormatter, shouldAppendQuotes } = options;
let value: any;
if (ignoreValueFormatter) {
const columnType = cellParams.colDef.type;
Expand All @@ -39,7 +46,7 @@ export const serializeCellValue = (
value = cellParams.formattedValue;
}

return sanitizeCellValue(value, delimiterCharacter);
return sanitizeCellValue(value, delimiterCharacter, shouldAppendQuotes);
};

const objectFormattedValueWarning = buildWarning([
Expand All @@ -49,7 +56,8 @@ const objectFormattedValueWarning = buildWarning([

type CSVRowOptions = {
delimiterCharacter: string;
sanitizeCellValue?: (value: any, delimiterCharacter: string) => any;
sanitizeCellValue?: (value: any, delimiterCharacter: string, shouldAppendQuotes: boolean) => any;
shouldAppendQuotes: boolean;
};
class CSVRow {
options: CSVRowOptions;
Expand All @@ -69,7 +77,11 @@ class CSVRow {
if (value === null || value === undefined) {
this.rowString += '';
} else if (typeof this.options.sanitizeCellValue === 'function') {
this.rowString += this.options.sanitizeCellValue(value, this.options.delimiterCharacter);
this.rowString += this.options.sanitizeCellValue(
value,
this.options.delimiterCharacter,
this.options.shouldAppendQuotes,
);
} else {
this.rowString += value;
}
Expand All @@ -87,14 +99,16 @@ const serializeRow = ({
getCellParams,
delimiterCharacter,
ignoreValueFormatter,
shouldAppendQuotes,
}: {
id: GridRowId;
columns: GridStateColDef[];
getCellParams: (id: GridRowId, field: string) => GridCellParams;
delimiterCharacter: string;
ignoreValueFormatter: boolean;
shouldAppendQuotes: boolean;
}) => {
const row = new CSVRow({ delimiterCharacter });
const row = new CSVRow({ delimiterCharacter, shouldAppendQuotes });

columns.forEach((column) => {
const cellParams = getCellParams(id, column.field);
Expand All @@ -103,7 +117,13 @@ const serializeRow = ({
objectFormattedValueWarning();
}
}
row.addValue(serializeCellValue(cellParams, { delimiterCharacter, ignoreValueFormatter }));
row.addValue(
serializeCellValue(cellParams, {
delimiterCharacter,
ignoreValueFormatter,
shouldAppendQuotes,
}),
);
});

return row.getRowString();
Expand All @@ -117,6 +137,7 @@ interface BuildCSVOptions {
includeColumnGroupsHeaders: NonNullable<GridCsvExportOptions['includeColumnGroupsHeaders']>;
ignoreValueFormatter: boolean;
apiRef: React.MutableRefObject<GridApiCommunity>;
shouldAppendQuotes: boolean;
}

export function buildCSV(options: BuildCSVOptions): string {
Expand All @@ -128,6 +149,7 @@ export function buildCSV(options: BuildCSVOptions): string {
includeColumnGroupsHeaders,
ignoreValueFormatter,
apiRef,
shouldAppendQuotes,
} = options;

const CSVBody = rowIds
Expand All @@ -139,6 +161,7 @@ export function buildCSV(options: BuildCSVOptions): string {
getCellParams: apiRef.current.getCellParams,
delimiterCharacter,
ignoreValueFormatter,
shouldAppendQuotes,
})}\r\n`,
'',
)
Expand Down Expand Up @@ -168,7 +191,11 @@ export function buildCSV(options: BuildCSVOptions): string {
}, {});

for (let i = 0; i < maxColumnGroupsDepth; i += 1) {
const headerGroupRow = new CSVRow({ delimiterCharacter, sanitizeCellValue });
const headerGroupRow = new CSVRow({
delimiterCharacter,
sanitizeCellValue,
shouldAppendQuotes,
});
headerRows.push(headerGroupRow);
filteredColumns.forEach((column) => {
const columnGroupId = (columnGroupPathsLookup[column.field] || [])[i];
Expand All @@ -178,7 +205,7 @@ export function buildCSV(options: BuildCSVOptions): string {
}
}

const mainHeaderRow = new CSVRow({ delimiterCharacter, sanitizeCellValue });
const mainHeaderRow = new CSVRow({ delimiterCharacter, sanitizeCellValue, shouldAppendQuotes });
filteredColumns.forEach((column) => {
mainHeaderRow.addValue(column.headerName || column.field);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const useGridCsvExport = (
includeColumnGroupsHeaders: options.includeColumnGroupsHeaders ?? true,
ignoreValueFormatter,
apiRef,
shouldAppendQuotes: options.shouldAppendQuotes ?? true,
});
},
[logger, apiRef, ignoreValueFormatter],
Expand Down
6 changes: 6 additions & 0 deletions packages/x-data-grid/src/models/gridExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ export interface GridCsvExportOptions extends GridFileExportOptions {
* @returns {GridRowId[]} The list of row ids to export.
*/
getRowsToExport?: (params: GridCsvGetRowsToExportParams) => GridRowId[];
/**
* @ignore
* If `false`, the quotes will not be appended to the cell value.
* @default true
*/
shouldAppendQuotes?: boolean;
}

/**
Expand Down

0 comments on commit 5342203

Please sign in to comment.