UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

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