274 lines
9.7 KiB
JavaScript
274 lines
9.7 KiB
JavaScript
"use strict";
|
|||
|
|
|
||
|
|
exports.__esModule = true;
|
||
|
|
exports._dataToHTML = _dataToHTML;
|
||
|
|
exports.htmlToGridSettings = htmlToGridSettings;
|
||
|
|
exports.instanceToHTML = instanceToHTML;
|
||
|
|
require("core-js/modules/es.array.push.js");
|
||
|
|
var _mixed = require("./../helpers/mixed");
|
||
|
|
const ESCAPED_HTML_CHARS = {
|
||
|
|
' ': '\x20',
|
||
|
|
'&': '&',
|
||
|
|
'<': '<',
|
||
|
|
'>': '>'
|
||
|
|
};
|
||
|
|
const regEscapedChars = new RegExp(Object.keys(ESCAPED_HTML_CHARS).map(key => `(${key})`).join('|'), 'gi');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Verifies if node is an HTMLTable element.
|
||
|
|
*
|
||
|
|
* @param {Node} element Node to verify if it's an HTMLTable.
|
||
|
|
* @returns {boolean}
|
||
|
|
*/
|
||
|
|
function isHTMLTable(element) {
|
||
|
|
return (element && element.nodeName || '') === 'TABLE';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Converts Handsontable into HTMLTableElement.
|
||
|
|
*
|
||
|
|
* @param {Core} instance The Handsontable instance.
|
||
|
|
* @returns {string} OuterHTML of the HTMLTableElement.
|
||
|
|
*/
|
||
|
|
function instanceToHTML(instance) {
|
||
|
|
const hasColumnHeaders = instance.hasColHeaders();
|
||
|
|
const hasRowHeaders = instance.hasRowHeaders();
|
||
|
|
const coords = [hasColumnHeaders ? -1 : 0, hasRowHeaders ? -1 : 0, instance.countRows() - 1, instance.countCols() - 1];
|
||
|
|
const data = instance.getData(...coords);
|
||
|
|
const countRows = data.length;
|
||
|
|
const countCols = countRows > 0 ? data[0].length : 0;
|
||
|
|
const TABLE = ['<table>', '</table>'];
|
||
|
|
const THEAD = hasColumnHeaders ? ['<thead>', '</thead>'] : [];
|
||
|
|
const TBODY = ['<tbody>', '</tbody>'];
|
||
|
|
const rowModifier = hasRowHeaders ? 1 : 0;
|
||
|
|
const columnModifier = hasColumnHeaders ? 1 : 0;
|
||
|
|
for (let row = 0; row < countRows; row += 1) {
|
||
|
|
const isColumnHeadersRow = hasColumnHeaders && row === 0;
|
||
|
|
const CELLS = [];
|
||
|
|
for (let column = 0; column < countCols; column += 1) {
|
||
|
|
const isRowHeadersColumn = !isColumnHeadersRow && hasRowHeaders && column === 0;
|
||
|
|
let cell = '';
|
||
|
|
if (isColumnHeadersRow) {
|
||
|
|
cell = `<th>${instance.getColHeader(column - rowModifier)}</th>`;
|
||
|
|
} else if (isRowHeadersColumn) {
|
||
|
|
cell = `<th>${instance.getRowHeader(row - columnModifier)}</th>`;
|
||
|
|
} else {
|
||
|
|
const cellData = data[row][column];
|
||
|
|
const {
|
||
|
|
hidden,
|
||
|
|
rowspan,
|
||
|
|
colspan
|
||
|
|
} = instance.getCellMeta(row - columnModifier, column - rowModifier);
|
||
|
|
if (!hidden) {
|
||
|
|
const attrs = [];
|
||
|
|
if (rowspan) {
|
||
|
|
attrs.push(`rowspan="${rowspan}"`);
|
||
|
|
}
|
||
|
|
if (colspan) {
|
||
|
|
attrs.push(`colspan="${colspan}"`);
|
||
|
|
}
|
||
|
|
if ((0, _mixed.isEmpty)(cellData)) {
|
||
|
|
cell = `<td ${attrs.join(' ')}></td>`;
|
||
|
|
} else {
|
||
|
|
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, '	');
|
||
|
|
cell = `<td ${attrs.join(' ')}>${value}</td>`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
CELLS.push(cell);
|
||
|
|
}
|
||
|
|
const TR = ['<tr>', ...CELLS, '</tr>'].join('');
|
||
|
|
if (isColumnHeadersRow) {
|
||
|
|
THEAD.splice(1, 0, TR);
|
||
|
|
} else {
|
||
|
|
TBODY.splice(-1, 0, TR);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
TABLE.splice(1, 0, THEAD.join(''), TBODY.join(''));
|
||
|
|
return TABLE.join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Converts 2D array into HTMLTableElement.
|
||
|
|
*
|
||
|
|
* @param {Array} input Input array which will be converted to HTMLTable.
|
||
|
|
* @returns {string} OuterHTML of the HTMLTableElement.
|
||
|
|
*/
|
||
|
|
// eslint-disable-next-line no-restricted-globals
|
||
|
|
function _dataToHTML(input) {
|
||
|
|
const inputLen = input.length;
|
||
|
|
const result = ['<table>'];
|
||
|
|
for (let row = 0; row < inputLen; row += 1) {
|
||
|
|
const rowData = input[row];
|
||
|
|
const columnsLen = rowData.length;
|
||
|
|
const columnsResult = [];
|
||
|
|
if (row === 0) {
|
||
|
|
result.push('<tbody>');
|
||
|
|
}
|
||
|
|
for (let column = 0; column < columnsLen; column += 1) {
|
||
|
|
const cellData = rowData[column];
|
||
|
|
const parsedCellData = (0, _mixed.isEmpty)(cellData) ? '' : cellData.toString().replace(/</g, '<').replace(/>/g, '>').replace(/(<br(\s*|\/)>(\r\n|\n)?|\r\n|\n)/g, '<br>\r\n').replace(/\x20{2,}/gi, substring => {
|
||
|
|
// The way how Excel serializes data with at least two spaces.
|
||
|
|
return `<span style="mso-spacerun: yes">${' '.repeat(substring.length - 1)} </span>`;
|
||
|
|
}).replace(/\t/gi, '	');
|
||
|
|
columnsResult.push(`<td>${parsedCellData}</td>`);
|
||
|
|
}
|
||
|
|
result.push('<tr>', ...columnsResult, '</tr>');
|
||
|
|
if (row + 1 === inputLen) {
|
||
|
|
result.push('</tbody>');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
result.push('</table>');
|
||
|
|
return result.join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Converts HTMLTable or string into Handsontable configuration object.
|
||
|
|
*
|
||
|
|
* @param {Element|string} element Node element which should contain `<table>...</table>`.
|
||
|
|
* @param {Document} [rootDocument] The document window owner.
|
||
|
|
* @returns {object} Return configuration object. Contains keys as DefaultSettings.
|
||
|
|
*/
|
||
|
|
// eslint-disable-next-line no-restricted-globals
|
||
|
|
function htmlToGridSettings(element) {
|
||
|
|
let rootDocument = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document;
|
||
|
|
const settingsObj = {};
|
||
|
|
const fragment = rootDocument.createDocumentFragment();
|
||
|
|
const tempElem = rootDocument.createElement('div');
|
||
|
|
fragment.appendChild(tempElem);
|
||
|
|
let checkElement = element;
|
||
|
|
if (typeof checkElement === 'string') {
|
||
|
|
const escapedAdjacentHTML = checkElement.replace(/<td\b[^>]*?>([\s\S]*?)<\/\s*td>/g, cellFragment => {
|
||
|
|
const openingTag = cellFragment.match(/<td\b[^>]*?>/g)[0];
|
||
|
|
const cellValue = cellFragment.substring(openingTag.length, cellFragment.lastIndexOf('<')).replace(/(<(?!br)([^>]+)>)/gi, '');
|
||
|
|
const closingTag = '</td>';
|
||
|
|
return `${openingTag}${cellValue}${closingTag}`;
|
||
|
|
});
|
||
|
|
tempElem.insertAdjacentHTML('afterbegin', `${escapedAdjacentHTML}`);
|
||
|
|
checkElement = tempElem.querySelector('table');
|
||
|
|
}
|
||
|
|
if (!checkElement || !isHTMLTable(checkElement)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const generator = tempElem.querySelector('meta[name$="enerator"]');
|
||
|
|
const hasRowHeaders = checkElement.querySelector('tbody th') !== null;
|
||
|
|
const trElement = checkElement.querySelector('tr');
|
||
|
|
const countCols = !trElement ? 0 : Array.from(trElement.cells).reduce((cols, cell) => cols + cell.colSpan, 0) - (hasRowHeaders ? 1 : 0);
|
||
|
|
const fixedRowsBottom = checkElement.tFoot && Array.from(checkElement.tFoot.rows) || [];
|
||
|
|
const fixedRowsTop = [];
|
||
|
|
let hasColHeaders = false;
|
||
|
|
let thRowsLen = 0;
|
||
|
|
let countRows = 0;
|
||
|
|
if (checkElement.tHead) {
|
||
|
|
const thRows = Array.from(checkElement.tHead.rows).filter(tr => {
|
||
|
|
const isDataRow = tr.querySelector('td') !== null;
|
||
|
|
if (isDataRow) {
|
||
|
|
fixedRowsTop.push(tr);
|
||
|
|
}
|
||
|
|
return !isDataRow;
|
||
|
|
});
|
||
|
|
thRowsLen = thRows.length;
|
||
|
|
hasColHeaders = thRowsLen > 0;
|
||
|
|
if (thRowsLen > 1) {
|
||
|
|
settingsObj.nestedHeaders = Array.from(thRows).reduce((rows, row) => {
|
||
|
|
const headersRow = Array.from(row.cells).reduce((headers, header, currentIndex) => {
|
||
|
|
if (hasRowHeaders && currentIndex === 0) {
|
||
|
|
return headers;
|
||
|
|
}
|
||
|
|
const {
|
||
|
|
colSpan: colspan,
|
||
|
|
innerHTML
|
||
|
|
} = header;
|
||
|
|
const nextHeader = colspan > 1 ? {
|
||
|
|
label: innerHTML,
|
||
|
|
colspan
|
||
|
|
} : innerHTML;
|
||
|
|
headers.push(nextHeader);
|
||
|
|
return headers;
|
||
|
|
}, []);
|
||
|
|
rows.push(headersRow);
|
||
|
|
return rows;
|
||
|
|
}, []);
|
||
|
|
} else if (hasColHeaders) {
|
||
|
|
settingsObj.colHeaders = Array.from(thRows[0].children).reduce((headers, header, index) => {
|
||
|
|
if (hasRowHeaders && index === 0) {
|
||
|
|
return headers;
|
||
|
|
}
|
||
|
|
headers.push(header.innerHTML);
|
||
|
|
return headers;
|
||
|
|
}, []);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (fixedRowsTop.length) {
|
||
|
|
settingsObj.fixedRowsTop = fixedRowsTop.length;
|
||
|
|
}
|
||
|
|
if (fixedRowsBottom.length) {
|
||
|
|
settingsObj.fixedRowsBottom = fixedRowsBottom.length;
|
||
|
|
}
|
||
|
|
const dataRows = [...fixedRowsTop, ...Array.from(checkElement.tBodies).reduce((sections, section) => {
|
||
|
|
sections.push(...Array.from(section.rows));
|
||
|
|
return sections;
|
||
|
|
}, []), ...fixedRowsBottom];
|
||
|
|
countRows = dataRows.length;
|
||
|
|
const dataArr = new Array(countRows);
|
||
|
|
for (let r = 0; r < countRows; r++) {
|
||
|
|
dataArr[r] = new Array(countCols);
|
||
|
|
}
|
||
|
|
const mergeCells = [];
|
||
|
|
const rowHeaders = [];
|
||
|
|
for (let row = 0; row < countRows; row++) {
|
||
|
|
const tr = dataRows[row];
|
||
|
|
const cells = Array.from(tr.cells);
|
||
|
|
const cellsLen = cells.length;
|
||
|
|
for (let cellId = 0; cellId < cellsLen; cellId++) {
|
||
|
|
const cell = cells[cellId];
|
||
|
|
const {
|
||
|
|
nodeName,
|
||
|
|
innerHTML,
|
||
|
|
rowSpan: rowspan,
|
||
|
|
colSpan: colspan
|
||
|
|
} = cell;
|
||
|
|
const col = dataArr[row].findIndex(value => value === void 0);
|
||
|
|
if (nodeName === 'TD') {
|
||
|
|
if (rowspan > 1 || colspan > 1) {
|
||
|
|
for (let rstart = row; rstart < row + rowspan; rstart++) {
|
||
|
|
if (rstart < countRows) {
|
||
|
|
for (let cstart = col; cstart < col + colspan; cstart++) {
|
||
|
|
dataArr[rstart][cstart] = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const styleAttr = cell.getAttribute('style');
|
||
|
|
const ignoreMerge = styleAttr && styleAttr.includes('mso-ignore:colspan');
|
||
|
|
if (!ignoreMerge) {
|
||
|
|
mergeCells.push({
|
||
|
|
col,
|
||
|
|
row,
|
||
|
|
rowspan,
|
||
|
|
colspan
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
let cellValue = '';
|
||
|
|
if (generator && /excel/gi.test(generator.content)) {
|
||
|
|
cellValue = innerHTML.replace(/[\r\n][\x20]{0,2}/g, '\x20').replace(/<br(\s*|\/)>[\r\n]?[\x20]{0,3}/gim, '\r\n');
|
||
|
|
} else {
|
||
|
|
cellValue = innerHTML.replace(/<br(\s*|\/)>[\r\n]?/gim, '\r\n');
|
||
|
|
}
|
||
|
|
dataArr[row][col] = cellValue.replace(regEscapedChars, match => ESCAPED_HTML_CHARS[match]);
|
||
|
|
} else {
|
||
|
|
rowHeaders.push(innerHTML);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (mergeCells.length) {
|
||
|
|
settingsObj.mergeCells = mergeCells;
|
||
|
|
}
|
||
|
|
if (rowHeaders.length) {
|
||
|
|
settingsObj.rowHeaders = rowHeaders;
|
||
|
|
}
|
||
|
|
if (dataArr.length) {
|
||
|
|
settingsObj.data = dataArr;
|
||
|
|
}
|
||
|
|
return settingsObj;
|
||
|
|
}
|