UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

302 lines (274 loc) 10.3 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides access to Java-like properties files /*global chrome, v8 */ sap.ui.define(['sap/base/util/LoaderExtensions'], function(LoaderExtensions) { "use strict"; /** * @class Represents a collection of string properties (key/value pairs). * * Each key and its corresponding value in the collection is a string, keys are case-sensitive. * * Use {@link module:sap/base/util/Properties.create} to create an instance of {@link module:sap/base/util/Properties}. * * The {@link #getProperty} method can be used to retrieve a value from the collection, * {@link #setProperty} to store or change a value for a key and {@link #getKeys} * can be used to retrieve an array of all keys that are currently stored in the collection. * * @public * @since 1.58 * @alias module:sap/base/util/Properties * @hideconstructor */ var Properties = function() { this.mProperties = {}; this.aKeys = null; }; /** * Returns the value for the given key or <code>null</code> if the collection has no value for the key. * * Optionally, a default value can be given which will be returned if the collection does not contain * a value for the key; only non-empty default values are supported. * * @param {string} sKey Key to return the value for * @param {string} [sDefaultValue=null] Optional, a default value that will be returned * if the requested key is not in the collection * @returns {string|null} Value for the given key or the default value or <code>null</code> * if no default value or a falsy default value was given * @public */ Properties.prototype.getProperty = function(sKey, sDefaultValue) { var sValue = this.mProperties[sKey]; if (typeof (sValue) == "string") { return sValue; } else if (sDefaultValue) { return sDefaultValue; } return null; }; /** * Returns an array of all keys in the property collection. * * @function * @returns {string[]} All keys in the property collection * @public */ Properties.prototype.getKeys = function() { if (!this.aKeys) { this.aKeys = Object.keys(this.mProperties); } return this.aKeys; }; /** * Stores or changes the value for the given key in the collection. * * If the given value is not a string, the collection won't be modified. * The key is always cast to a string. * * @param {string} sKey Key of the property * @param {string} sValue String value for the key * @public */ Properties.prototype.setProperty = function(sKey, sValue) { if (typeof (sValue) != "string") { return; } if (typeof (this.mProperties[sKey]) != "string" && this.aKeys ) { this.aKeys.push(String(sKey)); } this.mProperties[sKey] = sValue; }; /** * Creates and returns a clone of the property collection. * * @returns {module:sap/base/util/Properties} A clone of the property collection * @public */ Properties.prototype.clone = function() { var oClone = new Properties(); oClone.mProperties = Object.assign({}, this.mProperties); return oClone; }; // helper to create a memory-optimized version of the given string, depending on the number of concat operations (V8 only) var flatstr = (typeof chrome === "object" || typeof v8 === "object") ? function (s, iConcatOps) { if ( iConcatOps > 2 && 40 * iConcatOps > s.length ) { Number(s); // cast to number on V8 has the side effect of creating a flat version of concat strings } return s; } : function(s) { return s; }; /** * RegExp used to split file into lines, also removes leading whitespace. * Note: group must be non-capturing, otherwise the line feeds will be part of the split result. */ var rLines = /(?:\r\n|\r|\n|^)[ \t\f]*/; /** * Regular expressions to detect escape sequences (unicode or special) and continuation line markers * in a single line of a properties file. The first expression also detects key/value separators and is used * as long as no key has been found. The second one is used for the remainder of the line. * * [---unicode escape--] [esc] [cnt] [---key/value separator---] */ var rEscapesOrSeparator = /(\\u[0-9a-fA-F]{0,4})|(\\.)|(\\$)|([ \t\f]*[ \t\f:=][ \t\f]*)/g; var rEscapes = /(\\u[0-9a-fA-F]{0,4})|(\\.)|(\\$)/g; /** * Special escape characters as supported by properties format. * @see JDK API doc for java.util.Properties */ var mEscapes = { '\\f' : '\f', '\\n' : '\n', '\\r' : '\r', '\\t' : '\t' }; /* * Parses the given text sText and sets the properties * in the properties object oProp accordingly. * @param {string} sText the text to parse * @param oProp the properties object to fill * @private */ function parse(sText, oProp) { var aLines = sText.split(rLines), // split file into lines sLine,rMatcher,sKey,sValue,i,m,iLastIndex,iConcatOps; function append(s) { if ( sValue ) { sValue = sValue + s; iConcatOps++; } else { sValue = s; iConcatOps = 0; } } oProp.mProperties = {}; for (i = 0; i < aLines.length; i++) { sLine = aLines[i]; // ignore empty lines if (sLine === "" || sLine.charAt(0) === "#" || sLine.charAt(0) === "!" ) { continue; } // start with the full regexp incl. key/value separator rMatcher = rEscapesOrSeparator; rMatcher.lastIndex = iLastIndex = 0; sKey = null; sValue = ""; while ( (m = rMatcher.exec(sLine)) !== null ) { // handle any raw, unmatched input if ( iLastIndex < m.index ) { append(sLine.slice(iLastIndex, m.index)); } iLastIndex = rMatcher.lastIndex; if ( m[1] ) { // unicode escape if ( m[1].length !== 6 ) { throw new Error("Incomplete Unicode Escape '" + m[1] + "'"); } append(String.fromCharCode(parseInt(m[1].slice(2), 16))); } else if ( m[2] ) { // special or simple escape append(mEscapes[m[2]] || m[2].slice(1)); } else if ( m[3] ) { // continuation line marker sLine = aLines[++i]; rMatcher.lastIndex = iLastIndex = 0; } else if ( m[4] ) { // only occurs in full regexp // key/value separator detected // -> remember key and switch to simplified regexp sKey = sValue; sValue = ""; rMatcher = rEscapes; rMatcher.lastIndex = iLastIndex; } } if ( iLastIndex < sLine.length ) { append(sLine.slice(iLastIndex)); } if ( sKey == null ) { sKey = sValue; sValue = ""; } oProp.mProperties[sKey] = flatstr(sValue, sValue ? iConcatOps : 0); // Note: empty sValue implies iConcatOps == 0 } } /** * Creates and returns a new instance of {@link module:sap/base/util/Properties}. * * If option 'url' is passed, immediately a load request for the given target is triggered. * A property file that is loaded can contain comments with a leading ! or #. * The loaded property list does not contain any comments. * * @example <caption>Loading a property file</caption> * sap.ui.require(["sap/base/util/Properties"], function (Properties) { * var p = Properties.create({url : "../myProperty.properties"}); * }); * * @example <caption>getting and setting properties</caption> * sap.ui.require(["sap/base/util/Properties"], function (Properties) { * var oProperties = Properties.create(); * oProperties.setProperty("KEY_1","Test Key"); * var sValue1 = oProperties.getProperty("KEY_1"); * var sValue2 = oProperties.getProperty("KEY_2","Default"); * }); * * @example <caption>Loading a property file asynchronously (returns a Promise)</caption> * sap.ui.require(["sap/base/util/Properties"], function (Properties) { * Properties.create({url : "../myProperty.properties", async: true}).then(function(oProperties){ * ... * }); * }); * * @param {object} [mParams] Parameters used to initialize the property list * @param {string} [mParams.url] The URL to the .properties file which should be loaded * @param {boolean} [mParams.async=false] Whether the .properties file should be loaded asynchronously or not * @param {Object<string,any>} [mParams.headers] A map of additional header key/value pairs to send along with * the request (see <code>headers</code> option of <code>jQuery.ajax</code>) * @param {boolean} [mParams.returnNullIfMissing=false] Whether <code>null</code> should be returned * for a missing properties file; by default an empty collection is returned * @return {module:sap/base/util/Properties|null|Promise<module:sap/base/util/Properties|null>} A new * property collection (synchronous case) or <code>null</code> if the file could not be loaded and * <code>returnNullIfMissing</code> was set; in case of asynchronous loading, always a Promise is * returned, which resolves with the property collection or with <code>null</code> if the file could not * be loaded and <code>returnNullIfMissing</code> was set to true * @throws {Error} When the file has syntax issues (e.g. incomplete unicode escapes); * in async mode, the error is not thrown but the returned Promise will be rejected * @SecSink {0|PATH} Parameter is used for future HTTP requests * @public */ Properties.create = function (mParams) { mParams = Object.assign({url: undefined, headers: {}}, mParams); var bAsync = !!mParams.async, oProp = new Properties(), vResource; function _parse(sText){ if ( typeof sText === "string" ) { parse(sText, oProp); return oProp; } return mParams.returnNullIfMissing ? null : oProp; } if ( typeof mParams.url === "string" ) { // @evo-todo: dependency on loadResource implementation in compat layer vResource = LoaderExtensions.loadResource({ url: mParams.url, dataType: 'text', headers: mParams.headers, failOnError: false, async: bAsync }); } if (bAsync) { if ( !vResource ) { return Promise.resolve( _parse(null) ); } return vResource.then(function(oVal) { return _parse(oVal); }, function(oVal) { throw (oVal instanceof Error ? oVal : new Error("Problem during loading of property file '" + mParams.url + "': " + oVal)); }); } return _parse( vResource ); }; return Properties; });