Repository: plotly/dash
File: components/dash-table/src/dash-table/utils/TableClipboardHelper.ts (TypeScript)

Code before:
            TableClipboardHelper.lastLocalCopy
        );
        const localCopy = includeHeaders
            ? TableClipboardHelper.localCopyWithoutHeaders
            : TableClipboardHelper.lastLocalCopy;
        const values =
            localDf === text ? localCopy : TableClipboardHelper.parse(text);

        return applyClipboardToData(
            values,
            activeCell,
            derived_viewport_indices,
            columns,
            visibleColumns,
            data,
            overflowColumns,
            overflowRows
        );
    }

    private static parse(str: string) {
        const temprows = str.split('\n');
        if (temprows.length > 1 && temprows[temprows.length - 1] === '') {
            temprows.pop();
        }
        const rows = temprows.map(row => row.split('\t'));
        return rows;
    }
}


Review comment (security):
Thanks for tracking down the original [`SheetClip.parse`](https://github.com/warpech/sheetclip/blob/f6fc218347e37ad6e630e01c757a83e2c4055445/sheetclip.js#L21). I'm a little worried that this simple replacement - while fixing the problem you encountered with multiple quotes within a cell - will cause other problems due to extra cases that were covered by `SheetClip` and missed here. I'm thinking particularly about cells that have tab or newline characters in them, which presumably are the whole reason for all that monkey business with quotes, so if a cell starts and ends with quote characters it can have tabs and newlines inside it and these don't cause the parser to move to the next column or row.

Then of course we could ask what happens if there are ALSO quote characters inside the cell text. I'd have guessed these would be escaped (with a backslash before the quote) but I see no code in the original `SheetClip.parse` to handle that so perhaps the quote character is doubled? That would explain their "replace two quotes with one" step, it's just applied too broadly.

Anyway I think the goal of `SheetClip` as expressed in its [head comment](https://github.com/warpech/sheetclip/blob/f6fc218347e37ad6e630e01c757a83e2c4055445/sheetclip.js#L5) is our goal as well:
> This tiny library transforms JavaScript arrays to strings that are pasteable by LibreOffice, OpenOffice, Google Docs and Microsoft Excel.

Meaning we want to be able to copy and paste rectangular sections of dash table to and from common spreadsheet programs, as well as to other dash tables, regardless of what text is in those cells. So I think this requires a little more investigation about how such cells are encoded by spreadsheets - and it may mean we have more work to do on the copy side as well as on paste.

Code after:
            TableClipboardHelper.lastLocalCopy
        );
        const localCopy = includeHeaders
            ? TableClipboardHelper.localCopyWithoutHeaders
            : TableClipboardHelper.lastLocalCopy;
        const values =
            localDf === text ? localCopy : TableClipboardHelper.parse(text);

        return applyClipboardToData(
            values,
            activeCell,
            derived_viewport_indices,
            columns,
            visibleColumns,
            data,
            overflowColumns,
            overflowRows
        );
    }

    private static parse(str: string) {
        let r,
            rlen,
            a = 0,
            c,
            clen,
            multiline,
            last,
            arr: string[][] = [[]];
        const rows = str.split('\n');
        if (rows.length > 1 && rows[rows.length - 1] === '') {
            rows.pop();
        }
        arr = [];
        for (r = 0, rlen = rows.length; r < rlen; r += 1) {
            const row = rows[r].split('\t');
            for (c = 0, clen = row.length; c < clen; c += 1) {
                if (!arr[a]) {
                    arr[a] = [];
                }
                if (multiline && c === 0) {
                    last = arr[a].length - 1;
                    arr[a][last] =
                        arr[a][last] + '\n' + row[0].replace(/""/g, '"');
                    if (
                        multiline &&
                        TableClipboardHelper.countQuotes(row[0]) & 1
                    ) {
                        multiline = false;
                        arr[a][last] = arr[a][last].substring(
                            0,