UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

358 lines (325 loc) 15.8 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ //Provides class sap.ui.core.util.PasteHelper - a utility for converting and validating data pasted from clipboard. sap.ui.define([ "sap/ui/core/Lib", "sap/ui/model/ParseException", "sap/ui/model/ValidateException", "sap/base/Log"], function(Library, ParseException, ValidateException, Log) { "use strict"; /** * A utility for converting and validating data pasted from the clipboard. This utility is used for importing data * from spreadsheets to SAPUI5 tables. * * @class Parses and validates data on the <code>paste</code> event of an SAPUI5 table. * @author SAP SE * @version 1.147.0 * @private * @ui5-restricted sap.ui.table.Table, sap.m.Table, sap.m.plugins.PasteProvider, sap.m.MultiInput, sap.ui.mdc.FieldBase, sap.ui.comp.smarttable.SmartTable * @alias sap.ui.core.util.PasteHelper */ var PasteHelper = {}; /** * Returns the clipboard text from the provided parameter. * * @param {ClipboardEvent|string} vEventOrText The paste event of the browser or the textual content of the clipboard returned from the navigator.clipboard.readText() API * @return {string} The textual content of the clipboard extracted from the parameter */ PasteHelper.getClipboardText = function(vEventOrText) { return (typeof vEventOrText == "string") ? vEventOrText : vEventOrText.clipboardData.getData("text"); }; /** * Parses the clipboard data from the <code>paste</code> event or <code>Clipboard.readText</code> API and converts the data into a * two-dimensional array that can be used further in SAPUI5 controls, for example, for importing data from spreadsheets to SAPUI5 tables. * * @param {ClipboardEvent|string} vEventOrText The paste event of the browser or the textual content of the clipboard returned from the navigator.clipboard.readText() API * This contains the clipboard data in the following export format: <br> * Cells are separated by tabs, lines by new line characters (\n, \r, and \r\n are supported). The cells containing * more than one line separated by new line characters are enclosed by double quotes (if there are already some * double quotes in the content, you will have multiple quotes in the cell). * @returns {Array} Returns a two-dimensional array containing the pasted data. If a single value is pasted (no grid data), * this value will still be in an array that is inside another array. */ PasteHelper.getPastedDataAs2DArray = function(vEventOrText) { // Variables for containing the input data and the end-result. var aData, sRow, aResult = []; // Placeholder for temporary replacement of the cells with multiple lines var rPlaceHolder = /sapui5Placeholder4MultiLine/g; var sPlaceHolder = rPlaceHolder.source; var rDoubleQuotes = /""/g; // Data directly from the clipboard with all special characters as in Spreadsheet/Excel var sData = this.getClipboardText(vEventOrText); // Array of the taken out cells with multiple lines var aCuts = []; // Flag - true if a multi-line cell is found var bMultiLineCellFound = false; // Index of the " that shows at multi-lines inside of one cell var index1 = sData.indexOf("\""), index2 = -1; var cNextChar, cPrevChar; while (index1 > -1) { // Identify if there are cells with multiple lines cPrevChar = sData.charAt(index1 - 1); // If the opening quotation mark comes after the new line, tab, return or it is the first char // in the first cell - then look for the closing quotation mark if ((index1 === 0) || (cPrevChar === '\n') || (cPrevChar === '\t') || (cPrevChar === '\r')) { index2 = sData.indexOf("\"", index1 + 1); if (index2 > -1) { // if at least one second " exists //Check if the found at position index2 quotation mark is closing one or belongs to the data cNextChar = sData.charAt(index2 + 1); while ((index2 > -1) && (cNextChar === '\"')) { // It is " around the customer data - jump to the next index2 = sData.indexOf("\"", index2 + 2); cNextChar = sData.charAt(index2 + 1); } // The closing quotation mark must be front of new line, tab, CReturn or to be the last char of the whole data if ((cNextChar === '\n') || (cNextChar === '\t') || (cNextChar === '') || (cNextChar === '\r')) { var sMultiLineCell = sData.substring(index1 + 1, index2); sData = sData.replace("\"" + sMultiLineCell + "\"", sPlaceHolder); // remove one of duplicated "" as it is only internal copy format of the excel sMultiLineCell = sMultiLineCell.replace(rDoubleQuotes, "\""); aCuts.push(sMultiLineCell); // Search for the next multi-line cell index1 = sData.indexOf("\"", index1 + sPlaceHolder.length + 1); bMultiLineCellFound = true; } } } if (!bMultiLineCellFound) { index1 = sData.indexOf("\"", index1 + 1); } bMultiLineCellFound = false; } aData = sData.split(/\r\n|\r|\n/); var j = 0; // Function that gives the next entry from the array of the cut off multi-line cells. var fnGetReplacement = function() { return aCuts[j++]; }; for (var i = 0; i < aData.length; i++) { // parse string into two-dimensional array sRow = aData[i]; if (aCuts.length > 0) { sRow = sRow.replace(rPlaceHolder, fnGetReplacement); } if (sRow.length || i < aData.length - 1) { // there's always an empty row appended aResult.push(sRow.split("\t")); } } // result: two-dimensional array containing the data return aResult; }; /** * Validates and parses the data of a two-dimensional array against SAPUI5 standard and EDM types based * on the <code>ColumnInfo</code> object and returns result with the parsed data (if the validation is successful) * or the error information (if the validation fails). * * @param {array} aData Two-dimensional array containing the pasted data * @param {array} aColumnInfo Provides information for each column, such as a property name and the corresponding data * type instance or validation function for custom data type, * as required in the target SAPUI5 table. * Use <code>ignore: true</code> for read-only columns or for all other columns that must not be pasted into a SAPUI5 table. * Use either <code>customParseFunction</code> or <code>type</code> parameter for the same column. * Function <code>customParseFunction</code> must return parsed value, if the validation for this custom type was successful, * or throw an exception if the validation failed. * Parameter <code>type</code> is an instance of the data type object and can include the original constraints and format options * * Example: * <pre> * var aColumnsInfo = [ * { * property: "name", * type: new sap.ui.model.odata.type.String() * }, * { * property: "lastname", * type: new sap.ui.model.odata.type.String({maxLength: 30}) * }, * { * property: "age", * type: new sap.ui.model.odata.type.Byte() * }, * { * property: "fullname", * customParseFunction: function(sCellData) { return ( doValidationAndModification(sCellData)) ...} * }, * { * ignore: true * }]; * </pre> * * @returns {Promise} Promise that gets resolved as soon as the parsing is done and provides the result object. * The result object contains parsed data if the validation was successful (in this case, * the error array has the value <code>null</code>), or it contains all collected errors if the validation failed (in this case, * the data array has the value <code>null</code>). * * Example of the result object after the successful validation - the data array is filled, and the error object is empty: * <pre> * oResult = { * parsedData: [ * {name: "/firstName", age: "/age"}, * {name: "myModel2>/firstName", age: "myModel2>/age"} * ], * errors: null * }); * </pre> * * Example of the result object after the failed validation - error information is available, and the data array is empty: * <pre> * oResult = { * parsedData: * null, * errors: [ * {row: 2 , column: 3, property: "age", value: "blub", type:"sap.ui.model.odata.type.Byte", * message: "Value "blub" in row 2 and column 2 could not be parsed. * } * ] * }); * </pre> */ PasteHelper.parse = function(aData, aColumnInfo) { var oResult = {parsedData: null, errors: null}; // Validate input Data if (!aData) { throw new Error("Parameter aData is not specified"); } // Validate columns information if (aColumnInfo) { for (var i = 0; i < aColumnInfo.length; i++) { var oColumnInfo = aColumnInfo[i]; // that should be only visible columns // Ignore columns and go to the next if (oColumnInfo.ignore) { continue; } if (oColumnInfo.property) { if (oColumnInfo.type) { var oType = oColumnInfo.type; // Check if it is an instance of the simple type class as expected if (oType.isA && oType.isA("sap.ui.model.SimpleType")) { oColumnInfo.typeInstance = oType; } else { throw new Error("Data type " + oColumnInfo.type + " is not an instance of any data type"); } } else if (oColumnInfo.customParseFunction == undefined) { // Exception for the application developers - the definition of the type is missing throw new Error("Missing ColumnInfo.type or custom type parse function for column " + (i + 1) + ". Check the application calling PasteHelper.parse(aData, aColumnInfo) and specify the missing type in the parameter aColumnInfo."); } } else { // Exception for the application developers - the name of property is missing throw new Error("Missing ColumnInfo.property for column " + (i + 1) + ". Check the application calling PasteHelper.parse(aData, aColumnInfo) and specify the missing property in the parameter aColumnInfo."); } } } else { throw new Error("Missing parameter aColumnInfo"); //Check - missing param standard exception? } var aErrors = [], aRowPromises = [], oBundle = Library.getResourceBundleFor("sap.ui.core"); var fnParse = function(sCellData, oType) { return oType.parseValue(sCellData, "string"); }; var fnValidate = function(sCellData, oType) { return oType.validateValue(sCellData); }; for (var i = 0; i < aData.length; i++) { var aRowData = aData[i]; // here a promise or an value may be returned var oSingleRowPromise = PasteHelper._parseRow(aRowData, aColumnInfo, i, fnParse, fnValidate, oBundle, aErrors); aRowPromises.push(oSingleRowPromise); // push promises in the promises array, so then we can iterate over it with PromiseAll() } return Promise.all(aRowPromises) .then(function(aResults) { if (aErrors.length > 0) { oResult.parsedData = null; oResult.errors = aErrors; } else { if (aResults) { oResult.parsedData = aResults; } oResult.errors = null; } return oResult; }); }; /** * Returns the result object for one single row that contains an array of parsed values if the validation was successful. This function also * adds errors to the given error array if any validation errors occur. * * @param {array} aRowData Contains data for one row * @param {array} aColumnInfo Provides information about the corresponding property name and its required type * @param {int} iRowIndex Index of the row * @param {function} fnParse Parse function parsing the value of the cell * @param {function} fnValidate validate function validating the value of the cell * @param {object} oBundle Resource bundle for translatable text of the error message * @param {array} aErrors Contains error information, such as the row and column number where the validation error occurs, the value, and the required type * @returns {object} Returns result object for one row with the name-values pairs that have passed the type validation successfully * @private */ PasteHelper._parseRow = function(aRowData, aColumnInfo, iRowIndex, fnParse, fnValidate, oBundle, aErrors) { var aCellPromises = [], oObject = {}; for (var i = 0; (i < aColumnInfo.length) && (i < aRowData.length); i++) { var oColumnInfo = aColumnInfo[i]; // that should be only visible columns // Ignore columns and go to the next if (oColumnInfo.ignore) { continue; } var sCellData = aRowData[i]; let fnCellParse = fnParse; let fnCellValidate = fnValidate; if (!oColumnInfo.typeInstance) { fnCellParse = oColumnInfo.customParseFunction; fnCellValidate = function() { }; } var oSingleCellPromise = PasteHelper._parseCell(i, sCellData, oColumnInfo, iRowIndex, fnCellParse, fnCellValidate, oBundle, oObject, aErrors); aCellPromises.push(oSingleCellPromise); } // Return object with name value pairs for the cells in a row as soon as the validation for all cells is finished return Promise.all(aCellPromises) .then(function() { return oObject; }); }; /** * Returns the result object for one single cell that contains an object of parsed name value pairs if the validation was successful. This function also * adds error to the given error array if any validation error occur. * * @param {int} i Index of the column * @param {string} sCellData Value of the cell to be parsed and validated * @param {object} oColumnInfo Provides information about the corresponding property name and its required type * @param {int} iRowIndex Index of the row * @param {function} fnParse Parse function parsing the value of the cell * @param {function} fnValidate validate function validating the value of the cell * @param {object} oBundle Resource bundle for translatable text of the error message * @param {object} oObject Result object for a cell with name value pair if the type validation is successful * @param {array} aErrors Contains error information, such as the row and column number where the validation error occurs, the value, and the required type * @private */ PasteHelper._parseCell = function(i, sCellData, oColumnInfo, iRowIndex, fnParse, fnValidate, oBundle, oObject, aErrors) { return Promise.resolve(sCellData) .then(function (vValue) { return fnParse(vValue, oColumnInfo.typeInstance); }).then(function (vValue) { // use Promise.all to get the value as the first argument as the validateData // fnValidate does not return the value, but returns a Promise or undefined if it is nor async type // do not forget handler for reject for validateValue return Promise.all([vValue, fnValidate(vValue, oColumnInfo.typeInstance)]); }).then(function (aResult) { oObject[oColumnInfo.property] = aResult[0]; }).catch(function (oException) { if (oException instanceof ParseException || oException instanceof ValidateException) { // Build error object for single cell var oError = { row: iRowIndex + 1, column: i + 1, property: oColumnInfo.property, value: sCellData, type: oColumnInfo.type, message: oBundle.getText("PasteHelper.ErrorMessage", [sCellData, iRowIndex + 1, i + 1]) + " " + oException.message + "\n"//"Value '" + sCellData + "' in row " + (iRowIndex + 1) + " and column " + (i + 1) + " could not be parsed." }; aErrors.push(oError); sCellData = null; } else { Log.error(oException); } }); }; return PasteHelper; });