UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

950 lines (895 loc) 34 kB
/*! * 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; });