@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
302 lines (274 loc) • 10.3 kB
JavaScript
/*!
* 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;
});