@sapui5/sap.ui.export 1.141.2 → 1.143.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,8 +5,8 @@ Runtime resources of the [SAPUI5](https://ui5.sap.com) Library **sap.ui.export**
5
5
 
6
6
  ## Usage
7
7
  Refrain from installing this package using npm, Yarn or similar package managers.
8
- It is meant to be consumed using the [UI5 Tooling](https://sap.github.io/ui5-tooling/).
9
- For details please refer to our documentation on [Consuming SAPUI5 Libraries](https://sap.github.io/ui5-tooling/pages/SAPUI5/).
8
+ It is meant to be consumed using the [UI5 CLI](https://ui5.github.io/cli/).
9
+ For details please refer to our documentation on [Consuming SAPUI5 Libraries](https://ui5.github.io/cli/pages/SAPUI5/).
10
10
 
11
11
  ## License
12
12
  This package is provided under the terms of the [SAP Developer License Agreement](https://tools.hana.ondemand.com/developer-license-3_2.txt).
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@sapui5/sap.ui.export",
3
- "version": "1.141.2",
3
+ "version": "1.143.0",
4
4
  "description": "SAPUI5 Library sap.ui.export",
5
- "homepage": "https://sap.github.io/ui5-tooling/pages/SAPUI5/",
5
+ "homepage": "https://ui5.github.io/cli/pages/SAPUI5/",
6
6
  "author": "SAP SE (https://www.sap.com)",
7
7
  "license": "SEE LICENSE IN LICENSE.txt",
8
8
  "keywords": [
@@ -5,7 +5,7 @@
5
5
  <vendor>SAP SE</vendor>
6
6
  <copyright>SAPUI5
7
7
  * (c) Copyright 2025 SAP SE. All rights reserved.</copyright>
8
- <version>1.141.2</version>
8
+ <version>1.143.0</version>
9
9
 
10
10
  <documentation>UI5 library: sap.ui.export</documentation>
11
11
 
@@ -0,0 +1,188 @@
1
+ /*!
2
+ * SAPUI5
3
+ * (c) Copyright 2025 SAP SE. All rights reserved.
4
+ */
5
+
6
+ sap.ui.define([
7
+ "./library",
8
+ "sap/base/Log",
9
+ "sap/ui/export/js/CSVBuilder",
10
+ "sap/ui/export/ExportBase",
11
+ "sap/ui/export/ExportUtils"
12
+ ], function(library, Log, CSVBuilder, ExportBase, ExportUtils) {
13
+ "use strict";
14
+
15
+ const FileType = library.FileType;
16
+
17
+ /**
18
+ * Provides functionality to export data in CSV format.
19
+ *
20
+ * The <code>CommaSeparatedValues</code> class extends the <code>ExportBase</code> class and provides the functionality to create CSV files.
21
+ * It supports appending data, validating data, escaping content, and building the final CSV file. Additionally, it provides methods to
22
+ * process data sources, apply default export settings, and manage the export process.
23
+ *
24
+ * There are the following key features:
25
+ * <ul>
26
+ * <li>Supports JSON arrays and ClientListBindings as data sources.</li>
27
+ * <li>Escapes special characters and prevents CSV injection.</li>
28
+ * <li>Adds a UTF-8 Byte Order Mark (BOM) for compatibility with spreadsheet software.</li>
29
+ * </ul>
30
+ *
31
+ * Example Usage:
32
+ * ```javascript
33
+ * const oCSV = new sap.ui.export.CommaSeparatedValues(mSettings);
34
+ * oCSV.build();
35
+ * ```
36
+ *
37
+ * @class sap.ui.export.CommaSeparatedValues
38
+ * @extends sap.ui.export.ExportBase
39
+ * @alias sap.ui.export.CommaSeparatedValues
40
+ * @public
41
+ * @since 1.142
42
+ */
43
+ const CSV = ExportBase.extend("sap.ui.export.CommaSeparatedValues", {});
44
+
45
+ /**
46
+ * Marks the current export process as cancelled which prevents the file from being saved.
47
+ *
48
+ * @returns {this} Reference to <code>this</code> in order to allow method chaining
49
+ *
50
+ * @public
51
+ */
52
+ CSV.prototype.cancel = function() {
53
+ if (!this._bIsCancelled) {
54
+ this._bIsCancelled = true;
55
+ }
56
+
57
+ return this;
58
+ };
59
+
60
+ /**
61
+ * Sets the data source configuration that is used for exporting the data.
62
+ *
63
+ * <ul>
64
+ * <li>If the passed parameter is null, the call is ignored.</li>
65
+ * <li>Supports JSON arrays as data sources.</li>
66
+ * <li>Logs an error for unsupported data source types.</li>
67
+ * </ul>
68
+ *
69
+ * @param {object|sap.ui.model.ListBinding|sap.ui.model.TreeBinding} oDataSource Possible types are a data
70
+ * source configuration, a <code>sap.ui.model.ListBinding</code> or <code>sap.ui.model.TreeBinding</code>
71
+ * @returns {object|null} Valid <code>dataSource</code> object or null in case the <code>dataSource</code> configuration is not supported
72
+ *
73
+ * @private
74
+ */
75
+ CSV.prototype.processDataSource = function(oDataSource) {
76
+ const sDataSourceType = typeof oDataSource;
77
+ const mDataSource = {data: [], type: "array"};
78
+
79
+ if (!oDataSource) {
80
+ return null;
81
+ }
82
+
83
+ if (sDataSourceType !== "object") {
84
+ Log.error("CommaSeparatedValues#processDataSource: Unable to apply data source of type " + sDataSourceType);
85
+
86
+ return null;
87
+ }
88
+
89
+ if (oDataSource.dataUrl) {
90
+ Log.error("CommaSeparatedValues#processDataSource: URLs (such as dataUrl) are not supported as data sources. Type: " + sDataSourceType);
91
+
92
+ return null;
93
+ }
94
+
95
+ if (Array.isArray(oDataSource)) {
96
+ mDataSource.data = oDataSource;
97
+ }
98
+
99
+ /**
100
+ * If <code>ClientListBinding</code>, we use the binding path to receive the
101
+ * data from the underlying model. This takes sorter and filters into account.
102
+ */
103
+ if (oDataSource.isA?.("sap.ui.model.ClientListBinding")) {
104
+ const aData = [];
105
+
106
+ oDataSource.getAllCurrentContexts().forEach(function(oContext) {
107
+ aData.push(oContext.getObject());
108
+ });
109
+
110
+ mDataSource.data = aData;
111
+ }
112
+
113
+ return mDataSource;
114
+ };
115
+
116
+ /**
117
+ * Applies default settings to the export configuration.
118
+ *
119
+ * - Adjusts the provided settings object to include default values where necessary.
120
+ * - Delegates the actual adjustment logic to the CSVBuilder instance.
121
+ *
122
+ * @param {object} mParameters Export parameters object
123
+ *
124
+ * @returns {Promise} A Promise that resolves when default settings have been applied
125
+ * @private
126
+ */
127
+ CSV.prototype.setDefaultExportSettings = function(mParameters) {
128
+ if (!mParameters.fileType) {
129
+ mParameters.fileType = FileType.CSV;
130
+ }
131
+
132
+ if (!mParameters.workbook.separator) {
133
+ mParameters.workbook.separator = ",";
134
+ }
135
+
136
+ return Promise.resolve();
137
+ };
138
+
139
+ /**
140
+ * Creates a Promise that is resolved after the export has been finished.
141
+ *
142
+ * - Ensures that no other export process is running before starting a new one.
143
+ * - Resolves the Promise with the result of the build process.
144
+ * - Rejects the Promise if an error occurs or if a process is already running.
145
+ *
146
+ * @param {object} mParameters Validated export configuration
147
+ * @returns {Promise} A Promise that resolves when the build is complete
148
+ *
149
+ * @private
150
+ */
151
+ CSV.prototype.createBuildPromise = function(mParameters) {
152
+ try {
153
+ if (this._oBuilder) {
154
+ return Promise.reject('Cannot start export: The process is already running');
155
+ }
156
+
157
+ const mWorkbook = mParameters.workbook;
158
+
159
+ this._bIsCancelled = false;
160
+ this._oBuilder = new CSVBuilder(mWorkbook.columns, mWorkbook.separator);
161
+ this._oBuilder.append(mParameters.dataSource.data);
162
+
163
+ const oBlob = this._oBuilder.build();
164
+
165
+ if (!this._bIsCancelled) {
166
+ ExportUtils.saveAsFile(oBlob, mParameters.fileName);
167
+ }
168
+
169
+ this._oBuilder = null;
170
+
171
+ return Promise.resolve();
172
+ } catch (oError) {
173
+ return Promise.reject(oError);
174
+ }
175
+ };
176
+
177
+ /**
178
+ * Returns the specific MIME type.
179
+ *
180
+ * @public
181
+ * @returns {string} The MIME type of the Comma Separated Values format
182
+ */
183
+ CSV.prototype.getMimeType = function() {
184
+ return "text/csv";
185
+ };
186
+
187
+ return CSV;
188
+ });
@@ -27,7 +27,7 @@ sap.ui.define([
27
27
  * @class The <code>sap.ui.export.ExportBase</code> class allows you to export table data from a UI5 application to certain formats. This class is an abstract class that requires specific implementations for each file format.
28
28
  *
29
29
  * @author SAP SE
30
- * @version 1.141.2
30
+ * @version 1.143.0
31
31
  *
32
32
  * @since 1.96
33
33
  * @alias sap.ui.export.ExportBase
@@ -28,7 +28,7 @@ sap.ui.define([
28
28
  * @class The <code>sap.ui.export.ExportHandler</code> class allows you to export table data from an SAPUI5 application.
29
29
  *
30
30
  * @author SAP SE
31
- * @version 1.141.2
31
+ * @version 1.143.0
32
32
  *
33
33
  * @since 1.102
34
34
  * @alias sap.ui.export.ExportHandler
@@ -145,7 +145,7 @@ sap.ui.define([
145
145
  * @class Utilities related to export to enable reuse in integration scenarios (e.g. tables).
146
146
  *
147
147
  * @author SAP SE
148
- * @version 1.141.2
148
+ * @version 1.143.0
149
149
  *
150
150
  * @since 1.59
151
151
  * @alias sap.ui.export.ExportUtils
@@ -1245,7 +1245,7 @@ sap.ui.define([
1245
1245
  *
1246
1246
  * @param {object} oContext Context object
1247
1247
  * @param {string} [oContext.application] Name of the application (default: "SAP UI5")
1248
- * @param {string} [oContext.version] Application version (default: "1.141.2")
1248
+ * @param {string} [oContext.version] Application version (default: "1.143.0")
1249
1249
  * @param {string} [oContext.title] Title that will be written to the file (NOT the filename)
1250
1250
  * @param {string} [oContext.modifiedBy] Optional user context that will be written to the file
1251
1251
  * @param {string} [oContext.sheetName] Name of the data sheet - Maximum length of 31 characters
@@ -28,7 +28,7 @@ sap.ui.define([
28
28
  * @class The <code>sap.ui.export.PortableDocument</code> class allows you to export table data from a UI5 application to a Portable Document Format (*.PDF) file.
29
29
  *
30
30
  * @author SAP SE
31
- * @version 1.141.2
31
+ * @version 1.143.0
32
32
  *
33
33
  * @since 1.96
34
34
  * @alias sap.ui.export.PortableDocument
@@ -96,7 +96,7 @@ sap.ui.define([
96
96
  * columns: aColumns,
97
97
  * context: {
98
98
  * application: 'Debug Test Application',
99
- * version: '1.141.2',
99
+ * version: '1.143.0',
100
100
  * title: 'Some random title',
101
101
  * modifiedBy: 'John Doe',
102
102
  * metaSheetName: 'Custom metadata',
@@ -183,7 +183,7 @@ sap.ui.define([
183
183
  * @class The <code>sap.ui.export.Spreadsheet</code> class allows you to export table data from a UI5 application to a spreadsheet file.
184
184
  *
185
185
  * @author SAP SE
186
- * @version 1.141.2
186
+ * @version 1.143.0
187
187
  *
188
188
  * @since 1.50
189
189
  * @alias sap.ui.export.Spreadsheet
@@ -21,7 +21,7 @@ sap.ui.define(['sap/base/Log', 'sap/ui/export/ExportUtils'], function(Log, Expor
21
21
  * Utility class to perform spreadsheet export.
22
22
  *
23
23
  * @author SAP SE
24
- * @version 1.141.2
24
+ * @version 1.143.0
25
25
  *
26
26
  * @alias sap.ui.export.SpreadsheetExport
27
27
  * @private
@@ -51,8 +51,8 @@ sap.ui.define(['sap/base/Log', 'sap/ui/export/ExportUtils'], function(Log, Expor
51
51
  });
52
52
  }
53
53
 
54
- function onError(error) {
55
- postMessage({ error: error.message || error });
54
+ function onError(oError) {
55
+ postMessage({ error: oError.message || oError });
56
56
  }
57
57
 
58
58
  function onFinish(oArrayBuffer) {
@@ -62,8 +62,8 @@ sap.ui.define(['sap/base/Log', 'sap/ui/export/ExportUtils'], function(Log, Expor
62
62
  // Export directly from an array in memory.
63
63
  // TBD: convert dates as in exportUtils
64
64
  function exportArray() {
65
- var oSpreadsheet;
66
- var fnConvertData;
65
+ let oSpreadsheet;
66
+ let fnConvertData;
67
67
 
68
68
  function start(DataProvider, XLSXBuilder) {
69
69
  fnConvertData = DataProvider.getDataConverter(mParams);
@@ -85,7 +85,7 @@ sap.ui.define(['sap/base/Log', 'sap/ui/export/ExportUtils'], function(Log, Expor
85
85
  }
86
86
 
87
87
  function exportInProcess() {
88
- var oSpreadsheet, oRequest;
88
+ let oSpreadsheet, oRequest;
89
89
 
90
90
  function start(DataProvider, XLSXBuilder) {
91
91
  var provider = new DataProvider(mParams);
@@ -122,42 +122,64 @@ sap.ui.define(['sap/base/Log', 'sap/ui/export/ExportUtils'], function(Log, Expor
122
122
  }
123
123
 
124
124
  function exportInWorker() {
125
- var spreadsheetWorker;
126
- var workerParams = {};
125
+ let oSpreadsheetWorker;
126
+ const mWorkerParams = {};
127
127
 
128
128
  var fnCancel = function() {
129
- spreadsheetWorker.postMessage({ cancel: true });
129
+ oSpreadsheetWorker.postMessage({ cancel: true });
130
130
  onFinish();
131
131
  };
132
132
 
133
- function createWorker(url) {
134
- var worker = new Worker(url);
135
- worker.onmessage = postMessage;
136
-
137
- // Skips error handling if fired in Firefox with cross origin
138
- if (navigator.userAgent.indexOf("Firefox") === -1 || isSameOrigin(url)) {
139
- worker.onerror = onError;
140
- }
133
+ function createWorker(sUrl) {
134
+ const {promise: oPromise, resolve: fnResolve, reject: fnReject} = Promise.withResolvers();
135
+ const oWorker = new Worker(sUrl);
136
+ const errorHandler = (oEvent) => {
137
+ oWorker.terminate();
138
+ fnReject(oEvent);
139
+ };
140
+ const messageHandler = (oEvent) => {
141
+ if (oEvent.data.initialized) {
142
+ oWorker.removeEventListener("message", messageHandler);
143
+ oWorker.removeEventListener("error", errorHandler);
144
+
145
+ oWorker.addEventListener("message", postMessage);
146
+ oWorker.addEventListener("error", onError);
147
+ fnResolve(oWorker);
148
+ }
149
+ };
141
150
 
142
- worker.postMessage(mParams);
151
+ oWorker.addEventListener("message", messageHandler);
152
+ oWorker.addEventListener("error", errorHandler);
143
153
 
144
- return worker;
154
+ return oPromise;
145
155
  }
146
156
 
147
- function isSameOrigin(url) {
148
- return url.indexOf(window.location.host) > 0
149
- || /^[^/]+\/[^/].*$|^\/[^/].*$/i.test(url); //check for relative address
150
- }
157
+ function createBlobWorker() {
158
+ Log.warning('Direct worker is not allowed. Load the worker via Blob.');
151
159
 
152
- function blobWorker() {
153
- var sBlobCode, oBlobURL;
160
+ const sBlobCode = `self.origin = "${mWorkerParams.base}"; importScripts("${mWorkerParams.src}");`;
161
+ const oBlobURL = window.URL.createObjectURL(new Blob([sBlobCode]));
154
162
 
155
- Log.warning('Direct worker is not allowed. Load the worker via blob.');
163
+ return createWorker(oBlobURL);
164
+ }
156
165
 
157
- sBlobCode = 'self.origin = "' + workerParams.base + '"; ' + 'importScripts("' + workerParams.src + '")';
158
- oBlobURL = window.URL.createObjectURL(new Blob([sBlobCode]));
166
+ /**
167
+ * Returns a worker instance. First tries to create a direct worker, if this fails, e.g. due to
168
+ * cross-origin or CSP restrictions, a blob worker is created. When no worker can be created, the
169
+ * Promise is rejected.
170
+ *
171
+ * @returns {Promise<Worker>} Worker instance
172
+ */
173
+ async function getWorker() {
174
+ let oWorker;
159
175
 
160
- return createWorker(oBlobURL);
176
+ try {
177
+ oWorker = await createWorker(mWorkerParams.src);
178
+ } catch (oError) {
179
+ oWorker = await createBlobWorker();
180
+ }
181
+
182
+ return oWorker;
161
183
  }
162
184
 
163
185
  function noWorker() {
@@ -165,34 +187,23 @@ sap.ui.define(['sap/base/Log', 'sap/ui/export/ExportUtils'], function(Log, Expor
165
187
  fnCancel = exportInProcess(mParams).cancel;
166
188
  }
167
189
 
168
- function start() {
190
+ async function start() {
169
191
  try {
170
- spreadsheetWorker = createWorker(workerParams.src);
171
- spreadsheetWorker.addEventListener('error', function (e) { // Firefox fires an error event instead of a security exception
172
- spreadsheetWorker = blobWorker();
173
- spreadsheetWorker.addEventListener('error', function (e) {
174
- noWorker();
175
- e.preventDefault();
176
- });
177
- e.preventDefault();
178
- });
179
- } catch (err1) {
180
- try {
181
- spreadsheetWorker = blobWorker();
182
- } catch (err2) {
183
- noWorker();
184
- }
192
+ oSpreadsheetWorker = await getWorker();
193
+ oSpreadsheetWorker.postMessage(mParams);
194
+ } catch (oError) {
195
+ noWorker();
185
196
  }
186
197
  }
187
198
 
188
199
  // worker settings
189
- workerParams.base = ExportUtils.normalizeUrl(sap.ui.require.toUrl('sap/ui/export/js/'));
190
- workerParams.src = workerParams.base + 'SpreadsheetWorker.js';
200
+ mWorkerParams.base = ExportUtils.normalizeUrl(sap.ui.require.toUrl('sap/ui/export/js/'));
201
+ mWorkerParams.src = `${mWorkerParams.base}SpreadsheetWorker.js`;
191
202
 
192
203
  start();
193
204
 
194
205
  // fnCancel may be overwritten asynchronously after return, therefore it should be wrapped into a closure
195
- return {cancel: function() {fnCancel();}};
206
+ return {cancel: () => { fnCancel(); }};
196
207
  }
197
208
 
198
209
  if (mParams.dataSource.type === 'array') {
@@ -0,0 +1,201 @@
1
+ /*!
2
+ * SAPUI5
3
+ * (c) Copyright 2025 SAP SE. All rights reserved.
4
+ */
5
+
6
+ sap.ui.define([
7
+ "sap/ui/base/EventProvider",
8
+ "sap/base/Log"
9
+ ], function(EventProvider, Log) {
10
+ "use strict";
11
+
12
+ // Matches a formula (for usage see #escapeContent):
13
+ // Starts with one of = + - @ but excludes "number only" formulas like -123,45 or =1.234e+5 as they are safe to use
14
+ const rFormula = /^[=\+\-@](?![\d.,]+(?:e[\+-]?\d+)?$)/i;
15
+ const MAX_CELL_LENGTH = 32760;
16
+
17
+ /**
18
+ * CSVBuilder provides functionality to build and export data in CSV format.
19
+ *
20
+ * @extends sap.ui.base.EventProvider
21
+ * @class
22
+ * @constructor
23
+ * @param {Array.<Object>} aColumns Array of column metadata objects.
24
+ * Each object must have:
25
+ * <ul>
26
+ * <li>{string} label: The column header name in the CSV file.</li>
27
+ * <li>{string} property: The property name used to extract data for this column from each data row.</li>
28
+ * </ul>
29
+ * @param {string} [sSeparator=","] Character used to separate columns in the CSV file. Default is comma (",").
30
+ */
31
+ const CSVBuilder = EventProvider.extend("sap.ui.export.js.CSVBuilder", {
32
+
33
+ constructor: function(aColumns, sSeparator = ",") {
34
+
35
+ this.validateSettings(aColumns, sSeparator);
36
+
37
+ this.aCompleteData = [];
38
+ this.aColumns = aColumns;
39
+ this.sSeparator = sSeparator;
40
+
41
+ // Create the regex for escaping content based on the separator
42
+ const escapedSeparator = this.sSeparator.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
43
+ this.rContentNeedsEscaping = new RegExp(`[\\r\\n"\\t${escapedSeparator}]`);
44
+ },
45
+
46
+ validateSettings(aColumns, sSeparator) {
47
+ if (!Array.isArray(aColumns)) {
48
+ throw new Error("Column information must be in form of an Array");
49
+ }
50
+
51
+ if (aColumns.length === 0) {
52
+ throw new Error("Column information must not be an empty array");
53
+ }
54
+
55
+ if (!sSeparator || typeof sSeparator !== "string" || sSeparator.length !== 1) {
56
+ throw new Error("Separator must be a single character string");
57
+ }
58
+ }
59
+ });
60
+
61
+ /**
62
+ * Appends data to the current dataset and converts it into a CSV-formatted string.
63
+ *
64
+ * @param {Array} aData An array of objects containing the data to be added to the dataset, which can later be converted into a CSV file
65
+ */
66
+ CSVBuilder.prototype.append = function(aData) {
67
+ const aColumnKeys = this.aColumns.map((col) => col.property);
68
+
69
+ aData.forEach((obj) => {
70
+ const aCells = [];
71
+
72
+ aColumnKeys.forEach((key) => {
73
+ aCells.push(this.getValue(obj, key));
74
+ });
75
+
76
+ this.aCompleteData.push(aCells.join(this.sSeparator));
77
+ });
78
+ };
79
+
80
+ /**
81
+ * Retrieves and escapes the value of a specified property from a data object.
82
+ *
83
+ * @param {object} oData
84
+ * @param {string} sProperty
85
+ * @returns
86
+ */
87
+ CSVBuilder.prototype.getValue = function(oData, sProperty) {
88
+ let sValue = oData[sProperty];
89
+
90
+ // Explicitly ignore NaN since the String conversion happens afterwards
91
+ // Early return for empty strings will be handled by the escapeContent method
92
+ if (sValue === null || typeof sValue === "undefined") {
93
+ return "";
94
+ }
95
+
96
+ if (typeof sValue !== "string") {
97
+ sValue = String(sValue);
98
+ }
99
+
100
+ return this.escapeContent(sValue);
101
+ };
102
+
103
+ /**
104
+ * Escapes the content of a cell in a CSV file according to the CSV format specification (RFC 4180).
105
+ *
106
+ * This method processes the input value and applies the following rules:
107
+ * <ul>
108
+ * <li>If the value contains special characters (for example, separator, newline, or double quotes), it is enclosed in double quotes.</li>
109
+ * <li>Double quotes within the value are escaped by doubling them (for example, `"` becomes `""`).</li>
110
+ * <li>If the value starts with '=', '+', '-', or '@', a single quote is prepended to prevent CSV injection.</li>
111
+ * <li>Values longer than 32,760 characters will be truncated to ensure compatibility with spreadsheet software.</li>
112
+ * </ul>
113
+ *
114
+ * @param {string} valueToCheck - The cell value to process and escape according to CSV rules
115
+ * @returns {string} The escaped value, ready for inclusion in a CSV file
116
+ */
117
+ CSVBuilder.prototype.escapeContent = function(valueToCheck) {
118
+ let sValue = valueToCheck;
119
+
120
+ if (!sValue) {
121
+ return sValue;
122
+ }
123
+
124
+ sValue = sValue.trim();
125
+
126
+ // Remove Unicode BOM if present
127
+ if (sValue.charCodeAt(0) === 0xFEFF) {
128
+ sValue = sValue.slice(1);
129
+ }
130
+
131
+ // Prepend single quote in case cell content is a formula
132
+ if (rFormula.test(sValue)) {
133
+ sValue = "'" + sValue;
134
+ }
135
+
136
+ // Prevent cell overflow
137
+ if (sValue.length > MAX_CELL_LENGTH) {
138
+ sValue = sValue.slice(0, MAX_CELL_LENGTH);
139
+ Log.warning("Cell content truncated to prevent overflow.");
140
+ }
141
+
142
+ // Convert value to string
143
+ sValue = sValue.toString();
144
+
145
+ // Check if the value contains the separator or other special characters
146
+ const bContainsSeparatorChar = sValue.indexOf(this.sSeparator) > -1;
147
+
148
+ // Only wrap content with double quotes if it contains the separator char,
149
+ // a new line (CR / LF), a double quote, or other special characters
150
+ if (bContainsSeparatorChar || this.rContentNeedsEscaping.test(sValue)) {
151
+ // Escape double quotes by preceding them with another one
152
+ sValue = sValue.replace(/"/g, '""');
153
+
154
+ // Wrap final content with double quotes
155
+ sValue = `"${sValue}"`;
156
+ }
157
+
158
+ return sValue;
159
+ };
160
+
161
+ /**
162
+ * Combines the column labels and corresponding row data into a CSV-formatted string using a separator defined in the settings.
163
+ *
164
+ * This method performs the following actions:
165
+ * <ul>
166
+ * <li>Converts the CSV-formatted string into a <code>Blob</code> object.</li>
167
+ * <li>Adds a UTF-8 Byte Order Mark (BOM) at the beginning of the <code>Blob</code> for compatibility with spreadsheet software.</li>
168
+ * </ul>
169
+ *
170
+ * @returns {Blob} A Blob object containing the CSV data with a UTF-8 BOM
171
+ */
172
+ CSVBuilder.prototype.build = function() {
173
+
174
+ // Generate the CSV header
175
+ const aColumnNamesCSV = this.aColumns.map((item) => item.label);
176
+ const sColumnNamesCSV = aColumnNamesCSV.join(this.sSeparator) + "\r\n";
177
+
178
+ // Combine all line items into a CSV-formatted string
179
+ const sCsvContent = this.aCompleteData.join("\r\n");
180
+
181
+ // Combine the header and data
182
+ const sCompleteCSV = sColumnNamesCSV + sCsvContent;
183
+
184
+ // Add UTF-8 BOM
185
+ const blobData = new TextEncoder().encode(sCompleteCSV);
186
+ const blobBOM = Uint8Array.of(0xef, 0xbb, 0xbf);
187
+ const blobContent = new Uint8Array(blobBOM.length + blobData.length);
188
+ blobContent.set(blobBOM);
189
+ blobContent.set(blobData, blobBOM.length);
190
+
191
+ try {
192
+ return new Blob([blobContent], {
193
+ type: 'text/csv;charset=utf-8'
194
+ });
195
+ } catch (error) {
196
+ throw new Error("Failed to create CSV Blob");
197
+ }
198
+ };
199
+
200
+ return CSVBuilder;
201
+ });
@@ -12,6 +12,10 @@ importScripts(origin + 'XLSXBuilder.js');
12
12
  importScripts(origin + '../provider/DataProviderBase.js');
13
13
  importScripts(origin + 'libs/JSZip3.js');
14
14
 
15
+ postMessage({
16
+ initialized: true
17
+ });
18
+
15
19
  self.onmessage = function(oMessage) {
16
20
  'use strict';
17
21