@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
950 lines (895 loc) • 34 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.
*/
/*eslint-disable max-len */
// This module provides internal functions for dynamic expressions in OData V4 annotations. It is a
// helper module for sap.ui.model.odata.AnnotationHelper.
sap.ui.define([
"./_AnnotationHelperBasics",
"sap/base/Log",
"sap/ui/base/BindingParser",
"sap/ui/base/ManagedObject",
"sap/ui/core/CalendarType",
"sap/ui/core/format/DateFormat",
"sap/ui/model/odata/ODataUtils",
"sap/ui/performance/Measurement"
], function (Basics, Log, BindingParser, ManagedObject, CalendarType, DateFormat, ODataUtils,
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.AnnotationHelper",
oDateFormatter,
oDateTimeOffsetFormatter,
sDateValue = "\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])",
sDecimalValue = "[-+]?\\d+(?:\\.\\d+)?",
sMaxSafeInteger = "9007199254740991",
sMinSafeInteger = "-" + sMaxSafeInteger,
aPerformanceCategories = [sAnnotationHelper],
sPerformanceGetExpression = sAnnotationHelper + "/getExpression",
oTimeFormatter,
sTimeOfDayValue = "(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(\\.\\d{1,12})?)?",
mEdmType2RegExp = {
Bool : /^true$|^false$/i,
// Note: 'NaN' and 'INF' are case sensitive, "e" is not!
Float : new RegExp("^" + sDecimalValue + "(?:[eE][-+]?\\d+)?$|^NaN$|^-INF$|^INF$"),
Date : new RegExp("^" + sDateValue + "$"),
DateTimeOffset : new RegExp("^" + sDateValue + "T" + sTimeOfDayValue
+ "(?:Z|[-+](?:0\\d|1[0-3]):[0-5]\\d|[-+]14:00)$", "i"),
Decimal : new RegExp("^" + sDecimalValue + "$"),
Guid : /^[A-F0-9]{8}-(?:[A-F0-9]{4}-){3}[A-F0-9]{12}$/i,
Int : /^[-+]?\d{1,19}$/,
TimeOfDay : new RegExp("^" + sTimeOfDayValue + "$")
},
Expression,
// a simple binding (see sap.ui.base.BindingParser.simpleParser) to "@i18n" model
// w/o bad chars (see _AnnotationHelperBasics: rBadChars) inside path!
rI18n = /^\{@i18n>[^\\\{\}:]+\}$/,
rInteger = /^\d+$/,
mOData2JSOperators = { // mapping of OData operator to JavaScript operator
And : "&&",
Eq : "===",
Ge : ">=",
Gt : ">",
Le : "<=",
Lt : "<",
Ne : "!==",
Not : "!",
Or : "||"
},
rSchemaPath = /^(\/dataServices\/schema\/\d+)(?:\/|$)/,
mType2Category = { // mapping of EDM type to a type category
"Edm.Boolean" : "boolean",
"Edm.Byte" : "number",
"Edm.Date" : "date",
"Edm.DateTime" : "datetime",
"Edm.DateTimeOffset" : "datetime",
"Edm.Decimal" : "decimal",
"Edm.Double" : "number",
"Edm.Float" : "number",
"Edm.Guid" : "string",
"Edm.Int16" : "number",
"Edm.Int32" : "number",
"Edm.Int64" : "decimal",
"Edm.SByte" : "number",
"Edm.Single" : "number",
"Edm.String" : "string",
"Edm.Time" : "time",
"Edm.TimeOfDay" : "time"
},
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",
String : "Edm.String",
TimeOfDay : "Edm.TimeOfDay"
},
mTypeCategoryNeedsCompare = {
"boolean" : false,
"date" : true,
"datetime" : true,
"decimal" : true,
"number" : false,
"string" : false,
"time" : true
};
/**
* 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>path</code>: {string} the path in the metamodel that leads to the value
* <li><code>value</code>: {any} the value of the (sub) expression from the metamodel
* <li><code>withType</code>: {boolean} parser state: if this property is <code>true</code>,
* all bindings shall have type and constraints information
* </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"
* </ul>
*/
Expression = {
/**
* Sets the static date and time formatter instances.
*
* @private
*/
_setDateTimeFormatter : function () {
oDateFormatter = DateFormat.getDateInstance({
calendarType : CalendarType.Gregorian,
pattern : "yyyy-MM-dd",
strictParsing : true,
UTC : true
});
oDateTimeOffsetFormatter = DateFormat.getDateTimeInstance({
calendarType : CalendarType.Gregorian,
pattern : "yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
strictParsing : true
});
oTimeFormatter = DateFormat.getTimeInstance({
calendarType : CalendarType.Gregorian,
pattern : "HH:mm:ss.SSS",
strictParsing : true,
UTC : true
});
},
/**
* 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;
}
if (oOperand1.result === "constant" && oOperand1.category === "date"
&& oOperand2.result !== "constant" && oOperand2.category === "datetime") {
// adjust a datetime parameter to the date constant
oOperand2.category = "date";
}
},
/**
* Handling of "14.5.3 Expression edm:Apply".
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* The callback interface related to the current formatter call
* @param {object} oPathValue
* Path and value information pointing to the apply (see Expression object)
* @returns {object|undefined}
* The result object or <code>undefined</code> in error cases
*/
apply : function (oInterface, oPathValue) {
var oName = Basics.descend(oPathValue, "Name", "string"),
oParameters = Basics.descend(oPathValue, "Parameters");
switch (oName.value) {
case "odata.concat": // 14.5.3.1.1 Function odata.concat
return Expression.concat(oInterface, oParameters);
case "odata.fillUriTemplate": // 14.5.3.1.2 Function odata.fillUriTemplate
return Expression.fillUriTemplate(oInterface, oParameters);
case "odata.uriEncode": // 14.5.3.1.3 Function odata.uriEncode
return Expression.uriEncode(oInterface, oParameters);
default:
Basics.error(oName, "unknown function: " + oName.value);
return undefined;
}
},
/**
* Handling of "14.5.3.1.1 Function odata.concat".
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {object} oPathValue
* path and value information pointing to the parameters array (see Expression object)
* @returns {object}
* the result object
*/
concat : function (oInterface, oPathValue) {
var bExpression = oPathValue.asExpression,
aParts = [],
oResult,
aResults = [];
// needed so that we can safely call the forEach
Basics.expectType(oPathValue, "array");
oPathValue.value.forEach(function (_oValue, i) {
// an embedded concat must use expression binding
oResult = Expression.parameter(oInterface, oPathValue, i);
// if any parameter is type expression, the concat must become expression, too
bExpression = bExpression || oResult.result === "expression";
aResults.push(oResult);
});
// convert the results to strings after we know whether the result is expression
aResults.forEach(function (oResult) {
if (bExpression) {
// the expression might have a lower operator precedence than '+'
Expression.wrapExpression(oResult);
}
if (oResult.type !== 'edm:Null') {
// ignore null (otherwise the string 'null' would appear in expressions)
aParts.push(Basics.resultToString(oResult, bExpression, oPathValue.withType));
}
});
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 {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {object} oPathValue
* path and value information pointing to the parameters 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.
* @returns {object}
* the result object
*/
conditional : function (oInterface, oPathValue) {
var oCondition = Expression.parameter(oInterface, oPathValue, 0, "Edm.Boolean"),
oThen = Expression.parameter(oInterface, oPathValue, 1),
oElse = Expression.parameter(oInterface, oPathValue, 2),
sType = oThen.type,
bWithType = oPathValue.withType;
if (oThen.type === "edm:Null") {
sType = oElse.type;
} else if (oElse.type !== "edm:Null" && oThen.type !== oElse.type) {
Basics.error(oPathValue,
"Expected same type for second and third parameter, types are '" + oThen.type
+ "' and '" + oElse.type + "'");
}
return {
result : "expression",
type : sType,
value : Basics.resultToString(Expression.wrapExpression(oCondition), true, false)
+ "?" + Basics.resultToString(Expression.wrapExpression(oThen), true, bWithType)
+ ":" + Basics.resultToString(Expression.wrapExpression(oElse), true, bWithType)
};
},
/**
* Handling of "14.4 Constant Expressions", i.e.
* <ul>
* <li>"14.4.2 Expression edm:Bool",</li>
* <li>"14.4.3 Expression edm:Date",</li>
* <li>"14.4.4 Expression edm:DateTimeOffset",</li>
* <li>"14.4.5 Expression edm:Decimal",</li>
* <li>"14.4.8 Expression edm:Float",</li>
* <li>"14.4.9 Expression edm:Guid",</li>
* <li>"14.4.10 Expression edm:Int",</li>
* <li>"14.4.11 Expression edm:String",</li>
* <li>"14.4.12 Expression edm:TimeOfDay".</li>
* </ul>
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @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"
* @returns {object}
* the result object
*/
constant : function (oInterface, oPathValue, sEdmType) {
var sValue = oPathValue.value;
Basics.expectType(oPathValue, "string");
if (sEdmType === "String") {
if (rI18n.test(sValue)) { // a simple binding to "@i18n" model
return {
ignoreTypeInPath : true,
result : "binding",
type : "Edm.String",
value : sValue.slice(1, -1) // cut off "{" and "}"
};
} else if (oInterface.getSetting && oInterface.getSetting("bindTexts")) {
// We want a model binding to the path in the metamodel (which is
// oPathValue.path)
// "/##" is prepended because it leads from model to metamodel
return {
result : "binding",
type : "Edm.String",
ignoreTypeInPath : true,
value : "/##" + Expression.replaceIndexes(oInterface.getModel(),
oPathValue.path)
};
}
sEdmType = "Edm.String";
} else if (!mEdmType2RegExp[sEdmType].test(sValue)) {
Basics.error(oPathValue,
"Expected " + sEdmType + " value but instead saw '" + sValue + "'");
} else {
sEdmType = mType2Type[sEdmType];
if (sEdmType === "Edm.Int64"
&& ODataUtils.compare(sValue, sMinSafeInteger, true) >= 0
&& ODataUtils.compare(sValue, sMaxSafeInteger, true) <= 0) {
sEdmType = "Edm.Int32";
}
}
return {
result : "constant",
type : sEdmType,
value : sValue
};
},
/**
* Calculates an expression.
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* The callback interface related to the current formatter call
* @param {object} oPathValue
* Path and value information pointing to the parameters array (see Expression object)
* @returns {object|undefined}
* The result object or <code>undefined</code> in error cases
*/
expression : function (oInterface, oPathValue) {
var oRawValue = oPathValue.value,
oSubPathValue,
sType;
Basics.expectType(oPathValue, "object");
if (oRawValue.hasOwnProperty("Type")) {
sType = Basics.property(oPathValue, "Type", "string");
oSubPathValue = Basics.descend(oPathValue, "Value");
} else {
["And", "Apply", "Bool", "Date", "DateTimeOffset", "Decimal", "Float", "Eq", "Ge",
"Gt", "Guid", "If", "Int", "Le", "Lt", "Ne", "Not", "Null", "Or", "Path",
"PropertyPath", "String", "TimeOfDay"
].forEach(function (sProperty) {
if (oRawValue.hasOwnProperty(sProperty)) {
sType = sProperty;
oSubPathValue = Basics.descend(oPathValue, sProperty);
}
});
}
switch (sType) {
case "Apply": // 14.5.3 Expression edm:Apply
return Expression.apply(oInterface, oSubPathValue);
case "If": // 14.5.6 Expression edm:If
return Expression.conditional(oInterface, oSubPathValue);
case "Path": // 14.5.12 Expression edm:Path
case "PropertyPath": // 14.5.13 Expression edm:PropertyPath
return Expression.path(oInterface, oSubPathValue);
case "Bool": // 14.4.2 Expression edm:Bool
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 "Float": // 14.4.8 Expression edm:Float
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
return Expression.constant(oInterface, 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(oInterface, oSubPathValue, sType);
case "Not":
// 14.5.1 Comparison and Logical Operators
return Expression.not(oInterface, oSubPathValue);
case "Null":
// 14.5.10 Expression edm:Null
return {
result : "constant",
value : "null",
type : "edm:Null"
};
default:
Basics.error(oPathValue, "Unsupported OData expression");
return undefined;
}
},
/**
* Formats the result to be an operand for a logical or comparison operator. Handles
* constants accordingly.
*
* @param {object} oPathValue
* path and value information pointing to the parameters array (for a possible error
* message, see above)
* @param {number} iIndex
* the parameter index (for a possible error message)
* @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 (oPathValue, iIndex, oResult, bWrapExpression) {
var oDate;
if (oResult.result === "constant") {
switch (oResult.category) {
case "boolean":
case "number":
return oResult.value;
case "date":
oDate = Expression.parseDate(oResult.value);
if (!oDate) {
Basics.error(Basics.descend(oPathValue, iIndex),
"Invalid Date " + oResult.value);
}
return String(oDate.getTime());
case "datetime":
oDate = Expression.parseDateTimeOffset(oResult.value);
if (!oDate) {
Basics.error(Basics.descend(oPathValue, iIndex),
"Invalid DateTime " + oResult.value);
}
return String(oDate.getTime());
case "time":
return String(Expression.parseTimeOfDay(oResult.value).getTime());
// no default
}
}
if (bWrapExpression) {
Expression.wrapExpression(oResult);
}
return Basics.resultToString(oResult, true);
},
/**
* Calculates an expression. Ensures that errors that are thrown via {#error} while
* processing are handled accordingly.
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {object} oRawValue
* the raw value from the meta model
* @param {boolean} bWithType
* if <code>true</code>, embedded bindings contain type information
* @returns {string}
* the expression value or "Unsupported: oRawValue" in case of an error or
* <code>undefined</code> in case the raw value is undefined.
*/
getExpression : function (oInterface, oRawValue, bWithType) {
var oResult;
if (oRawValue === undefined) {
return undefined;
}
Measurement.average(sPerformanceGetExpression, "", aPerformanceCategories);
if ( !Expression.simpleParserWarningLogged &&
ManagedObject.bindingParser === BindingParser.simpleParser) {
Log.warning("Complex binding syntax not active", null, sAnnotationHelper);
Expression.simpleParserWarningLogged = true;
}
try {
oResult = Expression.expression(oInterface, {
asExpression : false,
path : oInterface.getPath(),
value : oRawValue,
withType : bWithType
});
Measurement.end(sPerformanceGetExpression);
return Basics.resultToString(oResult, false, bWithType);
} catch (e) {
Measurement.end(sPerformanceGetExpression);
if (e instanceof SyntaxError) {
return "Unsupported: "
+ BindingParser.complexParser.escape(Basics.toErrorString(oRawValue));
}
throw e;
}
},
/**
* Handling of "14.5.3.1.2 Function odata.fillUriTemplate".
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {object} oPathValue
* path and value information pointing to the parameters array (see Expression object)
* @returns {object}
* the result object
*/
fillUriTemplate : function (oInterface, oPathValue) {
var i,
sName,
aParts = [],
sPrefix = "",
oParameter,
aParameters = oPathValue.value,
oResult,
oTemplate = Expression.parameter(oInterface, oPathValue, 0, "Edm.String");
aParts.push('odata.fillUriTemplate(', Basics.resultToString(oTemplate, true), ',{');
for (i = 1; i < aParameters.length; i += 1) {
oParameter = Basics.descend(oPathValue, i, "object");
sName = Basics.property(oParameter, "Name", "string");
oResult = Expression.expression(oInterface, Basics.descend(oParameter, "Value"),
/*bExpression*/true);
aParts.push(sPrefix, Basics.toJSON(sName), ":",
Basics.resultToString(oResult, true));
sPrefix = ",";
}
aParts.push("})");
return {
result : "expression",
value : aParts.join(""),
type : "Edm.String"
};
},
/**
* Handling of "14.5.1 Comparison and Logical Operators": <code>edm:Not</code>.
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {object} oPathValue
* path and value information pointing to the parameter (see Expression object)
* @returns {object}
* the result object
*/
not : function (oInterface, oPathValue) {
var oParameter;
oPathValue.asExpression = true;
oParameter = Expression.expression(oInterface, oPathValue);
return {
result : "expression",
value : "!" + Basics.resultToString(Expression.wrapExpression(oParameter), true),
type : "Edm.Boolean"
};
},
/**
* Handling of "14.5.1 Comparison and Logical Operators" except <code>edm:Not</code>.
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @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 {object}
* the result object
*/
operator : function (oInterface, oPathValue, sType) {
var sExpectedEdmType = sType === "And" || sType === "Or" ? "Edm.Boolean" : undefined,
oParameter0 = Expression.parameter(oInterface, oPathValue, 0, sExpectedEdmType),
oParameter1 = Expression.parameter(oInterface, oPathValue, 1, sExpectedEdmType),
sTypeInfo,
bNeedsCompare,
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) {
Basics.error(oPathValue,
"Expected two comparable parameters but instead saw " + oParameter0.type
+ " and " + oParameter1.type);
}
sTypeInfo = oParameter0.category === "decimal" ? ",true" : "";
bNeedsCompare = mTypeCategoryNeedsCompare[oParameter0.category];
}
sValue0 = Expression.formatOperand(oPathValue, 0, oParameter0, !bNeedsCompare);
sValue1 = Expression.formatOperand(oPathValue, 1, oParameter1, !bNeedsCompare);
return {
result : "expression",
value : bNeedsCompare
? "odata.compare(" + sValue0 + "," + sValue1 + sTypeInfo + ")"
+ mOData2JSOperators[sType] + "0"
: sValue0 + mOData2JSOperators[sType] + sValue1,
type : "Edm.Boolean"
};
},
/**
* Evaluates a parameter and ensures that the result is of the given EDM type.
*
* The function calls <code>expression</code> with <code>bExpression=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 {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @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 {object}
* the result object
*/
parameter : function (oInterface, oPathValue, iIndex, sEdmType) {
var oParameter = Basics.descend(oPathValue, iIndex),
oResult;
oParameter.asExpression = true;
oResult = Expression.expression(oInterface, oParameter);
if (sEdmType && sEdmType !== oResult.type) {
Basics.error(oParameter,
"Expected " + sEdmType + " but instead saw " + oResult.type);
}
return oResult;
},
/**
* Parses an Edm.Date value and returns the corresponding JavaScript Date value.
*
* @param {string} sValue
* the Edm.Date value to parse
* @returns {Date}
* the JavaScript Date value or <code>null</code> in case the input could not be parsed
*/
parseDate : function (sValue) {
return oDateFormatter.parse(sValue);
},
/**
* Parses an Edm.DateTimeOffset value and returns the corresponding JavaScript Date value.
*
* @param {string} sValue
* the Edm.DateTimeOffset value to parse
* @returns {Date}
* the JavaScript Date value or <code>null</code> in case the input could not be parsed
*/
parseDateTimeOffset : function (sValue) {
var aMatches = mEdmType2RegExp.DateTimeOffset.exec(sValue);
if (aMatches && aMatches[1] && aMatches[1].length > 4) {
// "round" to millis, BEWARE of the dot!
sValue = sValue.replace(aMatches[1], aMatches[1].slice(0, 4));
}
return oDateTimeOffsetFormatter.parse(sValue.toUpperCase());
},
/**
* Parses an Edm.TimeOfDay value and returns the corresponding JavaScript Date value.
*
* @param {string} sValue
* the Edm.TimeOfDay value to parse
* @returns {Date}
* the JavaScript Date value or <code>null</code> in case the input could not be parsed
*/
parseTimeOfDay : function (sValue) {
if (sValue.length > 12) {
// "round" to millis: "HH:mm:ss.SSS"
sValue = sValue.slice(0, 12);
}
return oTimeFormatter.parse(sValue);
},
/**
* Handling of "14.5.12 Expression edm:Path" and "14.5.13 Expression edm:PropertyPath";
* embedded within an entity set or entity type (see {@link Basics.followPath}).
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {object} oPathValue
* path and value information pointing to the edm:Path (see Expression object)
* @returns {object}
* the result object
*/
path : function (oInterface, oPathValue) {
var sBindingPath = oPathValue.value,
oConstraints = {},
oExclusiveAnnotation,
oIsDigitSequence,
oMinMaxAnnotation,
oModel = oInterface.getModel(),
oPathValueInterface = {
getModel : function () {
return oModel;
},
getPath : function () {
return oPathValue.path;
}
},
oProperty,
oResult = {result : "binding", value : sBindingPath},
oTarget;
Basics.expectType(oPathValue, "string");
// Note: "PropertyPath" is treated the same...
oTarget = Basics.followPath(oPathValueInterface, {"Path" : sBindingPath});
if (oTarget && oTarget.resolvedPath) {
oProperty = oModel.getProperty(oTarget.resolvedPath);
oResult.type = oProperty.type;
switch (oProperty.type) {
case "Edm.DateTime":
oConstraints.displayFormat = oProperty["sap:display-format"];
break;
case "Edm.Decimal":
if (oProperty.precision) {
oConstraints.precision = oProperty.precision;
}
if (oProperty.scale) {
oConstraints.scale = oProperty.scale;
}
oMinMaxAnnotation = oProperty["Org.OData.Validation.V1.Minimum"];
if (oMinMaxAnnotation
&& (oMinMaxAnnotation.Decimal || oMinMaxAnnotation.String)) {
oConstraints.minimum =
oMinMaxAnnotation.Decimal || oMinMaxAnnotation.String;
oExclusiveAnnotation =
oMinMaxAnnotation["Org.OData.Validation.V1.Exclusive"];
if (oExclusiveAnnotation) {
oConstraints.minimumExclusive = oExclusiveAnnotation.Bool || "true";
}
}
oMinMaxAnnotation = oProperty["Org.OData.Validation.V1.Maximum"];
if (oMinMaxAnnotation
&& (oMinMaxAnnotation.Decimal || oMinMaxAnnotation.String)) {
oConstraints.maximum =
oMinMaxAnnotation.Decimal || oMinMaxAnnotation.String;
oExclusiveAnnotation =
oMinMaxAnnotation["Org.OData.Validation.V1.Exclusive"];
if (oExclusiveAnnotation) {
oConstraints.maximumExclusive = oExclusiveAnnotation.Bool || "true";
}
}
break;
case "Edm.String":
oConstraints.maxLength = oProperty.maxLength;
oIsDigitSequence =
oProperty["com.sap.vocabularies.Common.v1.IsDigitSequence"];
if (oIsDigitSequence) {
oConstraints.isDigitSequence = oIsDigitSequence.Bool || "true";
}
break;
// no default
}
if (oProperty.nullable === "false") {
oConstraints.nullable = "false";
}
oResult.constraints = oConstraints;
} else {
Log.warning("Could not find property '" + sBindingPath + "' starting from '"
+ oPathValue.path + "'", null, sAnnotationHelper);
}
return oResult;
},
/**
* Replaces the indexes in the given path by queries in the form
* <code>[${key}==='value']</code> if possible. Expects the path to start with
* "/dataServices/schema/<i>/".
*
* @param {sap.ui.model.Model} oModel
* the model the path belongs to
* @param {string} sPath
* the path, where to replace the indexes
* @returns {string}
* the replaced path
*/
replaceIndexes : function (oModel, sPath) {
var aMatches,
aParts = sPath.split('/'),
sObjectPath,
sRecordType;
/**
* Processes the property with the given path of the object at <code>sObjectPath</code>.
* If it exists and is of type "string", the index at position <code>i</code> in
* <code>aParts</code> is replaced by a query "[${propertyPath}==='propertyValue']".
*
* @param {string} sPropertyPath the property path
* @param {number} i the index in aParts
* @returns {boolean} true if the index was replaced by a query
*/
function processProperty(sPropertyPath, i) {
var sProperty = oModel.getProperty(sObjectPath + "/" + sPropertyPath);
if (typeof sProperty === "string") {
aParts[i] = "[${" + sPropertyPath + "}===" + Basics.toJSON(sProperty) + "]";
return true;
}
return false;
}
aMatches = rSchemaPath.exec(sPath);
if (!aMatches) {
return sPath;
}
sObjectPath = aMatches[1];
// aParts now contains ["", "dataServices", "schema", "<i>", ...]
// try to replace the schema index in aParts[3] by a query for the schema's namespace
if (!processProperty("namespace", 3)) {
return sPath;
}
// continue after the schema index
for (var i = 4; i < aParts.length; i += 1) {
sObjectPath = sObjectPath + "/" + aParts[i];
// if there is an index, first try a query for "name"
if (rInteger.test(aParts[i]) && !processProperty("name", i)) {
// check data fields: since they always extend DataFieldAbstract, the record
// type must be given
sRecordType = oModel.getProperty(sObjectPath + "/RecordType");
if (sRecordType) {
if (sRecordType === "com.sap.vocabularies.UI.v1.DataFieldForAction") {
processProperty("Action/String", i);
} else if (sRecordType ===
"com.sap.vocabularies.UI.v1.DataFieldForAnnotation") {
processProperty("Target/AnnotationPath", i);
} else if (sRecordType.indexOf("com.sap.vocabularies.UI.v1.DataField")
=== 0) {
processProperty("Value/Path", i);
}
}
}
}
return aParts.join('/');
},
/**
* Flag indicating that warning for missing complex binding parser has already been logged.
*
* @type {boolean}
*/
simpleParserWarningLogged : false,
/**
* Handling of "14.5.3.1.3 Function odata.uriEncode".
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {object} oPathValue
* path and value information pointing to the parameters array (see Expression object)
* @returns {object}
* the result object
*/
uriEncode : function (oInterface, oPathValue) {
var oResult = Expression.parameter(oInterface, oPathValue, 0);
if (oResult.result === "constant") {
// convert V4 to V2 for sap.ui.model.odata.ODataUtils
if (oResult.type === "Edm.Date") {
oResult.type = "Edm.DateTime";
// Note: ODataUtils.formatValue calls Date.parse() indirectly, make sure the
// value is interpreted in UTC
oResult.value = oResult.value + "T00:00:00Z";
} else if (oResult.type === "Edm.TimeOfDay") {
oResult.type = "Edm.Time";
oResult.value = "PT"
+ oResult.value.slice(0, 2) + "H"
+ oResult.value.slice(3, 5) + "M"
+ oResult.value.slice(6, 8) + "S";
}
}
return {
result : "expression",
value : 'odata.uriEncode(' + Basics.resultToString(oResult, true) + ","
+ Basics.toJSON(oResult.type) + ")",
type : "Edm.String"
};
},
/**
* 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;
}
};
Expression._setDateTimeFormatter();
return Expression;
});