diff --git a/cmd/dataxl/main_test.go b/cmd/dataxl/main_test.go index 54a7c55..c5733d0 100644 --- a/cmd/dataxl/main_test.go +++ b/cmd/dataxl/main_test.go @@ -67,6 +67,36 @@ func TestTSVToJSONRestoresPaths(t *testing.T) { } } +func TestTSVToJSONAcceptsExcelMultilineCells(t *testing.T) { + // Excel's clipboard format uses CRLF between records and quotes cells that + // contain line breaks. Quotes inside a quoted cell are doubled. + input := strings.NewReader("id\tdescription\tnote\r\n" + + "1\t\"first line\r\nsecond line\"\t\"She said \"\"hello\"\".\"\r\n" + + "2\tsingle line\tplain\r\n") + var out bytes.Buffer + var errOut bytes.Buffer + if err := run([]string{"-from", "tsv", "-to", "json"}, input, &out, &errOut); err != nil { + t.Fatalf("run failed: %v\nstderr: %s", err, errOut.String()) + } + + var records []map[string]any + if err := json.Unmarshal(out.Bytes(), &records); err != nil { + t.Fatalf("invalid json: %v\n%s", err, out.String()) + } + if len(records) != 2 { + t.Fatalf("record count = %d, want 2", len(records)) + } + if got := records[0]["description"]; got != "first line\nsecond line" { + t.Fatalf("description = %#v, want multiline cell", got) + } + if got := records[0]["note"]; got != `She said "hello".` { + t.Fatalf("note = %#v, want embedded quotes", got) + } + if got := records[1]["description"]; got != "single line" { + t.Fatalf("second description = %#v, want single line", got) + } +} + func TestJSONToXLSXAndBack(t *testing.T) { dir := t.TempDir() xlsxPath := filepath.Join(dir, "data.xlsx")