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

Sign up to get free protection for your applications and to get access to all the features.
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
  }