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.
- package/base.js +2 -2
- package/base.mjs +2 -2
- package/dist/handsontable.css +8 -2
- package/dist/handsontable.full.css +8 -2
- package/dist/handsontable.full.js +2084 -3359
- package/dist/handsontable.full.min.css +3 -3
- package/dist/handsontable.full.min.js +59 -66
- package/dist/handsontable.js +2086 -3361
- package/dist/handsontable.min.css +3 -3
- package/dist/handsontable.min.js +16 -23
- package/helpers/mixed.js +2 -2
- package/helpers/mixed.mjs +2 -2
- package/package.json +1 -1
- package/pluginHooks.d.ts +6 -28
- package/pluginHooks.js +63 -117
- package/pluginHooks.mjs +63 -117
- package/plugins/copyPaste/clipboardData.js +18 -0
- package/plugins/copyPaste/clipboardData.mjs +14 -0
- package/plugins/copyPaste/copyPaste.js +92 -38
- package/plugins/copyPaste/copyPaste.mjs +94 -40
- package/plugins/copyPaste/pasteEvent.js +14 -0
- package/plugins/copyPaste/pasteEvent.mjs +9 -0
- package/plugins/mergeCells/mergeCells.js +14 -0
- package/plugins/mergeCells/mergeCells.mjs +14 -0
- package/plugins/nestedHeaders/nestedHeaders.js +22 -21
- package/plugins/nestedHeaders/nestedHeaders.mjs +22 -21
- package/utils/parseTable.js +83 -527
- package/utils/parseTable.mjs +82 -523
- package/plugins/copyPaste/clipboardData/clipboardData.js +0 -516
- package/plugins/copyPaste/clipboardData/clipboardData.mjs +0 -512
- package/plugins/copyPaste/clipboardData/copyClipboardData.js +0 -69
- package/plugins/copyPaste/clipboardData/copyClipboardData.mjs +0 -65
- package/plugins/copyPaste/clipboardData/index.js +0 -9
- package/plugins/copyPaste/clipboardData/index.mjs +0 -4
- package/plugins/copyPaste/clipboardData/pasteClipboardData.js +0 -81
- package/plugins/copyPaste/clipboardData/pasteClipboardData.mjs +0 -77
package/utils/parseTable.mjs
CHANGED
@@ -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}
|
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
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
} =
|
284
|
-
if (
|
285
|
-
attrs
|
286
|
-
|
287
|
-
|
288
|
-
|
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('<', '<').replace('>', '>').replace(/(<br(\s*|\/)>(\r\n|\n)?|\r\n|\n)/g, '<br>\r\n').replace(/\x20/gi, ' ').replace(/\t/gi, '	');
|
68
|
+
cell = `<td ${attrs.join(' ')}>${value}</td>`;
|
69
|
+
}
|
289
70
|
}
|
290
|
-
} else if (mergedArea.has(checkedMergeCoordinate)) {
|
291
|
-
return;
|
292
71
|
}
|
293
|
-
|
294
|
-
}
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
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
|
-
|
368
|
-
return
|
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, '&').replace('<', '<').replace('>', '>').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">${' '.repeat(substring.length - 1)} </span>`;
|
381
|
-
}).replace(/\t/gi, '	');
|
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(' ', ' ');
|
81
|
+
TABLE.splice(1, 0, THEAD.join(''), TBODY.join(''));
|
82
|
+
return TABLE.join('');
|
394
83
|
}
|
395
84
|
|
396
85
|
/**
|
397
|
-
* Converts
|
86
|
+
* Converts 2D array into HTMLTableElement.
|
398
87
|
*
|
399
|
-
* @param {
|
400
|
-
* @
|
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
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
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
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
}
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
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, '&').replace(/</g, '<').replace(/>/g, '>').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">${' '.repeat(substring.length - 1)} </span>`;
|
107
|
+
}).replace(/\t/gi, '	');
|
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
|
-
|
493
|
-
|
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
|
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(
|
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] =
|
252
|
+
dataArr[row][col] = cellValue.replace(regEscapedChars, match => ESCAPED_HTML_CHARS[match]);
|
694
253
|
} else {
|
695
254
|
rowHeaders.push(innerHTML);
|
696
255
|
}
|