| 1 | /* The clipboard's wire format. |
| 2 | * |
| 3 | * Tab-separated with the quoting every spreadsheet uses, and its own |
| 4 | * module because it is a CODEC where clipboard.js is a PROTOCOL: one |
| 5 | * decides what the characters mean, the other decides which events to |
| 6 | * take and what to write. Split out when clipboard.js reached the |
| 7 | * 300-line cap, along that seam rather than at whatever line the cap |
| 8 | * happened to fall on. |
| 9 | * |
| 10 | * The quoting is not decoration. Without it one cell of wrapped text |
| 11 | * turns into two rows on the way back in — the same silent corruption |
| 12 | * this whole area exists to remove, and a cell holding a tab or a |
| 13 | * newline is exactly what a person pastes out of a document. |
| 14 | */ |
| 15 | (function () { |
| 16 | 'use strict'; |
| 17 | |
| 18 | window.SheetsTSV = { |
| 19 | /* encode reads a rectangle through the `at(r, c)` the caller |
| 20 | * supplies. The codec never touches the grid itself, which is what |
| 21 | * lets it be tested against a plain function. */ |
| 22 | encode: function (box, at) { |
| 23 | var rows = [], r, c, row; |
| 24 | for (r = box.r1; r <= box.r2; r++) { |
| 25 | for (row = [], c = box.c1; c <= box.c2; c++) row.push(quote(at(r, c))); |
| 26 | rows.push(row.join('\t')); |
| 27 | } |
| 28 | return rows.join('\n'); |
| 29 | }, |
| 30 | |
| 31 | /* decode reads a character at a time rather than splitting: a |
| 32 | * quoted field may contain the very characters a split would break |
| 33 | * on, which is the whole reason it is quoted. */ |
| 34 | decode: function (text) { |
| 35 | var rows = [], row = [], field = '', quoted = false, i = 0, ch; |
| 36 | for (; i < text.length; i++) { |
| 37 | ch = text[i]; |
| 38 | if (quoted) { |
| 39 | if (ch !== '"') { field += ch; continue; } |
| 40 | if (text[i + 1] === '"') { field += '"'; i++; continue; } |
| 41 | quoted = false; |
| 42 | continue; |
| 43 | } |
| 44 | if (ch === '"' && field === '') { quoted = true; continue; } |
| 45 | if (ch === '\t') { row.push(field); field = ''; continue; } |
| 46 | if (ch === '\r') continue; |
| 47 | if (ch === '\n') { row.push(field); rows.push(row); row = []; field = ''; continue; } |
| 48 | field += ch; |
| 49 | } |
| 50 | row.push(field); |
| 51 | rows.push(row); |
| 52 | /* A trailing newline is a line ending, not an empty last row — |
| 53 | * every editor writes one, and pasting it would clear a row. */ |
| 54 | if (rows.length > 1 && rows[rows.length - 1].length === 1 && |
| 55 | rows[rows.length - 1][0] === '') rows.pop(); |
| 56 | return rows; |
| 57 | } |
| 58 | }; |
| 59 | |
| 60 | function quote(v) { |
| 61 | if (!/[\t\n\r"]/.test(v)) return v; |
| 62 | return '"' + v.replace(/"/g, '""') + '"'; |
| 63 | } |
| 64 | }()); |
| 65 | |