@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
503 lines (468 loc) • 17.6 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides basic internal functions for sap.ui.model.odata.AnnotationHelper
sap.ui.define([
"sap/base/Log",
"sap/base/util/extend",
"sap/ui/base/BindingParser",
"sap/ui/performance/Measurement"
], function (Log, extend, BindingParser, Measurement) {
'use strict';
var sAnnotationHelper = "sap.ui.model.odata.AnnotationHelper",
rBadChars = /[\\\{\}:]/, // @see sap.ui.base.BindingParser: rObject, rBindingChars
Basics,
// path to entity set ("/dataServices/schema/<i>/entityContainer/<j>/entitySet/<k>")
rEntitySetPath
= /^(\/dataServices\/schema\/\d+\/entityContainer\/\d+\/entitySet\/\d+)(?:\/|$)/,
aPerformanceCategories = [sAnnotationHelper],
sPerformanceFollowPath = sAnnotationHelper + "/followPath",
// path to complex or entity type ("/dataServices/schema/<i>/complexType/<j>" or
// "/dataServices/schema/<i>/entityType/<j>")
rTypePath = /^(\/dataServices\/schema\/\d+\/(?:complex|entity)Type\/\d+)(?:\/|$)/,
mUi5TypeForEdmType = {
"Edm.Boolean" : "sap.ui.model.odata.type.Boolean",
"Edm.Byte" : "sap.ui.model.odata.type.Byte",
"Edm.Date" : "sap.ui.model.odata.type.Date",
"Edm.DateTime" : "sap.ui.model.odata.type.DateTime",
"Edm.DateTimeOffset" : "sap.ui.model.odata.type.DateTimeOffset",
"Edm.Decimal" : "sap.ui.model.odata.type.Decimal",
"Edm.Double" : "sap.ui.model.odata.type.Double",
"Edm.Float" : "sap.ui.model.odata.type.Single",
"Edm.Guid" : "sap.ui.model.odata.type.Guid",
"Edm.Int16" : "sap.ui.model.odata.type.Int16",
"Edm.Int32" : "sap.ui.model.odata.type.Int32",
"Edm.Int64" : "sap.ui.model.odata.type.Int64",
"Edm.SByte" : "sap.ui.model.odata.type.SByte",
"Edm.Single" : "sap.ui.model.odata.type.Single",
"Edm.String" : "sap.ui.model.odata.type.String",
"Edm.Stream" : "sap.ui.model.odata.type.Stream",
"Edm.Time" : "sap.ui.model.odata.type.Time",
"Edm.TimeOfDay" : "sap.ui.model.odata.type.TimeOfDay"
};
Basics = {
/**
* Descends the path/value pair to the given property or array index. Logs an error and
* throws an error if the result is not of the expected type.
*
* @param {object} oPathValue
* a path/value pair which may contain additional arbitrary properties
* @param {string} oPathValue.path
* the meta model path to start at
* @param {object|any[]} oPathValue.value
* the value at this path
* @param {string|number} vProperty
* the property name or array index
* @param {boolean|string} [vExpectedType]
* the expected type (tested w/ typeof) or the special value "array" for an array;
* if <code>undefined</code> the result is not checked; <code>true</code> means that no
* check happens, but the result carries the <code>asExpression : true</code> flag.
* @returns {object}
* the path/value pair after descending with all original additional properties
* @throws {SyntaxError}
* if the result is not of the expected type
*/
descend : function (oPathValue, vProperty, vExpectedType) {
var oTarget = extend({}, oPathValue);
Basics.expectType(oPathValue, typeof vProperty === "number" ? "array" : "object");
oTarget.path = oPathValue.path + "/" + vProperty;
oTarget.value = oPathValue.value[vProperty];
if (vExpectedType === true) {
oTarget.asExpression = true;
} else if (vExpectedType) {
Basics.expectType(oTarget, vExpectedType);
}
return oTarget;
},
/**
* Logs the error message for the given path and throws a SyntaxError.
* @param {object} oPathValue
* a path/value pair
* @param {string} sMessage
* the message to log
* @param {string} [sComponent="sap.ui.model.odata.AnnotationHelper"]
* Name of the component that produced the log entry
*/
error : function (oPathValue, sMessage, sComponent) {
sMessage = oPathValue.path + ": " + sMessage;
Log.error(sMessage, Basics.toErrorString(oPathValue.value),
sComponent || sAnnotationHelper);
throw new SyntaxError(sMessage);
},
/**
* Logs an error and throws an error if the value is not of the expected type.
*
* @param {object} oPathValue
* a path/value pair
* @param {string} oPathValue.path
* the meta model path to start at
* @param {any} oPathValue.value
* the value at this path
* @param {string} sExpectedType
* the expected type (tested w/ typeof) or the special value "array" for an array
* @throws {SyntaxError}
* if the result is not of the expected type
*/
expectType : function (oPathValue, sExpectedType) {
var bError,
vValue = oPathValue.value;
if (sExpectedType === "array") {
bError = !Array.isArray(vValue);
} else {
// eslint-disable-next-line valid-typeof
bError = typeof vValue !== sExpectedType
|| vValue === null
|| Array.isArray(vValue);
}
if (bError) {
Basics.error(oPathValue, "Expected " + sExpectedType);
}
},
/**
* Follows the dynamic "14.5.12 Expression edm:Path" (or variant thereof) contained within
* the given raw value, starting the absolute path identified by the given interface, and
* returns the resulting absolute path as well as some other aspects about the path.
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call; the path must be within
* a complex or entity type!
* @param {object} oRawValue
* the raw value from the meta model, e.g. <code>{AnnotationPath :
* "ToSupplier/@com.sap.vocabularies.Communication.v1.Address"}</code> or <code>
* {AnnotationPath : "@com.sap.vocabularies.UI.v1.FieldGroup#Dimensions"}</code>;
* embedded within an entity set or entity type
* @returns {object}
* - {object} [associationSetEnd=undefined]
* association set end corresponding to the last navigation property
* - {boolean} [navigationAfterMultiple=false]
* if the navigation path has an association end with multiplicity "*" which is not
* the last one
* - {boolean} [isMultiple=false]
* whether the navigation path ends with an association end with multiplicity "*"
* - {string[]} [navigationProperties=[]]
* all navigation property names
* - {string} [resolvedPath=undefined]
* the resulting absolute path
*
* @see sap.ui.model.odata.AnnotationHelper.getNavigationPath
* @see sap.ui.model.odata.AnnotationHelper.gotoEntitySet
* @see sap.ui.model.odata.AnnotationHelper.isMultiple
* @see sap.ui.model.odata.AnnotationHelper.resolvePath
*/
followPath : function (oInterface, oRawValue) {
var oAssociationEnd,
sPath,
sContextPath,
iIndexOfAt,
oModel = oInterface.getModel(),
aParts,
oResult = {
associationSetEnd : undefined,
navigationAfterMultiple : false,
isMultiple : false,
navigationProperties : [],
resolvedPath : undefined
},
sSegment,
oType;
Measurement.average(sPerformanceFollowPath, "", aPerformanceCategories);
sPath = Basics.getPath(oRawValue);
sContextPath = sPath !== undefined && Basics.getStartingPoint(oInterface, sPath);
if (!sContextPath) {
Measurement.end(sPerformanceFollowPath);
return undefined;
}
aParts = sPath.split("/");
if (sPath) {
while (aParts.length && sContextPath) {
sSegment = aParts[0];
iIndexOfAt = sSegment.indexOf("@");
if (iIndexOfAt === 0) {
// term cast
sContextPath += "/" + sSegment.slice(1);
aParts.shift();
continue;
// } else if (iIndexOfAt > 0) { // annotation of a navigation property
// sSegment = sSegment.slice(0, iIndexOfAt);
}
oType = oModel.getObject(sContextPath);
oAssociationEnd = oModel.getODataAssociationEnd(oType, sSegment);
if (oAssociationEnd) {
// navigation property
oResult.associationSetEnd
= oModel.getODataAssociationSetEnd(oType, sSegment);
oResult.navigationProperties.push(sSegment);
if (oResult.isMultiple) {
oResult.navigationAfterMultiple = true;
}
oResult.isMultiple = oAssociationEnd.multiplicity === "*";
sContextPath = oModel.getODataEntityType(oAssociationEnd.type, true);
aParts.shift();
continue;
}
// structural properties or some unsupported case
sContextPath = oModel.getODataProperty(oType, aParts, true);
}
}
oResult.resolvedPath = sContextPath;
Measurement.end(sPerformanceFollowPath);
return oResult;
},
/**
* Returns the dynamic "14.5.12 Expression edm:Path" (or variant thereof) contained within
* the given raw value.
*
* @param {object} oRawValue
* the raw value from the meta model, e.g. <code>{AnnotationPath :
* "ToSupplier/@com.sap.vocabularies.Communication.v1.Address"}</code> or <code>
* {AnnotationPath : "@com.sap.vocabularies.UI.v1.FieldGroup#Dimensions"}</code>
* @returns {string}
* the path or <code>undefined</code> in case the raw value is not supported
*/
getPath : function (oRawValue) {
if (oRawValue) {
if (oRawValue.hasOwnProperty("AnnotationPath")) {
return oRawValue.AnnotationPath;
}
if (oRawValue.hasOwnProperty("Path")) {
return oRawValue.Path;
}
if (oRawValue.hasOwnProperty("PropertyPath")) {
return oRawValue.PropertyPath;
}
if (oRawValue.hasOwnProperty("NavigationPropertyPath")) {
return oRawValue.NavigationPropertyPath;
}
}
// Note: we cannot return undefined above!
return undefined; // some unsupported case
},
/**
* Returns the starting point for the given dynamic "14.5.12 Expression edm:Path" (or
* variant thereof).
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call; the path must be within
* a complex or entity type!
* @param {string} sPath
* the path (just to see if it's empty)
* @returns {string}
* the meta model path to use as a starting point for following the given path
*/
getStartingPoint : function (oInterface, sPath) {
var oEntity,
aMatches = rTypePath.exec(oInterface.getPath()),
oModel;
if (aMatches) {
return aMatches[1]; // start at complex or entity type
}
aMatches = rEntitySetPath.exec(oInterface.getPath());
if (aMatches) {
if (!sPath) {
return aMatches[1]; // start at entity set
}
// go from entity set to entity type
oModel = oInterface.getModel();
oEntity = oModel.getObject(aMatches[1]);
return oModel.getODataEntityType(oEntity.entityType, true);
}
return undefined; // some unsupported case
},
/**
* Fetches the given property or array element at the path/value pair. Logs an error and
* throws an error if the property value is not of the expected type.
*
* @param {object} oPathValue
* a path/value pair
* @param {string} oPathValue.path
* the meta model path to start at
* @param {any} oPathValue.value
* the value at this path
* @param {string|number} vProperty
* the property name or array index
* @param {string} sExpectedType
* the expected type (tested w/ typeof) or the special value "array" for an array
* @returns {any}
* the property value
* @throws {SyntaxError}
* if the result is not of the expected type
*/
property : function (oPathValue, vProperty, sExpectedType) {
return Basics.descend(oPathValue, vProperty, sExpectedType).value;
},
/**
* Converts the result's value to a string.
*
* @param {object} oResult
* an object with the following properties:
* result: "constant", "binding", "composite" or "expression"
* value: {any} the value to write into the resulting string depending on result:
* when "constant": the constant value as a string (from the annotation)
* when "binding": the binding path
* when "expression": a binding expression not wrapped (no "{=" and "}")
* when "composite": a composite binding string
* type: an EDM data type (like "Edm.String")
* constraints: {object} optional type constraints when result is "binding"
* formatOptions: {object} optional type format options when result is "binding"
* parameters: {object} optional binding parameters when result is "binding"
* @param {boolean} bExpression
* if <code>true</code> the value is to be embedded into a binding expression,
* otherwise in a composite binding
* @param {boolean} [bWithType=false]
* if <code>true</code>, <code>oResult.result</code> is "binding" and
* <code>oResult.type</code> maps to a UI5 type, then both type and constraint
* information, as well as format options, are written to the resulting binding string;
* if this is <code>false</code> and <code>oResult.result</code> is "binding",
* then binding parameters are written to the resulting binding string if present
* @param {boolean} [bRaw=false]
* if <code>true</code> and <code>oResult.result</code> is "binding", the resulting
* string will contain the raw value instead of being formatted with the type
* @returns {string}
* the resulting string to embed into a composite binding or a binding expression
*/
resultToString : function (oResult, bExpression, bWithType, bRaw) {
var vValue = oResult.value;
function binding(bAddType) {
var sConstraints,
sFormatOptions,
sParameters = oResult.parameters && Basics.toJSON(oResult.parameters),
bHasParameters = sParameters && sParameters !== "{}",
sResult,
sType = mUi5TypeForEdmType[oResult.type];
bAddType = bAddType && !oResult.ignoreTypeInPath && sType;
if (bAddType || rBadChars.test(vValue) || bHasParameters) {
sResult = "{path:" + Basics.toJSON(vValue);
if (bAddType) {
sResult += ",type:'" + sType + "'";
sConstraints = Basics.toJSON(oResult.constraints);
if (sConstraints && sConstraints !== "{}") {
sResult += ",constraints:" + sConstraints;
}
sFormatOptions
= oResult.formatOptions && Basics.toJSON(oResult.formatOptions);
if (sFormatOptions && sFormatOptions !== "{}") {
sResult += ",formatOptions:" + sFormatOptions;
}
}
if (bHasParameters) {
sResult += ",parameters:" + sParameters;
}
return sResult + "}";
}
return "{" + vValue + "}";
}
function constant(oResult) {
switch (oResult.type) {
case "Edm.Boolean":
case "Edm.Double":
case "Edm.Int32":
return String(oResult.value);
default:
return Basics.toJSON(oResult.value);
}
}
switch (oResult.result) {
case "binding":
if (bExpression) {
return (bRaw ? "%" : "$") + binding(bWithType);
}
return binding(bWithType);
case "composite":
if (bExpression) {
throw new Error(
"Trying to embed a composite binding into an expression binding");
}
return vValue; // Note: it's already a composite binding string
case "constant":
if (oResult.type === "edm:Null") {
if (oResult.value === undefined) {
return bExpression ? "undefined" : undefined;
}
return bExpression ? "null" : null;
}
if (bExpression) {
return constant(oResult);
}
return typeof vValue === "string"
? BindingParser.complexParser.escape(vValue)
: String(vValue);
case "expression":
return bExpression ? vValue : "{=" + vValue + "}";
default:
return undefined;
}
},
/**
* Stringifies the value for usage in an error message. Special handling for functions and
* object trees with circular references.
*
* @param {any} vValue the value
* @returns {string} the stringified value
*/
toErrorString : function (vValue) {
var sJSON;
if (typeof vValue !== "function") {
try {
sJSON = Basics.toJSON(vValue);
// undefined --> undefined
// null, NaN, Infinity --> "null"
// all are correctly handled by String
if (sJSON !== undefined && sJSON !== "null") {
return sJSON;
}
} catch (e) {
// "converting circular structure to JSON"
}
}
return String(vValue);
},
/**
* Converts the value to a JSON string. Prefers the single quote over the double quote.
* This suits better for usage in an XML attribute.
*
* @param {any} vValue the value
* @returns {string} the stringified value
*/
toJSON : function (vValue) {
var sStringified,
bEscaped = false,
sResult = "",
i, c;
sStringified = JSON.stringify(vValue);
if (sStringified === undefined) {
return undefined;
}
for (i = 0; i < sStringified.length; i += 1) {
switch (c = sStringified.charAt(i)) {
case "'": // a single quote must be escaped (can only occur in a string)
sResult += "\\'";
break;
case '"':
if (bEscaped) { // a double quote needs no escaping (only in a string)
sResult += c;
bEscaped = false;
} else { // string begin or end with single quotes
sResult += "'";
}
break;
case "\\":
if (bEscaped) { // an escaped backslash
sResult += "\\\\";
}
bEscaped = !bEscaped;
break;
default:
if (bEscaped) {
sResult += "\\";
bEscaped = false;
}
sResult += c;
}
}
return sResult;
}
};
return Basics;
});