fix(grid): copy cells containing double quotes verbatim
This commit is contained in:
6 files changed
+97
-19
No files matched your search
@@ -726,7 +726,7 @@ describe("useDataGridExport prepared row statements", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses escaped TSV for a multi-cell smart copy without relying on clipboard metadata", async () => {
|
||||
it("uses TSV (quotes only for separator/newline) for a multi-cell smart copy without relying on clipboard metadata", async () => {
|
||||
const rows = [
|
||||
[1, '{"msg":"success"}'],
|
||||
[2, "inside\ttab\nnext line"],
|
||||
@@ -737,7 +737,9 @@ describe("useDataGridExport prepared row statements", () => {
|
||||
columns: ["id", "name"],
|
||||
rows,
|
||||
};
|
||||
const text = '1\t"{""msg"":""success""}"\n2\t"inside\ttab\nnext line"';
|
||||
// A value containing only double quotes is copied verbatim (#10087); a value
|
||||
// containing a tab/newline is still quoted so the TSV shape survives.
|
||||
const text = '1\t{"msg":"success"}\n2\t"inside\ttab\nnext line"';
|
||||
vi.mocked(extractDataGridSelection).mockResolvedValueOnce({ text, mimeType: "text/tab-separated-values", fileExtension: "tsv", rowCount: 2, columnCount: 2 });
|
||||
const state = createExportState(editableTable, ["id", "name"], matrix, undefined, undefined, rows);
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatTsv } from "@/lib/export/exportFormats";
|
||||
|
||||
describe("formatTsv", () => {
|
||||
it("copies a value that only contains double quotes verbatim (#10087)", () => {
|
||||
const output = formatTsv(["id", "mag"], [[1, '"9932b4d2ad1c4ff88b5bfe1690ee1bb8"']]);
|
||||
|
||||
expect(output).toBe('id\tmag\n1\t"9932b4d2ad1c4ff88b5bfe1690ee1bb8"');
|
||||
});
|
||||
|
||||
it("still quotes values containing a tab or newline so the TSV shape survives", () => {
|
||||
const output = formatTsv(["v"], [["a\tb"], ["c\nd"]]);
|
||||
|
||||
expect(output).toBe('v\n"a\tb"\n"c\nd"');
|
||||
});
|
||||
|
||||
it("renders NULL as an empty field without adding quotes", () => {
|
||||
const output = formatTsv(["id", "name"], [[1, null]]);
|
||||
|
||||
expect(output).toBe("id\tname\n1\t");
|
||||
});
|
||||
});
|
||||
@@ -12,13 +12,16 @@ export function formatCsv(columns: string[], rows: ExportCellValue[][], quoteMod
|
||||
}
|
||||
|
||||
// Tab-separated values with a header row, mirroring Navicat's "Text File (*.txt)"
|
||||
// export: fields are joined by a tab, NULL becomes empty, and a field is only
|
||||
// wrapped in double quotes (with " doubled) when it contains a tab, newline,
|
||||
// or quote - i.e. the minimum needed to round-trip the value.
|
||||
// export: fields are joined by a tab and NULL becomes empty. A field is wrapped
|
||||
// in double quotes only when it contains a tab or a line break - the characters
|
||||
// that would otherwise corrupt the TSV row/column shape. A field that merely
|
||||
// contains a double quote is emitted verbatim: TSV is parsed by splitting on the
|
||||
// tab alone (there is no quote state machine on paste-back), so quoting such a
|
||||
// value would corrupt it (e.g. a value of `"abc"` must not become `"""abc"""`).
|
||||
export function formatTsv(columns: string[], rows: ExportCellValue[][]): string {
|
||||
const esc = (value: ExportCellValue) => {
|
||||
const text = value === null ? "" : String(value);
|
||||
if (text.includes("\t") || text.includes("\n") || text.includes("\r") || text.includes('"')) {
|
||||
if (text.includes("\t") || text.includes("\n") || text.includes("\r")) {
|
||||
return `"${text.replace(/"/g, '""')}"`;
|
||||
}
|
||||
return text;
|
||||
|
||||
Reference in new issue
Block a user