@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
937 lines (864 loc) • 34.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.
*/
// This module provides internal functions for dynamic expressions in OData V4 annotations. It is a
// helper module for sap.ui.model.odata.v4.AnnotationHelper.
sap.ui.define([
"../_AnnotationHelperBasics",
"sap/base/Log",
"sap/ui/base/BindingParser",
"sap/ui/base/ManagedObject",
"sap/ui/base/SyncPromise",
"sap/ui/performance/Measurement"
], function (Basics, Log, BindingParser, ManagedObject, SyncPromise, Measurement) {
"use strict";
// see http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/abnf/odata-abnf-construction-rules.txt
var sAnnotationHelper = "sap.ui.model.odata.v4.AnnotationHelper",
aPerformanceCategories = [sAnnotationHelper],
sPerformanceGetExpression = sAnnotationHelper + "/getExpression",
// a simple binding (see sap.ui.base.BindingParser.simpleParser) to "@i18n" model
// w/o bad chars (see _AnnotationHelperBasics: rBadChars) inside path!
rI18n = /^{>[^\\{}:]+}$/,
mOData2JSOperators = { // mapping of OData operator to JavaScript operator
And : "&&",
Eq : "===",
Ge : ">=",
Gt : ">",
Le : "<=",
Lt : "<",
Ne : "!==",
Not : "!",
Or : "||"
},
// whether warning for missing complex binding parser has already been logged
bSimpleParserWarningLogged = false,
// mapping of EDM type to a type category (lower case = JavaScript primitive type)
mType2Category = {
"Edm.Boolean" : "boolean",
"Edm.Byte" : "number",
"Edm.Date" : "Date",
"Edm.DateTimeOffset" : "DateTimeOffset",
"Edm.Decimal" : "Decimal",
"Edm.Double" : "number",
"Edm.Guid" : "string",
"Edm.Int16" : "number",
"Edm.Int32" : "number",
"Edm.Int64" : "Decimal",
"Edm.SByte" : "number",
"Edm.Single" : "number",
"Edm.String" : "string",
"Edm.TimeOfDay" : "TimeOfDay"
},
mType2Type = { // mapping of constant "edm:*" type to dynamic "Edm.*" type
Bool : "Edm.Boolean",
Float : "Edm.Double",
Date : "Edm.Date",
DateTimeOffset : "Edm.DateTimeOffset",
Decimal : "Edm.Decimal",
Guid : "Edm.Guid",
Int : "Edm.Int64",
Int32 : "Edm.Int32",
String : "Edm.String",
TimeOfDay : "Edm.TimeOfDay"
},
mTypeCategoryNeedsCompare = {
boolean : false,
Date : false,
DateTimeOffset : true,
Decimal : true,
number : false,
string : false,
TimeOfDay : false
},
/**
* This object contains helper functions to process an expression in OData V4 annotations.
*
* The handler functions corresponding to nodes of an annotation expression all use
* a parameter <code>oPathValue</code>. This parameter contains the following properties:
* <ul>
* <li> <code>asExpression</code>: {boolean} parser state: if this property is
* <code>true</code>, an embedded <code>concat</code> must be rendered as an expression
* binding and not a composite binding.
* <li> <code>complexBinding</code>: {boolean} parser state: if this property is
* <code>true</code>, bindings shall have type and constraints information
* <li> <code>ignoreAsPrefix</code>: {string} an optional prefix to be ignored in a path
* expression (for example, binding parameter name)
* <li> <code>model</code>: {sap.ui.model.odata.v4.ODataMetaModel} the metamodel
* <li> <code>path</code>: {string} the path in the metamodel that leads to the value
* <li> <code>prefix</code>: {string} used in a path expression as a prefix for the
* value; is either an empty string or a path ending with a "/"
* <li> <code>value</code>: {any} the value of the (sub) expression from the metamodel
* </ul>
*
* Unless specified otherwise all functions return a result object with the following
* properties:
* <ul>
* <li> <code>result</code>: "binding", "composite", "constant" or "expression"
* <li> <code>value</code>: depending on <code>result</code>:
* <ul>
* <li> when "binding": {string} the binding path
* <li> when "composite": {string} the binding string incl. the curly braces
* <li> when "constant": {any} the constant value (not escaped if string)
* <li> when "expression": {string} the expression unwrapped (no "{=" and "}")
* </ul>
* <li> <code>type</code>: the EDM data type (like "Edm.String") if it could be
* determined
* <li> <code>constraints</code>: {object} type constraints if result is "binding"
* <li> <code>formatOptions</code>: {object} format options if result is "binding"
* <li> <code>parameters</code>: {object} binding parameters if result is "binding"
* </ul>
*
* @private
*/
Expression;
/*
* Logs the given error message for the given path and returns a sync promise that rejects with
* a SyntaxError.
*
* @param {object} oPathValue
* @param {string} sMessage
* @returns {sap.ui.base.SyncPromise}
*/
function asyncError(oPathValue, sMessage) {
return SyncPromise.resolve().then(function () {
error(oPathValue, sMessage);
});
}
/*
* Logs the given error message for the given path and throws a SyntaxError.
*
* @param {object} oPathValue
* @param {string} sMessage
* @throws {SyntaxError}
*/
function error(oPathValue, sMessage) {
Basics.error(oPathValue, sMessage, sAnnotationHelper);
}
Expression = {
/**
* Adjusts the second operand so that both have the same category, if possible.
*
* @param {object} oOperand1
* the operand 1 (as a result object with category)
* @param {object} oOperand2
* the operand 2 (as a result object with category) - may be modified
*/
adjustOperands : function (oOperand1, oOperand2) {
if (oOperand1.result !== "constant" && oOperand1.category === "number"
&& oOperand2.result === "constant" && oOperand2.type === "Edm.Int64") {
// adjust an integer constant of type "Edm.Int64" to the number
oOperand2.category = "number";
}
if (oOperand1.result !== "constant" && oOperand1.category === "Decimal"
&& oOperand2.result === "constant" && oOperand2.type === "Edm.Int32") {
// adjust an integer constant of type "Edm.Int32" to the decimal
oOperand2.category = "Decimal";
oOperand2.type = oOperand1.type;
}
},
/**
* Handling of "14.5.3 Expression edm:Apply".
*
* @param {object} oPathValue
* path and value information pointing to the apply (see Expression object)
* @param {object} oParameters
* path and value information pointing to the parameter array (see Expression object)
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
apply : function (oPathValue, oParameters) {
var oFunction = Basics.descend(oPathValue, "$Function", "string");
switch (oFunction.value) {
case "odata.concat": // 14.5.3.1.1 Function odata.concat
return Expression.concat(oParameters);
case "odata.fillUriTemplate": // 14.5.3.1.2 Function odata.fillUriTemplate
return Expression.fillUriTemplate(oParameters);
case "odata.uriEncode": // 14.5.3.1.3 Function odata.uriEncode
return Expression.uriEncode(oParameters);
default:
return asyncError(oFunction, "unknown function: " + oFunction.value);
}
},
/**
* Handling of "14.5.5 Expression edm:Collection".
*
* @param {object} oPathValue
* path and value information pointing to the array (see Expression object)
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
collection : function (oPathValue) {
var aPromises;
// needed so that we can safely call Array#map
Basics.expectType(oPathValue, "array");
aPromises = oPathValue.value.map(function (_oValue, i) {
return Expression.expression(
Basics.descend(oPathValue, i, true/*"as expression"*/),
/*bInCollection*/true);
});
return SyncPromise.all(aPromises).then(function (aElements) {
aElements = aElements.map(function (oElement) {
return Basics.resultToString(oElement, true, false, true);
});
return {
result : "expression",
value : "odata.collection([" + aElements.join(",") + "])"
};
});
},
/**
* Handling of "14.5.3.1.1 Function odata.concat".
*
* @param {object} oPathValue
* path and value information pointing to the parameter array (see Expression object)
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
concat : function (oPathValue) {
var aPromises;
// needed so that we can safely call Array#map
Basics.expectType(oPathValue, "array");
aPromises = oPathValue.value.map(function (_oValue, i) {
// an embedded concat must use expression binding
return Expression.parameter(oPathValue, i);
});
return SyncPromise.all(aPromises).then(function (aParameters) {
var bExpression,
aParts,
oResult;
bExpression = oPathValue.asExpression || aParameters.some(function (oParameter) {
// if any parameter is type expression, the concat must become expression, too
return oParameter.result === "expression";
});
// convert the results to strings after we know whether the result is expression
aParts = aParameters.filter(function (oParameter) {
// ignore null (otherwise the string 'null' would appear in expressions)
return oParameter.type !== "edm:Null";
}).map(function (oParameter) {
if (bExpression) {
// the expression might have a lower operator precedence than '+'
Expression.wrapExpression(oParameter);
}
return Basics.resultToString(oParameter, bExpression,
oPathValue.complexBinding);
});
oResult = bExpression
? {result : "expression", value : aParts.join("+")}
: {result : "composite", value : aParts.join("")};
oResult.type = "Edm.String";
return oResult;
});
},
/**
* Handling of "14.5.6 Expression edm:If".
*
* @param {object} oPathValue
* path and value information pointing to the parameter array (see Expression object).
* The first parameter element is the conditional expression and must evaluate to an
* Edm.Boolean. The second and third child elements are the expressions, which are
* evaluated conditionally.
* @param {boolean} [bInCollection]
* Whether "14.5.6 Expression edm:If" appears as a direct child of
* "14.5.5 Expression edm:Collection" and thus needs no third child element ("else")
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
conditional : function (oPathValue, bInCollection) {
var bComplexBinding = oPathValue.complexBinding,
oPathValueForCondition = bComplexBinding
? Object.assign({}, oPathValue, {complexBinding : false})
: oPathValue;
/*
* Returns the string representation of the given parameter value.
* @param {object} oParameterValue the parameter value
* @param {boolean} bComplex whether the result is a complex binding or a simple binding
* @param {boolean} [bRaw] whether the result will contain the raw value
*/
function toString(oParameterValue, bComplex, bRaw) {
return Basics.resultToString(Expression.wrapExpression(oParameterValue), true,
bComplex, bRaw);
}
return SyncPromise.all([
Expression.parameter(oPathValueForCondition, 0, "Edm.Boolean"),
Expression.parameter(oPathValue, 1),
bInCollection && oPathValue.value.length === 2
? {result : "constant", type : "edm:Null", value : undefined}
: Expression.parameter(oPathValue, 2)
]).then(function (aResults) {
var oCondition = aResults[0],
oThen = aResults[1],
oElse = aResults[2],
sType = oThen.type;
if (oThen.type === "edm:Null") {
sType = oElse.type;
} else if (oElse.type !== "edm:Null" && oThen.type !== oElse.type) {
error(oPathValue,
"Expected same type for second and third parameter, types are '"
+ oThen.type + "' and '" + oElse.type + "'");
}
return {
result : "expression",
type : sType,
value : toString(oCondition, false, true)
+ "?" + toString(oThen, bComplexBinding)
+ ":" + toString(oElse, bComplexBinding)
};
});
},
/**
* Handling of "14.4 Constant Expressions", i.e.
* <ul>
* <li> "14.4.2 Expression edm:Bool",
* <li> "14.4.3 Expression edm:Date",
* <li> "14.4.4 Expression edm:DateTimeOffset",
* <li> "14.4.5 Expression edm:Decimal",
* <li> "14.4.8 Expression edm:Float",
* <li> "14.4.9 Expression edm:Guid",
* <li> "14.4.10 Expression edm:Int",
* <li> "14.4.11 Expression edm:String",
* <li> "14.4.12 Expression edm:TimeOfDay".
* </ul>
*
* @param {object} oPathValue
* path and value information pointing to the constant (see Expression object)
* @param {string} sEdmType
* the "edm:*" type of the constant, e.g. "Bool" or "Int" (incl. "Int32")
* @returns {object}
* the result object
*/
constant : function (oPathValue, sEdmType) {
var vValue = oPathValue.value;
if (sEdmType === "String") {
if (rI18n.test(vValue)) { // a simple binding to "@i18n" model
return {
ignoreTypeInPath : true,
result : "binding",
type : "Edm.String",
value : vValue.slice(1, -1) // cut off "{" and "}"
};
}
}
return {
result : "constant",
type : mType2Type[sEdmType],
value : vValue
};
},
/**
* Calculates an expression.
*
* @param {object} oPathValue
* path and value information pointing to the expression (see Expression object)
* @param {boolean} [bInCollection]
* Whether the current expression appears as a direct child of
* "14.5.5 Expression edm:Collection"
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
expression : function (oPathValue, bInCollection) {
var oRawValue = oPathValue.value,
oSubPathValue = oPathValue,
sType;
if (oRawValue === null) {
sType = "Null";
} else if (typeof oRawValue === "boolean") {
sType = "Bool";
} else if (typeof oRawValue === "number") {
sType = isFinite(oRawValue) && Math.floor(oRawValue) === oRawValue
? "Int32"
: "Float";
} else if (typeof oRawValue === "string") {
sType = "String";
} else if (Array.isArray(oRawValue)) { // 14.5.5 Expression edm:Collection
return Expression.collection(oPathValue);
} else {
Basics.expectType(oPathValue, "object");
if (oRawValue.$kind === "Property") {
oPathValue.value = oPathValue.model.getObject(oPathValue.path + "@sapui.name");
return Expression.path(oPathValue);
}
[
"$And", "$Apply", "$Date", "$DateTimeOffset", "$Decimal", "$Float", "$Eq",
"$Ge", "$Gt", "$Guid", "$If", "$Int", "$Le", "$Lt", "$Name", "$Ne", "$Not",
"$Null", "$Or", "$Path", "$PropertyPath", "$TimeOfDay", "$LabeledElement"
].forEach(function (sProperty) {
if (oRawValue.hasOwnProperty(sProperty)) {
sType = sProperty.slice(1);
oSubPathValue = Basics.descend(oPathValue, sProperty);
}
});
}
switch (sType) {
case "Apply": // 14.5.3 Expression edm:Apply
return Expression.apply(oPathValue, oSubPathValue);
case "If": // 14.5.6 Expression edm:If
return Expression.conditional(oSubPathValue, bInCollection);
case "Name": // 12.4.1 Attribute Name
case "Path": // 14.5.12 Expression edm:Path
case "PropertyPath": // 14.5.13 Expression edm:PropertyPath
return Expression.path(oSubPathValue);
case "Date": // 14.4.3 Expression edm:Date
case "DateTimeOffset": // 14.4.4 Expression edm:DateTimeOffset
case "Decimal": // 14.4.5 Expression edm:Decimal
case "Guid": // 14.4.9 Expression edm:Guid
case "Int": // 14.4.10 Expression edm:Int
case "String": // 14.4.11 Expression edm:String
case "TimeOfDay": // 14.4.12 Expression edm:TimeOfDay
Basics.expectType(oSubPathValue, "string");
// fall through
case "Bool": // 14.4.2 Expression edm:Bool
case "Float": // 14.4.8 Expression edm:Float
case "Int32": // 14.4.10 Expression edm:Int
return SyncPromise.resolve(Expression.constant(oSubPathValue, sType));
case "And":
case "Eq":
case "Ge":
case "Gt":
case "Le":
case "Lt":
case "Ne":
case "Or":
// 14.5.1 Comparison and Logical Operators
return Expression.operator(oSubPathValue, sType);
case "Not": // 14.5.1 Comparison and Logical Operators
return Expression.not(oSubPathValue);
case "Null": // 14.5.10 Expression edm:Null
return SyncPromise.resolve({
result : "constant",
type : "edm:Null",
value : null
});
// case "LabeledElement": 14.5.8 Expression edm:LabeledElement
default:
return asyncError(oPathValue, "Unsupported OData expression");
}
},
/**
* Fetch the result object for a currency or a unit.
* If <code>oPathValue.path</code> references a property which has an
* <code>Org.OData.Measures.V1.ISOCurrency</code> annotation, a composite result object for
* a <code>sap.ui.model.odata.type.Currency</code> type with the currency, the currency code
* and the currency customizing as parts is returned.
* If <code>oPathValue.path</code> references a property which has an
* <code>Org.OData.Measures.V1.Unit</code> annotation, a composite result object for a
* <code>sap.ui.model.odata.type.Unit</code> type with the measures, the unit and the unit
* customizing as parts is returned.
*
* @param {object} oPathValue
* model, path (and value) information pointing to the path (see Expression object)
* @param {string} sValue
* use this value instead of <code>oPathValue.value</code>!
* @param {string} sType
* the type of the property referenced by <code>oPathValue.path</code>
* @param {object} mConstraints
* the type constraints for the property referenced by <code>oPathValue.path</code>
* @returns {sap.ui.base.SyncPromise|undefined}
* a sync promise which resolves with a result object for the currency or unit, or is
* rejected with an error; <code>undefined</code> if there are no unit and currency
* annotations for the property referenced by <code>oPathValue.path</code>
*/
fetchCurrencyOrUnit : function (oPathValue, sValue, sType, mConstraints) {
var sCompositeType = "sap.ui.model.odata.type.Unit",
sComputedAnnotation = "@@requestUnitsOfMeasure",
oModel = oPathValue.model,
sPath = oPathValue.path + "@Org.OData.Measures.V1.Unit/$Path",
sTargetPath = oModel.getObject(sPath);
function getBinding(mConstraints0, sType0, sPath0) {
return Basics.resultToString(
Expression.pathResult(oPathValue, sType0, sPath0, mConstraints0),
false, true);
}
if (!sTargetPath) {
sCompositeType = "sap.ui.model.odata.type.Currency";
sComputedAnnotation = "@@requestCurrencyCodes";
sPath = oPathValue.path + "@Org.OData.Measures.V1.ISOCurrency/$Path";
sTargetPath = oModel.getObject(sPath);
}
if (!sTargetPath) {
return undefined;
}
return oModel.fetchObject(sPath + "/$").then(function (oTarget) {
var sCompositeConstraints
= oModel.getObject(oPathValue.path
+ "@com.sap.vocabularies.UI.v1.DoNotCheckScaleOfMeasureQuantity")
? ",constraints:{'skipDecimalsValidation':true}"
: "";
return {
result : "composite",
type : sCompositeType,
value : (mType2Category[sType] === "number"
? "{formatOptions:{parseAsString:false},"
: "{")
+ "mode:'TwoWay',parts:["
+ getBinding(mConstraints, sType, sValue)
+ ","
+ getBinding(oModel.getConstraints(oTarget, sPath), oTarget.$Type,
sTargetPath)
+ ",{mode:'OneTime',path:'/##" + sComputedAnnotation + "',targetType:'any'}"
+ "],type:'" + sCompositeType + "'" + sCompositeConstraints + "}"
};
});
},
/**
* Fetch the result object for a date/time with timezone.
* If <code>oPathValue.path</code> references a property which has a
* <code>com.sap.vocabularies.Common.v1.Timezone</code> annotation, a composite result
* object for a <code>sap.ui.model.odata.type.DateTimeWithTimezone</code> type with the
* date/time and the timezone as parts is returned.
*
* @param {object} oPathValue
* model, path (and value) information pointing to the path (see Expression object)
* @param {string} sValue
* use this value instead of <code>oPathValue.value</code>!
* @param {object} mConstraints
* the type constraints for the property referenced by <code>oPathValue.path</code>
* @returns {sap.ui.base.SyncPromise|undefined}
* a sync promise which resolves with a result object for the date/time with timezone, or
* is rejected with an error; <code>undefined</code> if there is no timezone annotation
* for the property referenced by <code>oPathValue.path</code>
*/
fetchDateTimeWithTimezone : function (oPathValue, sValue, mConstraints) {
var oModel = oPathValue.model,
sPath = oPathValue.path + "@com.sap.vocabularies.Common.v1.Timezone/$Path",
sTargetPath = oModel.getObject(sPath);
function formatPart(mConstraints0, sType, sPath0) {
return Basics.resultToString(
Expression.pathResult(oPathValue, sType, sPath0, mConstraints0),
false, true);
}
if (!sTargetPath) {
return undefined;
}
return oModel.fetchObject(sPath + "/$").then(function (oTarget) {
return {
result : "composite",
type : "sap.ui.model.odata.type.DateTimeWithTimezone",
value : "{mode:'TwoWay',parts:["
+ formatPart(mConstraints, "Edm.DateTimeOffset", sValue)
+ ","
+ formatPart(oModel.getConstraints(oTarget, sPath), oTarget.$Type,
sTargetPath)
+ "],type:'sap.ui.model.odata.type.DateTimeWithTimezone'}"
};
});
},
/**
* Handling of "14.5.3.1.2 Function odata.fillUriTemplate".
*
* @param {object} oPathValue
* path and value information pointing to the parameter array (see Expression object)
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
fillUriTemplate : function (oPathValue) {
var aParameters = [],
aPromises,
i;
// Note: it is safe to modify the caller's object here
oPathValue.complexBinding = false;
aPromises = [Expression.parameter(oPathValue, 0, "Edm.String")];
for (i = 1; i < oPathValue.value.length; i += 1) {
aParameters[i] = Basics.descend(oPathValue, i, "object");
aPromises.push(Expression.expression(
Basics.descend(aParameters[i], "$LabeledElement", true/*"as expression"*/)));
}
return SyncPromise.all(aPromises).then(function (aResults) {
var sName,
aParts = [],
sPrefix = "";
aParts.push("odata.fillUriTemplate(",
Basics.resultToString(aResults[0], true, false, true),
",{");
for (i = 1; i < oPathValue.value.length; i += 1) {
sName = Basics.property(aParameters[i], "$Name", "string");
aParts.push(sPrefix, Basics.toJSON(sName), ":",
Basics.resultToString(aResults[i], true, false, true));
sPrefix = ",";
}
aParts.push("})");
return {
result : "expression",
type : "Edm.String",
value : aParts.join("")
};
});
},
/**
* Formats the result to be an operand for a logical or comparison operator. Handles
* constants accordingly.
*
* @param {object} oResult
* a result object with category
* @param {boolean} bWrapExpression
* if true, wrap an expression in <code>oResult</code> with "()"
* @returns {string}
* the formatted result
*/
formatOperand : function (oResult, bWrapExpression) {
if (oResult.result === "constant") {
switch (oResult.category) {
case "boolean":
case "number":
return String(oResult.value);
// no default
}
}
if (bWrapExpression) {
Expression.wrapExpression(oResult);
}
return Basics.resultToString(oResult, true, false, true);
},
/**
* Calculates an expression. Ensures that errors that are thrown while processing are
* handled accordingly.
*
* @param {object} oPathValue
* path and value information pointing to the expression (see Expression object)
* @returns {Promise|string|undefined}
* the expression value or "Unsupported: oRawValue" in case of an error or
* <code>undefined</code> in case the raw value is undefined; may instead return a
* <code>Promise</code> resolving with that result.
*/
getExpression : function (oPathValue) {
if (oPathValue.value === undefined) {
return undefined;
}
Measurement.average(sPerformanceGetExpression, "", aPerformanceCategories);
if (!bSimpleParserWarningLogged
&& ManagedObject.bindingParser === BindingParser.simpleParser) {
Log.warning("Complex binding syntax not active", null, sAnnotationHelper);
bSimpleParserWarningLogged = true;
}
return Expression.expression(oPathValue).then(function (oResult) {
return Basics.resultToString(oResult, false, oPathValue.complexBinding);
}, function (e) {
if (e instanceof SyntaxError) {
return "Unsupported: " + BindingParser.complexParser.escape(
Basics.toErrorString(oPathValue.value));
}
throw e;
}).finally(function () {
Measurement.end(sPerformanceGetExpression);
}).unwrap();
},
/**
* Handling of "14.5.1 Comparison and Logical Operators": <code>edm:Not</code>.
*
* @param {object} oPathValue
* path and value information pointing to the parameter (see Expression object)
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
not : function (oPathValue) {
// Note: it is safe to modify the caller's object here
oPathValue.asExpression = true;
oPathValue.complexBinding = false;
return Expression.expression(oPathValue).then(function (oParameter) {
return {
result : "expression",
type : "Edm.Boolean",
value : "!" + Basics.resultToString(Expression.wrapExpression(oParameter),
true, false, true)
};
});
},
/**
* Handling of "14.5.1 Comparison and Logical Operators" except <code>edm:Not</code>.
*
* @param {object} oPathValue
* path and value information pointing to the parameter array (see Expression object)
* @param {string} sType
* the operator as text (like "And" or "Or")
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
operator : function (oPathValue, sType) {
var sExpectedEdmType = sType === "And" || sType === "Or" ? "Edm.Boolean" : undefined;
// Note: it is safe to modify the caller's object here
oPathValue.complexBinding = false;
return SyncPromise.all([
Expression.parameter(oPathValue, 0, sExpectedEdmType),
Expression.parameter(oPathValue, 1, sExpectedEdmType)
]).then(function (aResults) {
var bNeedsCompare,
oParameter0 = aResults[0],
oParameter1 = aResults[1],
sSpecialType = "",
sValue0,
sValue1;
if (oParameter0.type !== "edm:Null" && oParameter1.type !== "edm:Null") {
oParameter0.category = mType2Category[oParameter0.type];
oParameter1.category = mType2Category[oParameter1.type];
Expression.adjustOperands(oParameter0, oParameter1);
Expression.adjustOperands(oParameter1, oParameter0);
if (oParameter0.category !== oParameter1.category) {
error(oPathValue, "Expected two comparable parameters but instead saw "
+ oParameter0.type + " and " + oParameter1.type);
}
switch (oParameter0.category) {
case "Decimal":
sSpecialType = ",'Decimal'";
break;
case "DateTimeOffset":
sSpecialType = ",'DateTime'";
break;
// no default
}
bNeedsCompare = mTypeCategoryNeedsCompare[oParameter0.category];
}
sValue0 = Expression.formatOperand(oParameter0, !bNeedsCompare);
sValue1 = Expression.formatOperand(oParameter1, !bNeedsCompare);
return {
result : "expression",
type : "Edm.Boolean",
value : bNeedsCompare
? "odata.compare(" + sValue0 + "," + sValue1 + sSpecialType + ")"
+ mOData2JSOperators[sType] + "0"
: sValue0 + mOData2JSOperators[sType] + sValue1
};
});
},
/**
* Evaluates a parameter and ensures that the result is of the given EDM type.
*
* The function calls <code>expression</code> with <code>asExpression : true</code>. This
* will cause any embedded <code>odata.concat</code> to generate an expression binding. This
* should be correct in any case because only a standalone <code>concat</code> may generate
* a composite binding.
*
* @param {object} oPathValue
* path and value information pointing to the parameter array (see Expression object)
* @param {number} iIndex
* the parameter index
* @param {string} [sEdmType]
* the expected EDM type or <code>undefined</code> if any type is allowed
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
parameter : function (oPathValue, iIndex, sEdmType) {
var oParameter = Basics.descend(oPathValue, iIndex, true/*"as expression"*/);
return Expression.expression(oParameter).then(function (oResult) {
if (sEdmType && sEdmType !== oResult.type) {
error(oParameter, "Expected " + sEdmType + " but instead saw " + oResult.type);
}
return oResult;
});
},
/**
* Handling of "14.5.12 Expression edm:Path" and "14.5.13 Expression edm:PropertyPath".
* If <code>oPathValue.path</code> references a property which has an
* <code>Org.OData.Measures.V1.ISOCurrency</code> annotation, a composite result object for
* a <code>sap.ui.model.odata.type.Currency</code> type with the currency, the currency code
* and the currency customizing as parts is returned.
* If <code>oPathValue.path</code> references a property which has an
* <code>Org.OData.Measures.V1.Unit</code> annotation, a composite result object for a
* <code>sap.ui.model.odata.type.Unit</code> type with the measures, the unit and the unit
* customizing as parts is returned.
* If <code>oPathValue.path</code> references a property with type
* <code>Edm.DateTimeOffset</code> and a
* <code>com.sap.vocabularies.Common.v1.Timezone</code> annotation, a composite result
* object for a <code>sap.ui.model.odata.type.DateTimeWithTimezone</code> type with the
* date/time and the timezone as parts is returned.
*
* @param {object} oPathValue
* model, path and value information pointing to the path (see Expression object)
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
path : function (oPathValue) {
var sIgnoreAsPrefix = oPathValue.ignoreAsPrefix,
oModel = oPathValue.model,
oPromise,
sValue = oPathValue.value;
if (sIgnoreAsPrefix && sValue.startsWith(sIgnoreAsPrefix)) {
sValue = sValue.slice(sIgnoreAsPrefix.length);
}
Basics.expectType(oPathValue, "string");
oPromise = oModel.fetchObject(oPathValue.path + "/$");
if (oPromise.isPending() && !oPathValue.$$valueAsPromise) {
oPromise.caught();
oPromise = SyncPromise.resolve();
}
return oPromise.then(function (oProperty) {
var oAnnotationBasedPromise,
mConstraints,
sType = oProperty && oProperty.$Type;
if (oProperty && oPathValue.complexBinding) {
mConstraints = oModel.getConstraints(oProperty, oPathValue.path);
oAnnotationBasedPromise = sType === "Edm.DateTimeOffset"
? Expression.fetchDateTimeWithTimezone(oPathValue, sValue, mConstraints)
: Expression.fetchCurrencyOrUnit(oPathValue, sValue, sType, mConstraints);
}
return oAnnotationBasedPromise
|| Expression.pathResult(oPathValue, sType, sValue, mConstraints);
});
},
/**
* Returns the result of a path expression. Takes care of parseKeepsEmptyString.
*
* @param {object} oPathValue
* model, path and value information pointing to the path (see Expression object)
* @param {string} sType
* the EDM type
* @param {string} sPath
* the relative binding path
* @param {object} mConstraints
* the type constraints for the property referenced by <code>sPath</code>
* @returns {object}
* the result object
*/
pathResult : function (oPathValue, sType, sPath, mConstraints) {
return {
constraints : mConstraints,
formatOptions : sType === "Edm.String"
? Object.assign({parseKeepsEmptyString : true}, oPathValue.formatOptions)
: oPathValue.formatOptions,
parameters : oPathValue.parameters,
result : "binding",
type : sType,
value : oPathValue.prefix + sPath
};
},
/**
* Handling of "14.5.3.1.3 Function odata.uriEncode".
*
* @param {object} oPathValue
* path and value information pointing to the parameter array (see Expression object)
* @returns {sap.ui.base.SyncPromise}
* a sync promise which resolves with the result object or is rejected with an error
*/
uriEncode : function (oPathValue) {
return Expression.parameter(oPathValue, 0).then(function (oResult) {
return {
result : "expression",
type : "Edm.String",
value : oResult.type === "Edm.String"
// Note: odata.uriEncode() is V2, but safe for Edm.String!
? "odata.uriEncode(" + Basics.resultToString(oResult, true, false, true)
+ "," + Basics.toJSON(oResult.type) + ")"
// Note: see _Helper.formatLiteral()
: "String(" + Basics.resultToString(oResult, true, false, true) + ")"
};
});
},
/**
* Wraps the result's value with "()" in case it is an expression because the result will be
* become a parameter of an infix operator and we have to ensure that the operator
* precedence remains correct.
*
* @param {object} oResult
* a result object
* @returns {object}
* the given result object (for chaining)
*/
wrapExpression : function (oResult) {
if (oResult.result === "expression") {
oResult.value = "(" + oResult.value + ")";
}
return oResult;
}
};
return Expression;
}, /* bExport= */ false);