@sapui5/sap.ushell_abap 1.150.1 → 1.151.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.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/src/main/js/sap/ushell_abap/.library +1 -1
  3. package/src/main/js/sap/ushell_abap/adapters/abap/AppStateAdapter.js +5 -5
  4. package/src/main/js/sap/ushell_abap/adapters/abap/ClientSideTargetResolutionAdapter.js +7 -7
  5. package/src/main/js/sap/ushell_abap/adapters/abap/CommonDataModelAdapter.js +1 -1
  6. package/src/main/js/sap/ushell_abap/adapters/abap/ConfigurationDefaultsAdapter.js +1 -1
  7. package/src/main/js/sap/ushell_abap/adapters/abap/ContainerAdapter.js +1 -1
  8. package/src/main/js/sap/ushell_abap/adapters/abap/FlpLaunchPageAdapter.js +5 -5
  9. package/src/main/js/sap/ushell_abap/adapters/abap/LaunchPageAdapter.js +1 -1
  10. package/src/main/js/sap/ushell_abap/adapters/abap/NavTargetResolutionAdapter.js +1 -1
  11. package/src/main/js/sap/ushell_abap/adapters/abap/NavTargetResolutionInternalAdapter.js +1 -1
  12. package/src/main/js/sap/ushell_abap/adapters/abap/PageBuildingAdapter.js +1 -1
  13. package/src/main/js/sap/ushell_abap/adapters/abap/PagePersistenceAdapter.js +1 -1
  14. package/src/main/js/sap/ushell_abap/adapters/abap/PersonalizationAdapter.js +2 -2
  15. package/src/main/js/sap/ushell_abap/adapters/abap/PersonalizationV2Adapter.js +1 -1
  16. package/src/main/js/sap/ushell_abap/adapters/abap/SearchAdapter.js +1 -1
  17. package/src/main/js/sap/ushell_abap/adapters/abap/SupportTicketAdapter.js +4 -4
  18. package/src/main/js/sap/ushell_abap/adapters/abap/Ui5ComponentLoaderAdapter.js +1 -1
  19. package/src/main/js/sap/ushell_abap/adapters/hana/ContainerAdapter.js +1 -1
  20. package/src/main/js/sap/ushell_abap/base/Ui2Error.js +46 -0
  21. package/src/main/js/sap/ushell_abap/base/Ui2Utils.js +710 -0
  22. package/src/main/js/sap/ushell_abap/bootstrap/LoadLaunchpad.js +6 -0
  23. package/src/main/js/sap/ushell_abap/bootstrap/PageSetsRequestHandler.js +1 -1
  24. package/src/main/js/sap/ushell_abap/bootstrap/StartupRequestHandler.js +6 -6
  25. package/src/main/js/sap/ushell_abap/bootstrap/abapConstants.js +1 -0
  26. package/src/main/js/sap/ushell_abap/library.js +1 -1
  27. package/src/main/js/sap/ushell_abap/pbServices/ui2/AllCatalogs.js +1 -1
  28. package/src/main/js/sap/ushell_abap/pbServices/ui2/Bag.js +2 -2
  29. package/src/main/js/sap/ushell_abap/pbServices/ui2/Catalog.js +2 -2
  30. package/src/main/js/sap/ushell_abap/pbServices/ui2/Chip.js +2 -2
  31. package/src/main/js/sap/ushell_abap/pbServices/ui2/ChipDefinition.js +1 -1
  32. package/src/main/js/sap/ushell_abap/pbServices/ui2/ChipInstance.js +4 -4
  33. package/src/main/js/sap/ushell_abap/pbServices/ui2/Error.js +5 -37
  34. package/src/main/js/sap/ushell_abap/pbServices/ui2/Factory.js +3 -3
  35. package/src/main/js/sap/ushell_abap/pbServices/ui2/ODataService.js +2 -2
  36. package/src/main/js/sap/ushell_abap/pbServices/ui2/ODataWrapper.js +3 -3
  37. package/src/main/js/sap/ushell_abap/pbServices/ui2/Page.js +2 -2
  38. package/src/main/js/sap/ushell_abap/pbServices/ui2/PageBuildingService.js +2 -2
  39. package/src/main/js/sap/ushell_abap/pbServices/ui2/PageSet.js +2 -2
  40. package/src/main/js/sap/ushell_abap/pbServices/ui2/Utils.js +5 -703
  41. package/src/main/js/sap/ushell_abap/pbServices/ui2/contracts/actions.js +1 -1
  42. package/src/main/js/sap/ushell_abap/pbServices/ui2/contracts/bag.js +3 -3
  43. package/src/main/js/sap/ushell_abap/pbServices/ui2/contracts/configuration.js +3 -3
  44. package/src/main/js/sap/ushell_abap/pbServices/ui2/contracts/configurationUi.js +1 -1
  45. package/src/main/js/sap/ushell_abap/pbServices/ui2/contracts/search.js +3 -3
  46. package/src/main/js/sap/ushell_abap/pbServices/ui2/contracts/visible.js +1 -1
  47. package/src/main/js/sap/ushell_abap/services/NavigationDataProvider.js +126 -0
  48. package/ui5.yaml +6 -3
@@ -0,0 +1,710 @@
1
+ // Copyright (c) 2009-2026 SAP SE, All Rights Reserved
2
+
3
+ /* eslint-disable no-console */
4
+
5
+ /**
6
+ * @fileOverview This file contains miscellaneous utility functions.
7
+ */
8
+
9
+ sap.ui.define([
10
+ // sap.ushell_abap.base is a bootstrap dependency!
11
+ // Be careful when adding new dependencies
12
+ "sap/base/Log",
13
+ "sap/ui/Device",
14
+ "sap/ui/thirdparty/URI",
15
+ "sap/ushell/bootstrap/Config",
16
+ "sap/ushell_abap/base/Ui2Error"
17
+ ], (
18
+ Log,
19
+ Device,
20
+ URI,
21
+ BootstrapConfig,
22
+ SrvcError
23
+ ) => {
24
+ "use strict";
25
+
26
+ function Utils () { }
27
+
28
+ // cache for GET requests
29
+ let oCache;
30
+
31
+ // "private static" methods **************************************************
32
+
33
+ /**
34
+ * Makes the given relative URL absolute. URLs containing host and/or protocol
35
+ * and URLs with an absolute path remain unchanged. The URL is in no way
36
+ * normalized; the function simply cuts off the file name from the base and
37
+ * appends the relative URL.
38
+ *
39
+ * @param {string} sUrl
40
+ * the (possibly server-relative) URL
41
+ * @param {string} [sBase=location.href]
42
+ * the base URL; it <b>must</b> at least be server-absolute
43
+ * @returns {string}
44
+ * the absolute URL
45
+ * @since 1.2.0
46
+ */
47
+ Utils.absoluteUrl = function (sUrl, sBase) {
48
+ /* jslint regexp: true */
49
+
50
+ // default base is the page location
51
+ sBase = sBase || window.location.href;
52
+ // base must be absolute
53
+ if (sBase.indexOf("://") < 0 && sBase.charAt(0) !== "/") {
54
+ throw new SrvcError(`Illegal base URL: ${sBase}`);
55
+ }
56
+ // do not change empty or absolute URL
57
+ if (!sUrl || sUrl.indexOf("://") >= 0 || sUrl.charAt(0) === "/") {
58
+ return Utils.addCacheBusterTokenUsingUshellConfig(sUrl);
59
+ }
60
+ if (sBase.search(/^([^:]*:)?\/\/[^/]+$/) < 0) {
61
+ // not a pure server URL -> cut off the file name
62
+ sBase = sBase.replace(/\/[^/]*$/, "");
63
+ }
64
+ // append the relative path
65
+ return Utils.addCacheBusterTokenUsingUshellConfig(`${sBase}/${sUrl}`);
66
+ };
67
+
68
+ /**
69
+ * Calls the given success handler (a)synchronously. Errors thrown in the success handler are
70
+ * caught and the error message is reported to the error handler; if an error stack is
71
+ * available, it is logged.
72
+ *
73
+ * @param {function} fnSuccess
74
+ * no-args success handler
75
+ * @param {function(string)} [fnFailure]
76
+ * error handler, taking an error message; MUST NOT throw any error itself!
77
+ * @param {boolean} [bAsync=false]
78
+ * whether the call shall be asynchronously
79
+ * @since 1.2.0
80
+ */
81
+ Utils.callHandler = function (fnSuccess, fnFailure, bAsync) {
82
+ // see also redundant declaration in sap.ushell.utils.call which has to be in sync
83
+ if (bAsync) {
84
+ setTimeout(() => {
85
+ Utils.callHandler(fnSuccess, fnFailure, false);
86
+ }, 0);
87
+ return;
88
+ }
89
+
90
+ try {
91
+ fnSuccess();
92
+ } catch (oError) {
93
+ const sMessage = oError.message || oError.toString();
94
+ Log.error(`Call to success handler failed: ${sMessage}`, oError);
95
+ if (fnFailure) {
96
+ fnFailure(sMessage);
97
+ }
98
+ }
99
+ };
100
+
101
+ /**
102
+ * GETs the given URL (as XML if indicated) and hands it to the given
103
+ * success handler. As this is a root cause for asynchronous behaviour,
104
+ * special precautions are taken: errors thrown in the success handler are
105
+ * caught and reported to the error handler!
106
+ *
107
+ * @param {string} sUrl
108
+ * URL for GET request
109
+ * @param {boolean} bXml
110
+ * whether the handler expects XML instead of plain text
111
+ * @param {function(*)} fnSuccess
112
+ * success handler, taking a DOM document or text string
113
+ * @param {function(string, string)} fnFailure
114
+ * error handler, taking an error message and (if http status is not OK) the GET response as
115
+ * text; MUST NOT throw any error itself!
116
+ * @param {object} [oXHR]
117
+ * the XMLHttpRequest object which may be predefined (e.g. by setting request headers). If
118
+ * <code>undefined</code>, a new XMLHttpRequest object is created.
119
+ * @param {boolean} [bCache]
120
+ * whether the response is cached for further calls (since 1.8.1). XML responses cannot be
121
+ * cached. An Error is thrown if both <code>bXml</code> and
122
+ * <code>bCache</code> are set to <code>true</code>.
123
+ * @since 1.2.0
124
+ */
125
+ Utils.get = function (sUrl, bXml, fnSuccess, fnFailure, oXHR, bCache) {
126
+ if (typeof fnSuccess !== "function") {
127
+ throw new SrvcError("Missing success handler");
128
+ }
129
+ if (typeof fnFailure !== "function") {
130
+ throw new SrvcError("Missing error handler");
131
+ }
132
+ if (bXml && bCache) {
133
+ throw new SrvcError("Caching of XML responses not supported");
134
+ }
135
+ if (typeof Utils.addCacheBusterTokenUsingUshellConfig === "function") {
136
+ sUrl = Utils.addCacheBusterTokenUsingUshellConfig(sUrl);
137
+ }
138
+ oXHR = oXHR || new XMLHttpRequest();
139
+
140
+ /**
141
+ * @private
142
+ */
143
+ oXHR.onreadystatechange = function () {
144
+ let oResult;
145
+ let oXml;
146
+ // Note: "this" refers to oXHR according to W3C
147
+ if (this.readyState !== /* DONE */4) {
148
+ return; // not yet DONE
149
+ }
150
+ if (this.status !== /* OK */200) {
151
+ // HTTP status not OK
152
+ Log.error(`Error ${this.status} in response for URL ${sUrl}`,
153
+ null);
154
+ fnFailure(`${sUrl}: ${this.status} ${this.statusText}`, this.responseText);
155
+ return;
156
+ }
157
+
158
+ Log.debug(`Received response for URL ${sUrl}`, null);
159
+ if (bXml) {
160
+ oXml = this.responseXML;
161
+ if (oXml === null || !oXml.documentElement) {
162
+ // in FF it is null, in IE it is a document with only an error message
163
+ fnFailure(`${sUrl}: no valid XML`);
164
+ return;
165
+ }
166
+ oResult = oXml;
167
+ } else {
168
+ oResult = this.responseText;
169
+ if (bCache) {
170
+ oCache.put(sUrl, oResult);
171
+ }
172
+ }
173
+ Utils.callHandler(fnSuccess.bind(null, oResult), fnFailure);
174
+ };
175
+
176
+ if (!bXml && oCache.containsKey(sUrl)) {
177
+ Log.debug(`Return cached response for URL ${sUrl}`, null);
178
+ Utils.callHandler(fnSuccess.bind(null, oCache.get(sUrl)), fnFailure);
179
+ } else {
180
+ try {
181
+ // Given that the XHR request could be provided as a parameter, we must
182
+ // check that this was not already opened before calling open. One
183
+ // reason this could happen is header settings. Only after opening the
184
+ // request headers can be set. Calling open again will cause the
185
+ // previous request to be aborted (i.e., headers loss).
186
+ if (oXHR.readyState < XMLHttpRequest.OPENED) { // keep working on the opened request
187
+ oXHR.open("GET", sUrl, /* asynchronously */true);
188
+ } else {
189
+ Log.debug(`XHR Request was already opened for ${sUrl}`, null);
190
+ }
191
+ oXHR.send();
192
+ Log.debug(`Sent request to URL ${sUrl}`, null);
193
+ } catch (oError) {
194
+ Log.error(`Error '${oError.message || oError}' in request to URL ${sUrl}`,
195
+ null);
196
+ throw oError;
197
+ }
198
+ }
199
+ };
200
+
201
+ /**
202
+ * Gets an URL and adds the given cache buster token to it if no other token is already
203
+ * contained. In case the URL is no valid URL the token is not added.
204
+ *
205
+ * @param {string} sUrl
206
+ * e.g. "/sap/bc/ui5_ui5/application/path"
207
+ * URL to be changed
208
+ * @param {RegExp} oPattern
209
+ * e.g /^\/sap\/bc\/ui5_ui5\//
210
+ * RegExp to determine if sUrl matches and needs to be extended by the cache buster token
211
+ * sToken
212
+ * @param {string} sReplacement
213
+ * e.g. "/sap/bc/ui5_ui5/[CacheBusterToken]/"
214
+ * The part of sUrl matched by oPattern will be exchanged by this. Before that is done sToken
215
+ * is inserted in sReplacement at the position indicated by [CacheBusterToken]
216
+ * The replacement may refer to capture groups of oPattern
217
+ * @param {string} sToken
218
+ * e.g. "~201412132350000~"
219
+ * token to be inserted in sUrl. It will be inserted as indicated in the final constructed URL!
220
+ * @returns {string}
221
+ * - if sUrl did not matched oPattern: unchanged sUrl
222
+ * - if sUrl matched oPattern: sUrl enhanced with sToken,
223
+ * e.g. "/sap/bc/ui5_ui5/~201412132350000~/application/path"
224
+ *
225
+ * @private
226
+ */
227
+ Utils.addCacheBusterToken = function (sUrl, oPattern, sReplacement, sToken) {
228
+ if (oPattern.test(sUrl)) { // url matches the pattern
229
+ sUrl = sUrl.replace(oPattern, sReplacement);
230
+ // replace the token placeholder globally in the final url (!)
231
+ // (also allow the token to be added elsewhere)
232
+ sUrl = sUrl.replace(/\[CacheBusterToken\]/g, sToken);
233
+ }
234
+ return sUrl;
235
+ };
236
+
237
+ /**
238
+ * Removes a cache buster token (if available) of an Url and normalizes the url afterwards
239
+ * @param {string} sUrl
240
+ * the URL to be normalized
241
+ * @returns {string}
242
+ * normalized url (without a cache buster token)
243
+ * @since 1.28.1
244
+ *
245
+ * @private
246
+ */
247
+ Utils.removeCBAndNormalizeUrl = function (sUrl) {
248
+ let sUrlPrefix;
249
+ let sCacheBusterSegment;
250
+ let sUrlPostfix;
251
+
252
+ function isUriWithRelativeOrEmptyPath (sUrl0) {
253
+ const oUri = new URI(sUrl0);
254
+ const sPath = oUri.path();
255
+
256
+ if (oUri.is("absolute")) {
257
+ return false;
258
+ }
259
+
260
+ if (sPath && sPath.charAt(0) === "/") {
261
+ return false;
262
+ }
263
+
264
+ return true;
265
+ }
266
+
267
+ if (typeof sUrl !== "string" || sUrl === "" || isUriWithRelativeOrEmptyPath(sUrl)) {
268
+ return sUrl;
269
+ }
270
+
271
+ // split up the URL into 3 parts: prefix, cache-buster segment, postfix
272
+ // leading slashes are always part of the segment, the postfix might have a trailing slash
273
+ const aMatches = sUrl.match(/(.*)(\/~[\w-]+~[A-Z0-9]?)(.*)/);
274
+ if (aMatches) {
275
+ sUrlPrefix = aMatches[1];
276
+ sCacheBusterSegment = aMatches[2];
277
+ sUrlPostfix = aMatches[3];
278
+ }
279
+
280
+ function normalizePath (sUrl0) {
281
+ return new URI(sUrl0).normalizePathname().toString();
282
+ }
283
+
284
+ function isRelativePathWithDotSegmentsThatGoOutside (sPath) {
285
+ const aSegments = new URI(sPath).segment();
286
+ let i;
287
+ let iPos = 0;
288
+
289
+ for (i = 0; i < aSegments.length && iPos >= 0; i += 1) {
290
+ if (aSegments[i] === "..") {
291
+ iPos = iPos - 1;
292
+ } else {
293
+ iPos = iPos + 1;
294
+ }
295
+ }
296
+
297
+ return iPos < 0;
298
+ }
299
+
300
+ // check if URL contains a cache-buster token
301
+ if (sCacheBusterSegment) {
302
+ // check if removal of cache-buster token is required
303
+ if (sUrlPostfix && isRelativePathWithDotSegmentsThatGoOutside(sUrlPostfix)) {
304
+ // remove the cache-buster token
305
+ sUrl = sUrlPrefix + sUrlPostfix;
306
+ }
307
+ }
308
+
309
+ // always normalize the URL path
310
+ return normalizePath(sUrl);
311
+ };
312
+
313
+ /**
314
+ * Gets an URL and adds the given cache buster token to it if no other token is already
315
+ * contained. The rules to be applied are coming from the ushell configuration:
316
+ * sap-ushell-config.cacheBusting.patterns
317
+ * The rules are applied by there order property (lowest first) and the modified URL is returned
318
+ * as soon as the first rule matched.
319
+ * <p>
320
+ * If the query parameter <code>sap-ushell-nocb</code> is set to <code>true</code> or <code>X</code>,
321
+ * no cache buster tokens are added and existing cache buster tokens are removed from the specified URL.
322
+ *
323
+ * @param {string} sUrl
324
+ * e.g. "/sap/bc/ui5_ui5/application/path"
325
+ * URL to be changed
326
+ * @returns {string}
327
+ * - if sUrl already contained a cache buster token (e.g. ~00000~): unchanged sUrl
328
+ * - if sUrl did not match any pattern: unchanged sUrl
329
+ * - if sUrl matched pattern: sUrl enhanced with sToken,
330
+ * e.g. "/sap/bc/ui5_ui5/~201412132350000~/application/path"
331
+ * - if the modified sUrl (normalized and cache buster token was removed)
332
+ * is found as an attribute of the config
333
+ * (window["sap-ushell-config"].cacheBusting.urls),
334
+ * the cache buster token which is defined as the value of this attribute
335
+ * is going to be returned.
336
+ *
337
+ * @private
338
+ */
339
+ Utils.addCacheBusterTokenUsingUshellConfig = function (sUrl) {
340
+ // TODO move to sap.ushell.utils
341
+ const oCacheBusting = BootstrapConfig.get()?.cacheBusting;
342
+ const oPatterns = oCacheBusting && oCacheBusting.patterns;
343
+ let sCacheBusterUrl = sUrl;
344
+ let aParameterMap = [];
345
+ const aRules = [];
346
+
347
+ aParameterMap = Utils.getParameterMap();
348
+ const sSapUshellNoCb = aParameterMap["sap-ushell-nocb"] && aParameterMap["sap-ushell-nocb"][0];
349
+
350
+ // When URL disables Cache Busting return URL without cache busting token
351
+ // It can happen that we get a URL which already has a cache-buster token included (from the ABAP server), so we also remove
352
+ // an existing token here (this implementation is simpler than passing the URL parameter to the resolveLink service and evaluate it there)
353
+ if ((sSapUshellNoCb === "true" || sSapUshellNoCb === "X") && typeof sUrl === "string") {
354
+ sUrl = sUrl.replace(/\/~[\w-]+~[A-Z0-9]?/, "");
355
+ return sUrl;
356
+ }
357
+
358
+ // don't continue if the string is empty or a token is already present,
359
+ // either as path segment (e.g.: /~0123_-Abc~/) or as query parameter
360
+ // /e.g. ?cb=~xxxxxx~
361
+ // also consider URLs with query parameters and fragments
362
+ // this case happens during navigation, because this method is both called from
363
+ // NavTargetResolutionInternal service as well as from the stubbed jQuery.sap.registerModulePath method
364
+ //
365
+ // syntax for application cache-buster contains now an additional scope qualifier that can be
366
+ // either empty, "R" for resource, "5" for UI5 app, "W" for web app and "C" for custom
367
+ // see ABAP class /UI5/CL_UI5_APP_HTTP_HANDLER for details
368
+ if (!oCacheBusting
369
+ || typeof sUrl !== "string"
370
+ || sUrl === ""
371
+ || /[/=]~[\w-]+~[A-Z0-9]?[/#?&]/.test(sUrl) // matches intermediate segment with cb-token; consider URLs with query string or fragment
372
+ || /[/=]~[\w-]+~[A-Z0-9]?$/.test(sUrl)) { // matches last segment with cb-token (no trailing slash or further parameters)
373
+ return sUrl;
374
+ }
375
+
376
+ if (oCacheBusting && oCacheBusting.urls) {
377
+ // Removing the last slash of the input url
378
+ if (sUrl.charAt(sUrl.length - 1) === "/") {
379
+ sUrl = sUrl.substr(0, sUrl.length - 1);
380
+ }
381
+ // Config contains the modified url (without a slash at the end)
382
+ if (oCacheBusting.urls.hasOwnProperty(sUrl)) {
383
+ return `${sUrl}/${oCacheBusting.urls[sUrl].cacheBusterToken}`;
384
+ }
385
+ // Config contains the modified url (having a slash at the end)
386
+ if (oCacheBusting.urls.hasOwnProperty(`${sUrl}/`)) {
387
+ return `${sUrl}/${oCacheBusting.urls[`${sUrl}/`].cacheBusterToken}`;
388
+ }
389
+ }
390
+
391
+ if (!oPatterns) {
392
+ return sUrl;
393
+ }
394
+
395
+ // put rules in aRules and sort them by oRule.order
396
+ Object.keys(oPatterns).forEach((sPattern) => {
397
+ if (oPatterns.hasOwnProperty(sPattern)) {
398
+ const oRule = oPatterns[sPattern];
399
+ // the property name is the pattern to be used, copy it to the object itself for later
400
+ oRule.pattern = new RegExp(sPattern);
401
+ aRules.push(oRule);
402
+ }
403
+ });
404
+ aRules.sort((oRule1, oRule2) => { return oRule1.order - oRule2.order; });
405
+
406
+ // apply rules
407
+ aRules.every((oRule) => { // use every to be able to break
408
+ if (oRule.pattern.test(sUrl)) {
409
+ if (!oRule.cacheBusterToken) {
410
+ oRule.cacheBusterToken = oCacheBusting.cacheBusterToken;
411
+ }
412
+
413
+ // url matches the pattern, note that this is not redundant
414
+ // one can define patterns without a replacement to match and end the matching process!
415
+ sCacheBusterUrl = Utils.addCacheBusterToken(sUrl, oRule.pattern, oRule.replacement,
416
+ oRule.cacheBusterToken);
417
+ // break as soon as first rule matches (irrespective of alteration)
418
+ return false;
419
+ }
420
+ return true;
421
+ });
422
+
423
+ return sCacheBusterUrl;
424
+ };
425
+
426
+ /**
427
+ * Clear cache for GET requests.
428
+ *
429
+ * @since 1.8.1
430
+ */
431
+ Utils.clearCache = function () {
432
+ oCache = new Utils.Map();
433
+ };
434
+
435
+ /**
436
+ * Gets the device's form factor. Based on <code>sap.ui.Device.system</code> from SAPUI5.
437
+ * @returns {string}
438
+ * the device's form factor ("desktop", "tablet" or "phone")
439
+ * @since 1.19.1
440
+ */
441
+ Utils.getFormFactor = function () {
442
+ // see also redundant declaration in sap.ushell.utils.getFormFactor which has to be in sync
443
+
444
+ if (Device.system.desktop) {
445
+ return Device.system.SYSTEMTYPE.DESKTOP;
446
+ }
447
+ if (Device.system.tablet) {
448
+ return Device.system.SYSTEMTYPE.TABLET;
449
+ }
450
+ if (Device.system.phone) {
451
+ return Device.system.SYSTEMTYPE.PHONE;
452
+ }
453
+ };
454
+
455
+ /**
456
+ * Returns a map of all search parameters present in the given search string
457
+ * or this window's current URL. To be precise, <code>location.search</code>
458
+ * is used as a default and any given search string must use the same syntax
459
+ * (start with a "?" and not include a "#").
460
+ *
461
+ * @param {string} [sSearchString=location.search]
462
+ * search string starting with a "?" (unless empty) and not including a "#"
463
+ * @returns {object}
464
+ * a <code>map&lt;string, string[]></code> from key to array of values
465
+ * @since 1.2.0
466
+ *
467
+ * @see <a href="http://java.sun.com/javaee/5/docs/api/javax/servlet/ServletRequest.html#getParameterMap()">
468
+ * javax.servlet.ServletRequest#getParameterMap()</a>
469
+ */
470
+ Utils.getParameterMap = function (sSearchString) {
471
+ let i;
472
+ let n;
473
+ const mResult = {};
474
+ let sKey;
475
+ let sValue;
476
+ let iIndexOfEquals;
477
+ // Note: location.search starts with "?" if not empty
478
+ const sSearch = arguments.length > 0 ? sSearchString : window.location.search;
479
+
480
+ if (sSearch && sSearch.charAt(0) !== "?") {
481
+ throw new SrvcError(`Illegal search string ${sSearch}`);
482
+ }
483
+ if (!sSearch || sSearch === "?") {
484
+ return {}; // Note: split("") would return [""]
485
+ }
486
+
487
+ // Note: W3C recommends that servers support ";" as well as "&"
488
+ // (http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2.2)
489
+ // http://unixpapa.com/js/querystring.html advocates this on the client-side also!
490
+ const aKeyValuePairs = sSearch.substring(1).replace(/\+/g, " ").split(/[&;]/);
491
+ const aKeyValuePairsLength = aKeyValuePairs.length < 500 ? aKeyValuePairs.length : 500;
492
+
493
+ for (i = 0, n = aKeyValuePairsLength; i < n; i += 1) {
494
+ // decode key/value pair at first "=" character
495
+ sKey = aKeyValuePairs[i];
496
+ sValue = ""; // Note: empty value may be omitted altogether
497
+ iIndexOfEquals = sKey.indexOf("=");
498
+ if (iIndexOfEquals >= 0) {
499
+ sValue = sKey.slice(iIndexOfEquals + 1);
500
+ sValue = decodeURIComponent(sValue);
501
+ sKey = sKey.slice(0, iIndexOfEquals);
502
+ }
503
+ sKey = decodeURIComponent(sKey);
504
+
505
+ // map key to value(s)
506
+ // Note: beware of inherited functions!
507
+ if (!Object.prototype.hasOwnProperty.call(mResult, sKey)) {
508
+ mResult[sKey] = [];
509
+ }
510
+ mResult[sKey].push(sValue);
511
+ }
512
+
513
+ return mResult;
514
+ };
515
+
516
+ /**
517
+ * Returns the value of the given URL's GET parameter with the given name, properly decoded.
518
+ * Returns "" if no such parameter can be found.
519
+ *
520
+ * @param {string} sUrl
521
+ * any URL
522
+ * @param {string} sName
523
+ * the name of the GET parameter we are looking for
524
+ * @returns {string}
525
+ * the parameter value, properly decoded
526
+ *
527
+ * @private
528
+ * @since 1.17.0
529
+ */
530
+ Utils.getParameterValue = function (sUrl, sName) {
531
+ let oParameterMap;
532
+
533
+ if (typeof sName !== "string") {
534
+ // avoid surprises when sName would later be converted into a string
535
+ throw new SrvcError("Missing parameter name");
536
+ }
537
+
538
+ sUrl = sUrl.split("#")[0];
539
+ const iQueryIndex = sUrl.indexOf("?");
540
+ if (iQueryIndex >= 0) {
541
+ oParameterMap = Utils.getParameterMap(sUrl.slice(iQueryIndex));
542
+ if (oParameterMap[sName]) {
543
+ return oParameterMap[sName][0];
544
+ }
545
+ }
546
+ return "";
547
+ };
548
+
549
+ /**
550
+ * Tells whether the given value is an array.
551
+ *
552
+ * @param {object} o
553
+ * any value
554
+ * @returns {boolean}
555
+ * <code>true</code> if and only if the given value is an array
556
+ * @since 1.2.0
557
+ * @deprecated since 1.136.0. Use {@link Array.isArray} instead
558
+ */
559
+ Utils.isArray = Array.isArray;
560
+ /**
561
+ * Tells whether the given value is a string.
562
+ *
563
+ * @param {object} o
564
+ * any value
565
+ * @returns {boolean}
566
+ * <code>true</code> if and only if the given value is a string
567
+ * @since 1.50.1
568
+ */
569
+ Utils.isString = function name (o) {
570
+ return /String/.test(Object.prototype.toString.call(o));
571
+ };
572
+
573
+ /**
574
+ * Parses the given XML string and returns it as a document.
575
+ *
576
+ * @param {string} sXml
577
+ * the XML
578
+ * @returns {DOMDocument}
579
+ * a DOM document, or <code>null</code> in case of missing or empty XML string
580
+ * @throws {Error}
581
+ * in case of invalid XML string
582
+ * @since 1.2.0
583
+ */
584
+ Utils.parseXml = function (sXml) {
585
+ if (!sXml || typeof sXml !== "string") {
586
+ return null;
587
+ }
588
+ const oXml = new DOMParser().parseFromString(sXml, "text/xml");
589
+ if (oXml.getElementsByTagName("parsererror").length) { // Chrome, Firefox
590
+ throw new SrvcError(`Invalid XML: ${sXml}`);
591
+ }
592
+ return oXml;
593
+ };
594
+
595
+ /**
596
+ * Serves as a marker for functions that are to be exposed in QUnit tests. Calls to this function
597
+ * are expected to be placed directly before the named function declaration (even <b>after</b>
598
+ * the JSDoc). The function itself does nothing.
599
+ *
600
+ * @param {object} o
601
+ * the object to which this function will be attached in tests; must not be <code>this</code>
602
+ * (use <code>that</code> instead)
603
+ * @since 1.3.0
604
+ */
605
+
606
+ // "public classes" **********************************************************
607
+
608
+ /**
609
+ * Creates an empty map. It is used for mapping from arbitrary string(!) keys (including "get" or
610
+ * "hasOwnProperty") to values of any type.
611
+ * @class
612
+ * @since 1.5.0
613
+ */
614
+ Utils.Map = function () {
615
+ this.entries = {};
616
+ };
617
+
618
+ /**
619
+ * Associates the specified value with the specified key in this map. If the map previously
620
+ * contained a mapping for the key, the old value is replaced by the specified value. Returns
621
+ * the old value. Note: It might be a good idea to assert that the old value is
622
+ * <code>undefined</code> in case you expect your keys to be unique.
623
+ *
624
+ * @param {string} sKey
625
+ * key with which the specified value is to be associated
626
+ * @param {any} vValue
627
+ * value to be associated with the specified key
628
+ * @returns {any}
629
+ * the old value
630
+ * @since 1.5.0
631
+ */
632
+ Utils.Map.prototype.put = function (sKey, vValue) {
633
+ const vOldValue = this.get(sKey);
634
+ this.entries[sKey] = vValue;
635
+ return vOldValue;
636
+ };
637
+
638
+ /**
639
+ * Returns <tt>true</tt> if this map contains a mapping for the specified key.
640
+ *
641
+ * @param {string} sKey
642
+ * key whose presence in this map is to be tested
643
+ * @returns {boolean}
644
+ * <tt>true</tt> if this map contains a mapping for the specified key
645
+ * @since 1.5.0
646
+ */
647
+ Utils.Map.prototype.containsKey = function (sKey) {
648
+ if (typeof sKey !== "string") {
649
+ throw new SrvcError(`Not a string key: ${sKey}`);
650
+ }
651
+ return Object.prototype.hasOwnProperty.call(this.entries, sKey);
652
+ };
653
+
654
+ /**
655
+ * Returns the value to which the specified key is mapped, or <code>undefined</code> if this map
656
+ * contains no mapping for the key.
657
+ * @param {string} sKey
658
+ * the key whose associated value is to be returned
659
+ * @returns {any}
660
+ * the value to which the specified key is mapped, or <code>undefined</code> if this map
661
+ * contains no mapping for the key
662
+ * @since 1.5.0
663
+ */
664
+ Utils.Map.prototype.get = function (sKey) {
665
+ if (this.containsKey(sKey)) {
666
+ return this.entries[sKey];
667
+ }
668
+ return undefined;
669
+ };
670
+
671
+ /**
672
+ * Returns an array of this map's keys. This array is a snapshot of the map; concurrent
673
+ * modifications of the map while iterating do not influence the sequence.
674
+ * @returns {string[]}
675
+ * this map's keys
676
+ * @since 1.5.0
677
+ */
678
+ Utils.Map.prototype.keys = function () {
679
+ return Object.keys(this.entries);
680
+ };
681
+
682
+ /**
683
+ * Removes a key together with its value from the map.
684
+ * @param {string} sKey
685
+ * the map's key to be removed
686
+ * @since 1.11.0
687
+ */
688
+ Utils.Map.prototype.remove = function (sKey) {
689
+ delete this.entries[sKey];
690
+ };
691
+
692
+ /**
693
+ * Returns this map's string representation.
694
+ *
695
+ * @returns {string}
696
+ * this map's string representation
697
+ * @since 1.5.0
698
+ */
699
+ Utils.Map.prototype.toString = function () {
700
+ const aResult = ["Map("];
701
+ aResult.push(JSON.stringify(this.entries));
702
+ aResult.push(")");
703
+ return aResult.join("");
704
+ };
705
+
706
+ // initialize the cache for GET
707
+ Utils.clearCache();
708
+
709
+ return Utils;
710
+ });