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