handsontable 14.0.0-next-c22ab6f-20231030 → 14.0.0-next-f88c253-20231106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/base.js +2 -2
  2. package/base.mjs +2 -2
  3. package/dist/handsontable.css +8 -2
  4. package/dist/handsontable.full.css +8 -2
  5. package/dist/handsontable.full.js +2084 -3359
  6. package/dist/handsontable.full.min.css +3 -3
  7. package/dist/handsontable.full.min.js +59 -66
  8. package/dist/handsontable.js +2086 -3361
  9. package/dist/handsontable.min.css +3 -3
  10. package/dist/handsontable.min.js +16 -23
  11. package/helpers/mixed.js +2 -2
  12. package/helpers/mixed.mjs +2 -2
  13. package/package.json +1 -1
  14. package/pluginHooks.d.ts +6 -28
  15. package/pluginHooks.js +63 -117
  16. package/pluginHooks.mjs +63 -117
  17. package/plugins/copyPaste/clipboardData.js +18 -0
  18. package/plugins/copyPaste/clipboardData.mjs +14 -0
  19. package/plugins/copyPaste/copyPaste.js +92 -38
  20. package/plugins/copyPaste/copyPaste.mjs +94 -40
  21. package/plugins/copyPaste/pasteEvent.js +14 -0
  22. package/plugins/copyPaste/pasteEvent.mjs +9 -0
  23. package/plugins/mergeCells/mergeCells.js +14 -0
  24. package/plugins/mergeCells/mergeCells.mjs +14 -0
  25. package/plugins/nestedHeaders/nestedHeaders.js +22 -21
  26. package/plugins/nestedHeaders/nestedHeaders.mjs +22 -21
  27. package/utils/parseTable.js +83 -527
  28. package/utils/parseTable.mjs +82 -523
  29. package/plugins/copyPaste/clipboardData/clipboardData.js +0 -516
  30. package/plugins/copyPaste/clipboardData/clipboardData.mjs +0 -512
  31. package/plugins/copyPaste/clipboardData/copyClipboardData.js +0 -69
  32. package/plugins/copyPaste/clipboardData/copyClipboardData.mjs +0 -65
  33. package/plugins/copyPaste/clipboardData/index.js +0 -9
  34. package/plugins/copyPaste/clipboardData/index.mjs +0 -4
  35. package/plugins/copyPaste/clipboardData/pasteClipboardData.js +0 -81
  36. package/plugins/copyPaste/clipboardData/pasteClipboardData.mjs +0 -77
@@ -1,8 +1,5 @@
1
1
  import "core-js/modules/es.array.push.js";
2
- import "core-js/modules/es.string.replace-all.js";
3
2
  import { isEmpty } from "./../helpers/mixed.mjs";
4
- import { isObject } from "./../helpers/object.mjs";
5
- import { rangeEach } from "../helpers/number.mjs";
6
3
  const ESCAPED_HTML_CHARS = {
7
4
  ' ': '\x20',
8
5
  '&': '&',
@@ -21,539 +18,102 @@ function isHTMLTable(element) {
21
18
  return (element && element.nodeName || '') === 'TABLE';
22
19
  }
23
20
 
24
- /**
25
- * Parses empty values to an empty string or leave them untouched otherwise.
26
- *
27
- * @private
28
- * @param {string} cellValue Parsed cell value.
29
- * @returns {string}
30
- */
31
- function parseEmptyValues(cellValue) {
32
- if (isEmpty(cellValue)) {
33
- return '';
34
- }
35
- return cellValue;
36
- }
37
-
38
21
  /**
39
22
  * Converts Handsontable into HTMLTableElement.
40
23
  *
41
- * @param {Core} hotInstance The Handsontable instance.
42
- * @returns {string} OuterHTML of the HTMLTableElement.
43
- */
44
- export function instanceToHTML(hotInstance) {
45
- const startColumn = hotInstance.hasRowHeaders() ? -1 : 0;
46
- const startRow = hotInstance.hasColHeaders() ? -1 : 0;
47
- const rows = Array.from({
48
- length: hotInstance.countRows() + Math.abs(startRow)
49
- }, (_, i) => i + startRow);
50
- const columns = Array.from({
51
- length: hotInstance.countCols() + Math.abs(startColumn)
52
- }, (_, i) => i + startColumn);
53
- return getHTMLByCoords(hotInstance, {
54
- rows,
55
- columns
56
- });
57
- }
58
-
59
- /**
60
- * Converts Handsontable's coordinates into HTMLTableElement.
61
- *
62
- * @param {Core} hotInstance The Handsontable instance.
63
- * @param {object} config Configuration for building HTMLTableElement.
64
- * @param {Array<number>} config.rows List of row indexes which should be taken into account when creating the table.
65
- * @param {Array<number>} config.columns List of column indexes which should be taken into account when creating the table.
66
- * @returns {string} OuterHTML of the HTMLTableElement.
67
- */
68
- export function getHTMLByCoords(hotInstance, config) {
69
- return ['<table>', ...getHeadersHTMLByCoords(hotInstance, config), ...getBodyHTMLByCoords(hotInstance, config), '</table>'].join('');
70
- }
71
-
72
- /**
73
- * Converts Handsontable's coordinates into list of cell values.
74
- *
75
- * @param {Core} hotInstance The Handsontable instance.
76
- * @param {object} config Configuration for building the cell value list.
77
- * @param {Array<number>} config.rows List of row indexes which should be taken into account when creating the
78
- * cell value list.
79
- * @param {Array<number>} config.columns List of column indexes which should be taken into account when creating the
80
- * cell value list.
81
- * @returns {Array<Array<string>>} List of displayed cell values.
82
- */
83
- export function getDataByCoords(hotInstance, config) {
84
- return [...getHeadersDataByCoords(hotInstance, config), ...getBodyDataByCoords(hotInstance, config)];
85
- }
86
-
87
- /**
88
- * Converts config into HTMLTableElement.
89
- *
90
- * @param {object} config Configuration for building HTMLTableElement.
91
- * @param {Array<number>} [config.excludedRows] List of row indexes which should be excluded when creating the table.
92
- * @param {Array<number>} [config.excludedColumns] List of column indexes which should be excluded when creating the table.
93
- * @param {Array<Array<string>>} [config.data] List of cell data.
94
- * @param {Array<object>} [config.mergeCells] List of merged cells.
95
- * @param {Array<Array<string|object>>} [config.nestedHeaders] List of headers and corresponding information about some
96
- * nested elements.
97
- * @param {Array<string>} [config.colHeaders] List of first level header values.
24
+ * @param {Core} instance The Handsontable instance.
98
25
  * @returns {string} OuterHTML of the HTMLTableElement.
99
26
  */
100
- export function getHTMLFromConfig(config) {
101
- return ['<table>', ...getHeadersHTMLByConfig(config), ...getBodyHTMLByConfig(config), '</table>'].join('');
102
- }
103
-
104
- /**
105
- * Get list of filtered nested headers.
106
- *
107
- * @param {Array<Array<string|object>>} nestedHeaders List of nested headers which will be filtered.
108
- * @param {Array<number>} excludedHeaders List of headers which should be excluded when creating the HTMLTableElement.tHead.
109
- * @param {Array<number>} excludedColumns List of column indexes which should be excluded when creating the HTMLTableElement.tHead.
110
- * @returns {*}
111
- */
112
- function getFilteredNestedHeaders(nestedHeaders, excludedHeaders, excludedColumns) {
113
- return nestedHeaders.reduce((listOfHeaders, headerValues, rowIndex) => {
114
- if (excludedHeaders.includes(rowIndex - nestedHeaders.length)) {
115
- return listOfHeaders;
116
- }
117
- const filteredNestedHeader = headerValues.filter((columnData, columnIndex) => excludedColumns.includes(columnIndex) === false);
118
- if (filteredNestedHeader.length > 0) {
119
- return listOfHeaders.concat([filteredNestedHeader]);
120
- }
121
- return listOfHeaders;
122
- }, []);
123
- }
124
-
125
- /**
126
- * Get HTML for nested headers.
127
- *
128
- * @param {Array<Array<string|object>>} nestedHeaders List of nested headers which will be filtered.
129
- * @param {Array<number>} excludedHeaders List of headers which should be excluded when creating the HTMLTableElement.tHead.
130
- * @param {Array<number>} excludedColumns List of column indexes which should be excluded when creating the HTMLTableElement.tHead.
131
- * @returns {Array<string>}
132
- */
133
- function getNestedHeadersHTML(nestedHeaders, excludedHeaders, excludedColumns) {
134
- const headersHTML = [];
135
- getFilteredNestedHeaders(nestedHeaders, excludedHeaders, excludedColumns).forEach(listOfHeaders => {
136
- const rowHTML = ['<tr>'];
137
- for (let i = 0; i < listOfHeaders.length; i += 1) {
138
- const header = listOfHeaders[i];
139
- let headerValue = header;
140
- let colspanAttribute = '';
141
- if (isObject(header)) {
142
- const {
143
- colspan,
144
- label
145
- } = header;
146
- headerValue = label;
147
- colspanAttribute = ` colspan=${colspan}`;
148
- }
149
- rowHTML.push(`<th${colspanAttribute}>${encodeHTMLEntities(parseEmptyValues(headerValue))}</th>`);
150
- }
151
- rowHTML.push('</tr>');
152
- headersHTML.push(...rowHTML);
153
- });
154
- return headersHTML;
155
- }
156
-
157
- /**
158
- * Get HTML for first level header.
159
- *
160
- * @param {Array<string>} columnHeaders List of header values which will be filtered.
161
- * @param {Array<number>} excludedHeaders List of headers which should be excluded when creating the HTMLTableElement.tHead.
162
- * @param {Array<number>} excludedColumns List of column indexes which should be excluded when creating the HTMLTableElement.tHead.
163
- * @returns {*[]}
164
- */
165
- function getSimpleHeadersHTML(columnHeaders, excludedHeaders, excludedColumns) {
166
- if (excludedHeaders.includes(-1)) {
167
- return [];
168
- }
169
- const filteredColumnHeaders = columnHeaders.filter((columnHeaderValue, columnIndex) => excludedColumns.includes(columnIndex) === false);
170
- if (filteredColumnHeaders.length === 0) {
171
- return [];
172
- }
173
- return ['<tr>', ...filteredColumnHeaders.map(columnHeader => `<th>${encodeHTMLEntities(parseEmptyValues(columnHeader))}</th>`), '</tr>'];
174
- }
175
-
176
- /**
177
- * Get list of cells filtered by list of excluded rows and columns.
178
- *
179
- * @private
180
- * @param {Array<Array<string>>} data List of cells values which will be filtered.
181
- * @param {Array<number>} excludedRows List of row indexes which should be excluded when creating the HTMLTableElement.tHead.
182
- * @param {Array<number>} excludedColumns List of column indexes which should be excluded when creating the HTMLTableElement.tHead.
183
- * @returns {Array<string>} List of cell values.
184
- */
185
- function getFilteredCells(data, excludedRows, excludedColumns) {
186
- if (Array.isArray(data) === false) {
187
- return [];
188
- }
189
- return data.reduce((listOfCells, rowData, rowIndex) => {
190
- if (excludedRows.includes(rowIndex)) {
191
- return listOfCells;
192
- }
193
- const filteredRowData = rowData.filter((cellData, columnIndex) => excludedColumns.includes(columnIndex) === false);
194
- if (filteredRowData.length > 0) {
195
- return listOfCells.concat([filteredRowData]);
196
- }
197
- return listOfCells;
198
- }, []);
199
- }
200
-
201
- /**
202
- * Prepare information about merged areas to reduce complexity of calculations.
203
- *
204
- * @private
205
- * @param {Array<object>} mergedCellsConfig List of merged cells.
206
- * @returns {{mergedCellsMap: Map<any, any>, mergedArea: Set<any>}}
207
- */
208
- function getMergedCellsInformation(mergedCellsConfig) {
209
- const mergedCellsMap = new Map();
210
- const mergedArea = new Set();
211
- let mergedRows = 1;
212
- let mergedColumns = 1;
213
- mergedCellsConfig === null || mergedCellsConfig === void 0 || mergedCellsConfig.forEach(mergeArea => {
214
- const {
215
- row,
216
- col,
217
- rowspan,
218
- colspan
219
- } = mergeArea;
220
- mergedCellsMap.set(`${row}x${col}`, {
221
- rowspan,
222
- colspan
223
- });
224
- if (Number.isInteger(rowspan)) {
225
- mergedRows = rowspan;
226
- }
227
- if (Number.isInteger(colspan)) {
228
- mergedColumns = colspan;
229
- }
230
- rangeEach(row, row + mergedRows - 1, rowIndex => {
231
- rangeEach(col, col + mergedColumns - 1, columnIndex => {
232
- // Other than start point.
233
- if (rowIndex !== row || columnIndex !== col) {
234
- mergedArea.add(`${rowIndex}x${columnIndex}`);
235
- }
236
- });
237
- });
238
- });
239
- return {
240
- mergedCellsMap,
241
- mergedArea
242
- };
243
- }
244
-
245
- /**
246
- * Converts config with information about cells into HTMLTableElement.tBodies.
247
- *
248
- * @private
249
- * @param {object} config Configuration for building HTMLTableElement.tBodies.
250
- * @param {Array<Array<string>>} config.data List of cell data.
251
- * @param {Array<number>} [config.excludedRows] List of row indexes which should be excluded when creating the HTMLTableElement.tBodies.
252
- * @param {Array<number>} [config.excludedColumns] List of column indexes which should be excluded when creating the HTMLTableElement.tBodies.
253
- * @param {Array<object>} [config.mergeCells] List of merged cells.
254
- * @returns {Array<string>} List of HTMLElements stored as strings.
255
- */
256
- function getBodyHTMLByConfig(config) {
257
- const excludedColumns = config.excludedColumns || [];
258
- const excludedRows = config.excludedRows || [];
259
- const {
260
- data,
261
- mergeCells
262
- } = config;
263
- const ignoredCellRows = excludedRows.filter(rowIndex => rowIndex >= 0);
264
- const filteredData = getFilteredCells(data, ignoredCellRows, excludedColumns);
265
- const cells = [];
266
- if (filteredData.length === 0) {
267
- return [];
268
- }
269
- const {
270
- mergedCellsMap,
271
- mergedArea
272
- } = getMergedCellsInformation(mergeCells);
273
- filteredData.forEach((rowData, rowIndex) => {
274
- const rowHTML = ['<tr>'];
275
- rowData.forEach((cellData, columnIndex) => {
276
- const attrs = [];
277
- const checkedMergeCoordinate = `${rowIndex}x${columnIndex}`;
278
- const mergeParent = mergedCellsMap.get(checkedMergeCoordinate);
279
- if (mergeParent !== undefined) {
27
+ export function instanceToHTML(instance) {
28
+ const hasColumnHeaders = instance.hasColHeaders();
29
+ const hasRowHeaders = instance.hasRowHeaders();
30
+ const coords = [hasColumnHeaders ? -1 : 0, hasRowHeaders ? -1 : 0, instance.countRows() - 1, instance.countCols() - 1];
31
+ const data = instance.getData(...coords);
32
+ const countRows = data.length;
33
+ const countCols = countRows > 0 ? data[0].length : 0;
34
+ const TABLE = ['<table>', '</table>'];
35
+ const THEAD = hasColumnHeaders ? ['<thead>', '</thead>'] : [];
36
+ const TBODY = ['<tbody>', '</tbody>'];
37
+ const rowModifier = hasRowHeaders ? 1 : 0;
38
+ const columnModifier = hasColumnHeaders ? 1 : 0;
39
+ for (let row = 0; row < countRows; row += 1) {
40
+ const isColumnHeadersRow = hasColumnHeaders && row === 0;
41
+ const CELLS = [];
42
+ for (let column = 0; column < countCols; column += 1) {
43
+ const isRowHeadersColumn = !isColumnHeadersRow && hasRowHeaders && column === 0;
44
+ let cell = '';
45
+ if (isColumnHeadersRow) {
46
+ cell = `<th>${instance.getColHeader(column - rowModifier)}</th>`;
47
+ } else if (isRowHeadersColumn) {
48
+ cell = `<th>${instance.getRowHeader(row - columnModifier)}</th>`;
49
+ } else {
50
+ const cellData = data[row][column];
280
51
  const {
52
+ hidden,
281
53
  rowspan,
282
54
  colspan
283
- } = mergeParent;
284
- if (Number.isInteger(rowspan) && rowspan > 1) {
285
- attrs.push(` rowspan="${rowspan}"`);
286
- }
287
- if (Number.isInteger(colspan) && colspan > 1) {
288
- attrs.push(` colspan="${colspan}"`);
55
+ } = instance.getCellMeta(row - columnModifier, column - rowModifier);
56
+ if (!hidden) {
57
+ const attrs = [];
58
+ if (rowspan) {
59
+ attrs.push(`rowspan="${rowspan}"`);
60
+ }
61
+ if (colspan) {
62
+ attrs.push(`colspan="${colspan}"`);
63
+ }
64
+ if (isEmpty(cellData)) {
65
+ cell = `<td ${attrs.join(' ')}></td>`;
66
+ } else {
67
+ const value = cellData.toString().replace('<', '&lt;').replace('>', '&gt;').replace(/(<br(\s*|\/)>(\r\n|\n)?|\r\n|\n)/g, '<br>\r\n').replace(/\x20/gi, '&nbsp;').replace(/\t/gi, '&#9;');
68
+ cell = `<td ${attrs.join(' ')}>${value}</td>`;
69
+ }
289
70
  }
290
- } else if (mergedArea.has(checkedMergeCoordinate)) {
291
- return;
292
71
  }
293
- rowHTML.push(`<td${attrs.join('')}>${encodeHTMLEntities(parseEmptyValues(cellData))}</td>`);
294
- });
295
- rowHTML.push('</tr>');
296
- cells.push(...rowHTML);
297
- });
298
- return ['<tbody>', ...cells, '</tbody>'];
299
- }
300
-
301
- /**
302
- * Converts config with information about headers into HTMLTableElement.tHead.
303
- *
304
- * @private
305
- * @param {object} config Configuration for building HTMLTableElement.tHead.
306
- * @param {Array<Array<string|object>>} [config.nestedHeaders] List of headers and corresponding information about some
307
- * nested elements.
308
- * @param {Array<string>} [config.colHeaders] List of first level header values.
309
- * @param {Array<number>} [config.excludedRows] List of row indexes which should be excluded when creating the HTMLTableElement.tHead.
310
- * @param {Array<number>} [config.excludedColumns] List of column indexes which should be excluded when creating the HTMLTableElement.tHead.
311
- * @returns {Array<string>} List of HTMLElements stored as strings.
312
- */
313
- function getHeadersHTMLByConfig(config) {
314
- const headersHTML = [];
315
- const excludedColumns = Array.isArray(config === null || config === void 0 ? void 0 : config.excludedColumns) ? config.excludedColumns : [];
316
- const excludedRows = Array.isArray(config === null || config === void 0 ? void 0 : config.excludedRows) ? config.excludedRows : [];
317
- const {
318
- nestedHeaders,
319
- colHeaders
320
- } = config;
321
- const excludedHeaders = excludedRows.filter(rowIndex => rowIndex < 0);
322
- if (Array.isArray(nestedHeaders)) {
323
- headersHTML.push(...getNestedHeadersHTML(nestedHeaders, excludedHeaders, excludedColumns));
324
- } else if (Array.isArray(colHeaders)) {
325
- headersHTML.push(...getSimpleHeadersHTML(colHeaders, excludedHeaders, excludedColumns));
326
- }
327
- if (headersHTML.length > 0) {
328
- return ['<thead>', ...headersHTML, '</thead>'];
329
- }
330
- return [];
331
- }
332
-
333
- /**
334
- * Converts config with information about cells and headers into list of values.
335
- *
336
- * @param {object} config Configuration for building list of values.
337
- * @param {Array<number>} [config.excludedRows] List of row indexes which should be excluded when creating the value list.
338
- * @param {Array<number>} [config.excludedColumns] List of column indexes which should be excluded when creating the value list.
339
- * @param {Array<Array<string|object>>} [config.nestedHeaders] List of headers and information about some nested elements.
340
- * @param {Array<string>} [config.colHeaders] List of first level header values.
341
- * @returns {string[][]} List of values.
342
- */
343
- export function getDataWithHeadersByConfig(config) {
344
- const dataWithHeaders = [];
345
- const excludedColumns = Array.isArray(config === null || config === void 0 ? void 0 : config.excludedColumns) ? config.excludedColumns : [];
346
- const excludedRows = Array.isArray(config === null || config === void 0 ? void 0 : config.excludedRows) ? config.excludedRows : [];
347
- const {
348
- data,
349
- nestedHeaders,
350
- colHeaders
351
- } = config;
352
- const excludedHeaders = excludedRows.filter(rowIndex => rowIndex < 0);
353
- if (Array.isArray(nestedHeaders)) {
354
- dataWithHeaders.push(...getFilteredNestedHeaders(nestedHeaders, excludedHeaders, excludedColumns).map(listOfHeaders => {
355
- return listOfHeaders.reduce((headers, header) => {
356
- if (isObject(header)) {
357
- headers.push(header.label, ...new Array(header.colspan - 1).fill(''));
358
- } else {
359
- headers.push(header);
360
- }
361
- return headers;
362
- }, []);
363
- }));
364
- } else if (Array.isArray(colHeaders)) {
365
- dataWithHeaders.push([...colHeaders.filter((columnHeaderData, columnIndex) => excludedColumns.includes(columnIndex) === false)]);
72
+ CELLS.push(cell);
73
+ }
74
+ const TR = ['<tr>', ...CELLS, '</tr>'].join('');
75
+ if (isColumnHeadersRow) {
76
+ THEAD.splice(1, 0, TR);
77
+ } else {
78
+ TBODY.splice(-1, 0, TR);
79
+ }
366
80
  }
367
- dataWithHeaders.push(...getFilteredCells(data, excludedRows.filter(rowIndex => rowIndex >= 0), excludedColumns.filter(columnIndex => columnIndex >= 0)));
368
- return dataWithHeaders;
369
- }
370
-
371
- /**
372
- * Encode text to HTML.
373
- *
374
- * @param {string} text Text to prepare.
375
- * @returns {string}
376
- */
377
- function encodeHTMLEntities(text) {
378
- return `${text}`.replace(/&/g, '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace(/(<br(\s*|\/)>(\r\n|\n)?|\r\n|\n)/g, '<br>\r\n').replace(/\x20{2,}/gi, substring => {
379
- // The way how Excel serializes data with at least two spaces.
380
- return `<span style="mso-spacerun: yes">${'&nbsp;'.repeat(substring.length - 1)} </span>`;
381
- }).replace(/\t/gi, '&#9;');
382
- }
383
-
384
- /**
385
- * Decode HTML to simple text.
386
- *
387
- * @param {string} html HTML for handling.
388
- * @returns {string}
389
- */
390
- function decodeHTMLEntities(html) {
391
- return html.replace(regEscapedChars, match => ESCAPED_HTML_CHARS[match])
392
- // The way how Excel serializes data with at least two spaces.
393
- .replace(/<span style="mso-spacerun: yes">(.+?)<\/span>/, '$1').replaceAll('&nbsp;', ' ');
81
+ TABLE.splice(1, 0, THEAD.join(''), TBODY.join(''));
82
+ return TABLE.join('');
394
83
  }
395
84
 
396
85
  /**
397
- * Converts Handsontable's header coordinates into HTMLTableElement.tHead.
86
+ * Converts 2D array into HTMLTableElement.
398
87
  *
399
- * @param {Core} hotInstance The Handsontable instance.
400
- * @param {object} config Configuration for building HTMLTableElement.tHead.
401
- * @param {Array<number>} config.rows List of row indexes which should be taken into account when creating
402
- * the HTMLTableElement.tHead.
403
- * @param {Array<number>} config.columns List of column indexes which should be taken into account when creating
404
- * the HTMLTableElement.tHead.
405
- * @returns {Array<string>} List of HTMLElements stored as strings.
88
+ * @param {Array} input Input array which will be converted to HTMLTable.
89
+ * @returns {string} OuterHTML of the HTMLTableElement.
406
90
  */
407
- function getHeadersHTMLByCoords(hotInstance, config) {
408
- const {
409
- rows,
410
- columns
411
- } = config;
412
- const headers = rows.filter(rowIndex => rowIndex < 0);
413
- const headersHTML = [];
414
- if (headers.length === 0 || columns.length === 0) {
415
- return [];
416
- }
417
- headers.forEach(rowIndex => {
418
- const rowHTML = ['<tr>'];
419
- for (let i = 0; i < columns.length; i += 1) {
420
- const columnIndex = columns[i];
421
- const headerCell = hotInstance.getCell(rowIndex, columnIndex);
422
- const colspan = headerCell === null || headerCell === void 0 ? void 0 : headerCell.getAttribute('colspan');
423
- let colspanAttribute = '';
424
- if (colspan) {
425
- const parsedColspan = parseInt(colspan, 10);
426
- colspanAttribute = ` colspan=${parsedColspan}`;
427
- i += parsedColspan - 1;
428
- }
429
- rowHTML.push(`<th${colspanAttribute}>${encodeHTMLEntities(parseEmptyValues(hotInstance.getColHeader(columnIndex, rowIndex)))}</th>`);
91
+ // eslint-disable-next-line no-restricted-globals
92
+ export function _dataToHTML(input) {
93
+ const inputLen = input.length;
94
+ const result = ['<table>'];
95
+ for (let row = 0; row < inputLen; row += 1) {
96
+ const rowData = input[row];
97
+ const columnsLen = rowData.length;
98
+ const columnsResult = [];
99
+ if (row === 0) {
100
+ result.push('<tbody>');
430
101
  }
431
- rowHTML.push('</tr>');
432
- headersHTML.push(...rowHTML);
433
- });
434
- return ['<thead>', ...headersHTML, '</thead>'];
435
- }
436
-
437
- /**
438
- * Converts Handsontable's coordinates into list of values for cells being headers.
439
- *
440
- * @param {Core} hotInstance The Handsontable instance.
441
- * @param {object} config Configuration for building the cell value list.
442
- * @param {Array<number>} config.rows List of row indexes which should be taken into account when creating the
443
- * cell value list.
444
- * @param {Array<number>} config.columns List of column indexes which should be taken into account when creating the
445
- * cell value list.
446
- * @returns {Array[]} List of displayed cell values.
447
- */
448
- function getHeadersDataByCoords(hotInstance, config) {
449
- const headersData = [];
450
- const {
451
- columns,
452
- rows
453
- } = config;
454
- const headers = rows.filter(rowIndex => rowIndex < 0);
455
- headers.forEach(rowIndex => {
456
- const rowData = [];
457
- for (let i = 0; i < columns.length; i += 1) {
458
- const columnIndex = columns[i];
459
- const headerCell = hotInstance.getCell(rowIndex, columnIndex);
460
- const colspan = headerCell === null || headerCell === void 0 ? void 0 : headerCell.getAttribute('colspan');
461
- rowData.push(hotInstance.getColHeader(columnIndex, rowIndex));
462
- if (colspan) {
463
- const parsedColspan = parseInt(colspan, 10);
464
- rowData.push(...new Array(parsedColspan - 1).fill(''));
465
- i += parsedColspan - 1;
466
- }
102
+ for (let column = 0; column < columnsLen; column += 1) {
103
+ const cellData = rowData[column];
104
+ const parsedCellData = isEmpty(cellData) ? '' : cellData.toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/(<br(\s*|\/)>(\r\n|\n)?|\r\n|\n)/g, '<br>\r\n').replace(/\x20{2,}/gi, substring => {
105
+ // The way how Excel serializes data with at least two spaces.
106
+ return `<span style="mso-spacerun: yes">${'&nbsp;'.repeat(substring.length - 1)} </span>`;
107
+ }).replace(/\t/gi, '&#9;');
108
+ columnsResult.push(`<td>${parsedCellData}</td>`);
109
+ }
110
+ result.push('<tr>', ...columnsResult, '</tr>');
111
+ if (row + 1 === inputLen) {
112
+ result.push('</tbody>');
467
113
  }
468
- headersData.push(rowData);
469
- });
470
- return headersData;
471
- }
472
-
473
- /**
474
- * Converts Handsontable's header coordinates into HTMLTableElement.tBodies.
475
- *
476
- * @param {Core} hotInstance The Handsontable instance.
477
- * @param {object} config Configuration for building HTMLTableElement.
478
- * @param {Array<number>} config.rows List of row indexes which should be taken into account when creating the table.
479
- * @param {Array<number>} config.columns List of column indexes which should be taken into account when creating the table.
480
- * @returns {Array<string>} List of HTMLElements stored as strings.
481
- */
482
- function getBodyHTMLByCoords(hotInstance, config) {
483
- const {
484
- columns,
485
- rows
486
- } = config;
487
- const bodyRows = rows.filter(rowIndex => rowIndex >= 0);
488
- const cells = [];
489
- if (bodyRows.length === 0 || columns.length === 0) {
490
- return [];
491
114
  }
492
- bodyRows.forEach((rowIndex, nthRow) => {
493
- const rowHTML = ['<tr>'];
494
- columns.forEach((columnIndex, nthColumn) => {
495
- if (columnIndex < 0) {
496
- rowHTML.push(`<th>${encodeHTMLEntities(parseEmptyValues(hotInstance.getRowHeader(rowIndex)))}</th>`);
497
- return;
498
- }
499
- const cellValue = hotInstance.getCopyableData(rowIndex, columnIndex);
500
- const cellValueParsed = encodeHTMLEntities(parseEmptyValues(cellValue));
501
- const {
502
- hidden,
503
- rowspan,
504
- colspan
505
- } = hotInstance.getCellMeta(rowIndex, columnIndex);
506
- if (!hidden) {
507
- const attrs = [];
508
- if (rowspan) {
509
- const recalculatedRowSpan = Math.min(rowspan, bodyRows.slice(nthRow).length);
510
- if (recalculatedRowSpan > 1) {
511
- attrs.push(` rowspan="${recalculatedRowSpan}"`);
512
- }
513
- }
514
- if (colspan) {
515
- const recalculatedColumnSpan = Math.min(colspan, columns.slice(nthColumn).length);
516
- if (recalculatedColumnSpan > 1) {
517
- attrs.push(` colspan="${recalculatedColumnSpan}"`);
518
- }
519
- }
520
- rowHTML.push(`<td${attrs.join('')}>${cellValueParsed}</td>`);
521
- }
522
- });
523
- rowHTML.push('</tr>');
524
- cells.push(...rowHTML);
525
- });
526
- return ['<tbody>', ...cells, '</tbody>'];
527
- }
528
-
529
- /**
530
- * Converts Handsontable's coordinates into list of values for cells not being headers.
531
- *
532
- * @param {Core} hotInstance The Handsontable instance.
533
- * @param {object} config Configuration for building the cell value list.
534
- * @param {Array<number>} config.rows List of row indexes which should be taken into account when creating the
535
- * cell value list.
536
- * @param {Array<number>} config.columns List of column indexes which should be taken into account when creating the
537
- * cell value list.
538
- * @returns {Array[]} List of displayed cell values.
539
- */
540
- function getBodyDataByCoords(hotInstance, config) {
541
- const cells = [];
542
- const {
543
- columns,
544
- rows
545
- } = config;
546
- const bodyRows = rows.filter(rowIndex => rowIndex >= 0);
547
- bodyRows.forEach(rowIndex => {
548
- const rowData = [];
549
- columns.forEach(columnIndex => {
550
- const cellValue = hotInstance.getCopyableData(rowIndex, columnIndex);
551
- const cellValueParsed = isEmpty(cellValue) ? '' : cellValue;
552
- rowData.push(cellValueParsed);
553
- });
554
- cells.push(rowData);
555
- });
556
- return cells;
115
+ result.push('</table>');
116
+ return result.join('');
557
117
  }
558
118
 
559
119
  /**
@@ -601,19 +161,18 @@ export function htmlToGridSettings(element) {
601
161
  }
602
162
  return !isDataRow;
603
163
  });
604
- const isAnyNested = thRows.find(tr => tr.querySelector('th[colspan]') !== null) !== undefined;
605
164
  thRowsLen = thRows.length;
606
165
  hasColHeaders = thRowsLen > 0;
607
- if (thRowsLen > 1 || isAnyNested) {
166
+ if (thRowsLen > 1) {
608
167
  settingsObj.nestedHeaders = Array.from(thRows).reduce((rows, row) => {
609
168
  const headersRow = Array.from(row.cells).reduce((headers, header, currentIndex) => {
610
169
  if (hasRowHeaders && currentIndex === 0) {
611
170
  return headers;
612
171
  }
613
172
  const {
614
- colSpan: colspan
173
+ colSpan: colspan,
174
+ innerHTML
615
175
  } = header;
616
- const innerHTML = decodeHTMLEntities(header.innerHTML);
617
176
  const nextHeader = colspan > 1 ? {
618
177
  label: innerHTML,
619
178
  colspan
@@ -629,7 +188,7 @@ export function htmlToGridSettings(element) {
629
188
  if (hasRowHeaders && index === 0) {
630
189
  return headers;
631
190
  }
632
- headers.push(decodeHTMLEntities(header.innerHTML));
191
+ headers.push(header.innerHTML);
633
192
  return headers;
634
193
  }, []);
635
194
  }
@@ -690,7 +249,7 @@ export function htmlToGridSettings(element) {
690
249
  } else {
691
250
  cellValue = innerHTML.replace(/<br(\s*|\/)>[\r\n]?/gim, '\r\n');
692
251
  }
693
- dataArr[row][col] = decodeHTMLEntities(cellValue);
252
+ dataArr[row][col] = cellValue.replace(regEscapedChars, match => ESCAPED_HTML_CHARS[match]);
694
253
  } else {
695
254
  rowHeaders.push(innerHTML);
696
255
  }