UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,287 lines (1,146 loc) 44.9 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 */ // Provides class sap.ui.model.odata.ODataAnnotations sap.ui.define([ "sap/base/assert", "sap/base/Log", "sap/base/util/isEmptyObject" ], function (assert, Log, isEmptyObject) { "use strict"; /* * Include list of property node names whose values should be put through alias replacement */ var mAliasNodeIncludeList = { EnumMember: true, Path: true, PropertyPath: true, NavigationPropertyPath: true, AnnotationPath: true }; var mTextNodeIncludeList = { Binary: true, Bool: true, Date: true, DateTimeOffset: true, Decimal: true, Duration: true, Float: true, Guid: true, Int: true, String: true, TimeOfDay: true, LabelElementReference: true, EnumMember: true, Path: true, PropertyPath: true, NavigationPropertyPath: true, AnnotationPath: true }; var mMultipleArgumentDynamicExpressions = { And: true, Or: true, // Not: true, Eq: true, Ne: true, Gt: true, Ge: true, Lt: true, Le: true, If: true, Collection: true }; /** * Gets the qualifier for an <Annotation> element, possibly inherited from the direct parent * <Annotations> element. * * @param {Element} oAnnotationNode An <Annotation> element * @returns {string} The qualifier or some falsy value */ function getQualifier(oAnnotationNode) { return oAnnotationNode.getAttribute("Qualifier") || oAnnotationNode.parentNode.nodeName === "Annotations" && oAnnotationNode.parentNode.getAttribute("Qualifier"); } /** * Static class for annotations parsing in the ODataModel (version 1 and 2 only). * * This class should not be used outside the annotations loaders. * * @static * @protected */ var AnnotationParser = { /** * Merges the given parsed annotation map into the given target annotation map. * * @param {Object<string,Object>} mTargetAnnotations The target annotation map into which the source annotations should be merged * @param {Object<string,Object>} mSourceAnnotations The source annotation map that should be merged into the target annotation map * @static * @protected */ merge: function(mTargetAnnotations, mSourceAnnotations) { // Merge must be done on Term level, this is why the original line does not suffice any more: // jQuery.extend(true, this.oAnnotations, mAnnotations); // Terms are defined on different levels, the main one is below the target level, which is directly // added as property to the annotations object and then in the same way inside two special properties // named "propertyAnnotations" and "EntityContainer" var sTarget, sTerm; var aSpecialCases = ["annotationsAtArrays", "propertyAnnotations", "EntityContainer", "annotationReferences"]; // First merge standard annotations for (sTarget in mSourceAnnotations) { if (aSpecialCases.indexOf(sTarget) !== -1) { // Skip these as they are special properties that contain Target level definitions continue; } // ...all others contain Term level definitions AnnotationParser._mergeAnnotation(sTarget, mSourceAnnotations, mTargetAnnotations); } // Now merge special cases except "annotationsAtArrays" for (var i = 1; i < aSpecialCases.length; ++i) { var sSpecialCase = aSpecialCases[i]; mTargetAnnotations[sSpecialCase] = mTargetAnnotations[sSpecialCase] || {}; // Make sure the target namespace exists for (sTarget in mSourceAnnotations[sSpecialCase]) { for (sTerm in mSourceAnnotations[sSpecialCase][sTarget]) { // Now merge every term mTargetAnnotations[sSpecialCase][sTarget] = mTargetAnnotations[sSpecialCase][sTarget] || {}; AnnotationParser._mergeAnnotation(sTerm, mSourceAnnotations[sSpecialCase][sTarget], mTargetAnnotations[sSpecialCase][sTarget]); } } } if (mSourceAnnotations.annotationsAtArrays) { mTargetAnnotations.annotationsAtArrays = (mTargetAnnotations.annotationsAtArrays || []) .concat(mSourceAnnotations.annotationsAtArrays); } }, /* * @static * @private */ _mergeAnnotation: function(sName, mAnnotations, mTarget) { // Everything in here must be on Term level, so we overwrite the target with the data from the source if (Array.isArray(mAnnotations[sName])) { // This is a collection - make sure it stays one mTarget[sName] = mAnnotations[sName].slice(0); } else { // Make sure the map exists in the target mTarget[sName] = mTarget[sName] || {}; for (var sKey in mAnnotations[sName]) { mTarget[sName][sKey] = mAnnotations[sName][sKey]; } } }, /** * Parses the given XML-document using the given ODataMetadata-object and returns a native JavaScript-object * representation of it. * * This method should only be used by the ODataAnnotation-loaders. * * @param {sap.ui.model.odata.ODataMetadata} oMetadata The metadata to be used for interpreting the annotation document * @param {document} oXMLDoc The annotation document * @param {string} [sSourceUrl="metadata document"] The source URL * @returns {object} The parsed annotation object * @static * @protected */ parse: function(oMetadata, oXMLDoc, sSourceUrl) { try { AnnotationParser._parserData = {}; AnnotationParser._oXPath = AnnotationParser.getXPath(); return AnnotationParser._parse(oMetadata, oXMLDoc, sSourceUrl); } finally { delete AnnotationParser._parserData; delete AnnotationParser._oXPath; } }, /** * Parses the given XML-document using the given ODataMetadata-object and returns a native JavaScript-object * representation of it. * * @param {sap.ui.model.odata.ODataMetadata} oMetadata The metadata to be used for interpreting the annotation document * @param {document} oXMLDoc The annotation document * @param {string} [sSourceUrl="metadata document"] The source URL * @returns {object} The parsed annotation object * @static * @private */ _parse: function(oMetadata, oXMLDoc, sSourceUrl) { var mappingList = {}, schemaNodes, schemaNode, termNodes, oTerms, termNode, sTermType, annotationNodes, annotationNode, annotationTarget, annotationNamespace, annotation, propertyAnnotation, propertyAnnotationNodes, propertyAnnotationNode, sTermValue, targetAnnotation, expandNodes, expandNode, path, pathValues, expandNodesApplFunc, i, nodeIndex, annotationsAtArrays = []; AnnotationParser._parserData.metadataInstance = oMetadata; AnnotationParser._parserData.serviceMetadata = oMetadata.getServiceMetadata(); AnnotationParser._parserData.xmlDocument = AnnotationParser._oXPath.setNameSpace(oXMLDoc); AnnotationParser._parserData.schema = {}; AnnotationParser._parserData.aliases = {}; AnnotationParser._parserData.url = sSourceUrl ? sSourceUrl : "metadata document"; AnnotationParser._parserData.annotationsAtArrays = annotationsAtArrays; // Schema Alias schemaNodes = AnnotationParser._oXPath.selectNodes("//d:Schema", AnnotationParser._parserData.xmlDocument); for (i = 0; i < schemaNodes.length; i += 1) { schemaNode = AnnotationParser._oXPath.nextNode(schemaNodes, i); AnnotationParser._parserData.schema.Alias = schemaNode.getAttribute("Alias"); AnnotationParser._parserData.schema.Namespace = schemaNode.getAttribute("Namespace"); } // Fill local alias and reference objects var oAnnotationReferences = {}; var bFoundReferences = AnnotationParser._parseReferences(oAnnotationReferences); if (bFoundReferences) { mappingList.annotationReferences = oAnnotationReferences; mappingList.aliasDefinitions = AnnotationParser._parserData.aliases; } // Term nodes termNodes = AnnotationParser._oXPath.selectNodes("//d:Term", AnnotationParser._parserData.xmlDocument); if (termNodes.length > 0) { oTerms = {}; for (nodeIndex = 0; nodeIndex < termNodes.length; nodeIndex += 1) { termNode = AnnotationParser._oXPath.nextNode(termNodes, nodeIndex); sTermType = AnnotationParser.replaceWithAlias(termNode.getAttribute("Type")); oTerms["@" + AnnotationParser._parserData.schema.Alias + "." + termNode.getAttribute("Name")] = sTermType; } mappingList.termDefinitions = oTerms; } // Metadata information of all properties AnnotationParser._parserData.metadataProperties = AnnotationParser.getAllPropertiesMetadata(AnnotationParser._parserData.serviceMetadata); if (AnnotationParser._parserData.metadataProperties.extensions) { mappingList.propertyExtensions = AnnotationParser._parserData.metadataProperties.extensions; } // Annotations annotationNodes = AnnotationParser._oXPath.selectNodes("//d:Annotations ", AnnotationParser._parserData.xmlDocument); for (nodeIndex = 0; nodeIndex < annotationNodes.length; nodeIndex += 1) { annotationNode = AnnotationParser._oXPath.nextNode(annotationNodes, nodeIndex); if (annotationNode.hasChildNodes() === false) { continue; } annotationTarget = annotationNode.getAttribute("Target"); annotationNamespace = annotationTarget.split(".")[0]; if (annotationNamespace && AnnotationParser._parserData.aliases[annotationNamespace]) { annotationTarget = annotationTarget.replace(new RegExp(annotationNamespace, ""), AnnotationParser._parserData.aliases[annotationNamespace]); } annotation = annotationTarget; propertyAnnotation = null; var sContainerAnnotation = null; if (annotationTarget.indexOf("/") > 0) { annotation = annotationTarget.split("/")[0]; // check sAnnotation is EntityContainer: if yes, something in there is annotated - EntitySet, FunctionImport, .. var bSchemaExists = AnnotationParser._parserData.serviceMetadata.dataServices && AnnotationParser._parserData.serviceMetadata.dataServices.schema && AnnotationParser._parserData.serviceMetadata.dataServices.schema.length; if (bSchemaExists) { for (var j = AnnotationParser._parserData.serviceMetadata.dataServices.schema.length - 1; j >= 0; j--) { var oMetadataSchema = AnnotationParser._parserData.serviceMetadata.dataServices.schema[j]; if (oMetadataSchema.entityContainer) { var aAnnotation = annotation.split('.'); for (var k = oMetadataSchema.entityContainer.length - 1; k >= 0; k--) { if (oMetadataSchema.entityContainer[k].name === aAnnotation[aAnnotation.length - 1] ) { sContainerAnnotation = annotationTarget.replace(annotation + "/", ""); break; } } } } } //else - it's a property annotation if (!sContainerAnnotation) { propertyAnnotation = annotationTarget.replace(annotation + "/", ""); } } // --- Value annotation of complex types. --- if (propertyAnnotation) { if (!mappingList.propertyAnnotations) { mappingList.propertyAnnotations = {}; } if (!mappingList.propertyAnnotations[annotation]) { mappingList.propertyAnnotations[annotation] = {}; } if (!mappingList.propertyAnnotations[annotation][propertyAnnotation]) { mappingList.propertyAnnotations[annotation][propertyAnnotation] = {}; } propertyAnnotationNodes = AnnotationParser._oXPath.selectNodes("./d:Annotation", annotationNode); for (var nodeIndexValue = 0; nodeIndexValue < propertyAnnotationNodes.length; nodeIndexValue += 1) { propertyAnnotationNode = AnnotationParser._oXPath.nextNode(propertyAnnotationNodes, nodeIndexValue); sTermValue = AnnotationParser.replaceWithAlias(propertyAnnotationNode.getAttribute("Term")); var sQualifierValue = getQualifier(propertyAnnotationNode); if (sQualifierValue) { sTermValue += "#" + sQualifierValue; } if (propertyAnnotationNode.hasChildNodes() === false) { var o = {}; AnnotationParser.enrichFromPropertyValueAttributes(o, propertyAnnotationNode); if (isEmptyObject(o)) { // assume DefaultValue="true" for annotation term w/o reading vocabulary o.Bool = "true"; } mappingList.propertyAnnotations[annotation][propertyAnnotation][sTermValue] = o; } else { mappingList.propertyAnnotations[annotation][propertyAnnotation][sTermValue] = AnnotationParser.getPropertyValue(propertyAnnotationNode); } } // --- Annotations --- } else { var mTarget; if (sContainerAnnotation) { // Target is an entity container if (!mappingList["EntityContainer"]) { mappingList["EntityContainer"] = {}; } if (!mappingList["EntityContainer"][annotation]) { mappingList["EntityContainer"][annotation] = {}; } mTarget = mappingList["EntityContainer"][annotation]; } else { if (!mappingList[annotation]) { mappingList[annotation] = {}; } mTarget = mappingList[annotation]; } targetAnnotation = annotation.replace(AnnotationParser._parserData.aliases[annotationNamespace], annotationNamespace); propertyAnnotationNodes = AnnotationParser._oXPath.selectNodes("./d:Annotation", annotationNode); for (var nodeIndexAnnotation = 0; nodeIndexAnnotation < propertyAnnotationNodes.length; nodeIndexAnnotation += 1) { propertyAnnotationNode = AnnotationParser._oXPath.nextNode(propertyAnnotationNodes, nodeIndexAnnotation); var oAnnotationTarget = mTarget; if (sContainerAnnotation) { if (!mTarget[sContainerAnnotation]) { mTarget[sContainerAnnotation] = {}; } oAnnotationTarget = mTarget[sContainerAnnotation]; } AnnotationParser._parseAnnotation(annotation, propertyAnnotationNode, oAnnotationTarget); } // --- Setup of Expand nodes. --- expandNodes = AnnotationParser._oXPath.selectNodes("//d:Annotations[contains(@Target, '" + targetAnnotation + "')]//d:PropertyValue[contains(@Path, '/')]//@Path", AnnotationParser._parserData.xmlDocument); for (i = 0; i < expandNodes.length; i += 1) { expandNode = AnnotationParser._oXPath.nextNode(expandNodes, i); path = expandNode.value; if (mappingList.propertyAnnotations) { if (mappingList.propertyAnnotations[annotation]) { if (mappingList.propertyAnnotations[annotation][path]) { continue; } } } pathValues = path.split('/'); if (AnnotationParser.findNavProperty(annotation, pathValues[0])) { if (!mappingList.expand) { mappingList.expand = {}; } if (!mappingList.expand[annotation]) { mappingList.expand[annotation] = {}; } mappingList.expand[annotation][pathValues[0]] = pathValues[0]; } } expandNodesApplFunc = AnnotationParser._oXPath.selectNodes("//d:Annotations[contains(@Target, '" + targetAnnotation + "')]//d:Path[contains(., '/')]", AnnotationParser._parserData.xmlDocument); for (i = 0; i < expandNodesApplFunc.length; i += 1) { expandNode = AnnotationParser._oXPath.nextNode(expandNodesApplFunc, i); path = AnnotationParser._oXPath.getNodeText(expandNode); if ( mappingList.propertyAnnotations && mappingList.propertyAnnotations[annotation] && mappingList.propertyAnnotations[annotation][path] ) { continue; } if (!mappingList.expand) { mappingList.expand = {}; } if (!mappingList.expand[annotation]) { mappingList.expand[annotation] = {}; } pathValues = path.split('/'); if (AnnotationParser.findNavProperty(annotation, pathValues[0])) { if (!mappingList.expand) { mappingList.expand = {}; } if (!mappingList.expand[annotation]) { mappingList.expand[annotation] = {}; } mappingList.expand[annotation][pathValues[0]] = pathValues[0]; } } } } if (annotationsAtArrays.length) { // A list of paths (as array of strings or numbers) to annotations at arrays mappingList.annotationsAtArrays = annotationsAtArrays.map( function (oAnnotationNode) { return AnnotationParser.backupAnnotationAtArray(oAnnotationNode, mappingList); }); } return mappingList; }, /* * Backs up the annotation corresponding to the given element by adding it also as a sibling of * the array under the name of "Property@Term#Qualifier". This way, it will survive * (de-)serialization via JSON.stringify and JSON.parse. Returns a path which points to it. * * @param {Element} oElement * The <Annotation> element of an annotation inside an array * @param {object} mAnnotations * The annotation map * @returns {string[]} * Path of string or number segments pointing to annotation at array * * @private * @see AnnotationParser.restoreAnnotationsAtArrays * @see AnnotationParser.syncAnnotationsAtArrays * @static */ backupAnnotationAtArray : function (oElement, mAnnotations) { var sQualifier, aSegments = []; // returns the current oElement's index in its parentElement's "children" collection function index() { return Array.prototype.filter.call(oElement.parentNode.childNodes, function (oNode) { return oNode.nodeType === 1; }).indexOf(oElement); } while (oElement.nodeName !== "Annotations") { switch (oElement.nodeName) { case "Annotation": sQualifier = getQualifier(oElement); aSegments.unshift(oElement.getAttribute("Term") + (sQualifier ? "#" + sQualifier : "")); break; case "Collection": break; case "PropertyValue": aSegments.unshift(oElement.getAttribute("Property")); break; case "Record": if (oElement.parentNode.nodeName === "Collection") { aSegments.unshift(index()); } break; default: if (oElement.parentNode.nodeName === "Apply") { aSegments.unshift("Value"); aSegments.unshift(index()); aSegments.unshift("Parameters"); } else { aSegments.unshift(oElement.nodeName); } break; } oElement = oElement.parentNode; } aSegments.unshift(oElement.getAttribute("Target")); aSegments = aSegments.map(function (vSegment) { return typeof vSegment === "string" ? AnnotationParser.replaceWithAlias(vSegment) : vSegment; }); AnnotationParser.syncAnnotationsAtArrays(mAnnotations, aSegments, true); return aSegments; }, /** * Restores annotations at arrays which might have been lost due to serialization. * * @param {object} mAnnotations - The annotation map * * @protected * @see AnnotationParser.backupAnnotationAtArray * @see AnnotationParser.syncAnnotationsAtArrays * @static */ restoreAnnotationsAtArrays: function (mAnnotations) { if (mAnnotations.annotationsAtArrays) { mAnnotations.annotationsAtArrays.forEach(function (aSegments) { AnnotationParser.syncAnnotationsAtArrays(mAnnotations, aSegments); }); } }, /* * Sync annotations at arrays and their siblings. * * @param {object} mAnnotations * The annotation map * @param {string[]} aSegments * Path of string or number segments pointing to annotation at array * @param {boolean} bWarn * Whether warnings are allowed * * @private * @see AnnotationParser.backupAnnotationAtArray * @see AnnotationParser.restoreAnnotationsAtArrays */ syncAnnotationsAtArrays: function (mAnnotations, aSegments, bWarn) { var i, n = aSegments.length - 2, sAnnotation = aSegments[n + 1], oParent = mAnnotations, sProperty = aSegments[n], sSiblingName = sProperty + "@" + sAnnotation; for (i = 0; i < n; i += 1) { oParent = oParent && oParent[aSegments[i]]; } if (oParent && Array.isArray(oParent[sProperty])) { if (!(sSiblingName in oParent)) { oParent[sSiblingName] = oParent[sProperty][sAnnotation]; } if (!(sAnnotation in oParent[sProperty])) { oParent[sProperty][sAnnotation] = oParent[sSiblingName]; } } else if (bWarn) { Log.warning("Wrong path to annotation at array", aSegments, "sap.ui.model.odata.AnnotationParser"); } }, /* * Sets the parsed annotation term at the given oAnnotationTarget object. * * @param {string} sAnnotationTarget * The target path used to determine EDM types * @param {Element} oAnnotationNode * The <Annotation> node to parse * @param {object|object[]} oAnnotationTarget * The target at which the annotation value is added * * @static * @private */ _parseAnnotation: function (sAnnotationTarget, oAnnotationNode, oAnnotationTarget) { var sQualifier = getQualifier(oAnnotationNode); var sTerm = AnnotationParser.replaceWithAlias(oAnnotationNode.getAttribute("Term")); if (sQualifier) { sTerm += "#" + sQualifier; } var vValue = AnnotationParser.getPropertyValue(oAnnotationNode, sAnnotationTarget); vValue = AnnotationParser.setEdmTypes(vValue, AnnotationParser._parserData.metadataProperties.types, sAnnotationTarget, AnnotationParser._parserData.schema); oAnnotationTarget[sTerm] = vValue; if (Array.isArray(oAnnotationTarget)) { AnnotationParser._parserData.annotationsAtArrays.push(oAnnotationNode); } }, /* * Parses the alias definitions of the annotation document and fills the internal oAlias object. * * @param {map} mAnnotationReferences - The annotation reference object (output) * @return {boolean} Whether references where found in the XML document * @static * @private */ _parseReferences: function(mAnnotationReferences) { var bFound = false; var oNode, i; var xPath = AnnotationParser._oXPath; var sAliasSelector = "//edmx:Reference/edmx:Include[@Namespace and @Alias]"; var oAliasNodes = xPath.selectNodes(sAliasSelector, AnnotationParser._parserData.xmlDocument); for (i = 0; i < oAliasNodes.length; ++i) { bFound = true; oNode = xPath.nextNode(oAliasNodes, i); AnnotationParser._parserData.aliases[oNode.getAttribute("Alias")] = oNode.getAttribute("Namespace"); } // order the aliases by length to ensure that aliases are properly resolved even if there is // an alias which is an infix of another alias AnnotationParser._parserData.aliasesByLength = Object.keys(AnnotationParser._parserData.aliases) .sort(function (sAlias0, sAlias1) { return sAlias1.length - sAlias0.length; }); var sReferenceSelector = "//edmx:Reference[@Uri]/edmx:IncludeAnnotations[@TermNamespace]"; var oReferenceNodes = xPath.selectNodes(sReferenceSelector, AnnotationParser._parserData.xmlDocument); for (i = 0; i < oReferenceNodes.length; ++i) { bFound = true; oNode = xPath.nextNode(oReferenceNodes, i); var sTermNamespace = oNode.getAttribute("TermNamespace"); var sTargetNamespace = oNode.getAttribute("TargetNamespace"); var sReferenceUri = oNode.parentNode.getAttribute("Uri"); if (sTargetNamespace) { if (!mAnnotationReferences[sTargetNamespace]) { mAnnotationReferences[sTargetNamespace] = {}; } mAnnotationReferences[sTargetNamespace][sTermNamespace] = sReferenceUri; } else { mAnnotationReferences[sTermNamespace] = sReferenceUri; } } return bFound; }, /* * @static * @private */ getAllPropertiesMetadata: function(oMetadata) { var oMetadataSchema = {}, oPropertyTypes = {}, oPropertyExtensions = {}, bPropertyExtensions = false, sNamespace, aEntityTypes, aComplexTypes, oEntityType = {}, oProperties = {}, oExtensions = {}, bExtensions = false, oProperty, oComplexTypeProp, sPropertyName, sType, oPropExtension, oReturn = { types : oPropertyTypes }; if (!oMetadata.dataServices.schema) { return oReturn; } for (var i = oMetadata.dataServices.schema.length - 1; i >= 0; i -= 1) { oMetadataSchema = oMetadata.dataServices.schema[i]; if (oMetadataSchema.entityType) { sNamespace = oMetadataSchema.namespace; aEntityTypes = oMetadataSchema.entityType; aComplexTypes = oMetadataSchema.complexType; for (var j = 0; j < aEntityTypes.length; j += 1) { oEntityType = aEntityTypes[j]; oExtensions = {}; oProperties = {}; if (oEntityType.property) { for (var k = 0; k < oEntityType.property.length; k += 1) { oProperty = oEntityType.property[k]; if (oProperty.type.substring(0, sNamespace.length) === sNamespace) { if (aComplexTypes) { for (var l = 0; l < aComplexTypes.length; l += 1) { if (aComplexTypes[l].name === oProperty.type.substring(sNamespace.length + 1)) { if (aComplexTypes[l].property) { for (var m = 0; m < aComplexTypes[l].property.length; m += 1) { oComplexTypeProp = aComplexTypes[l].property[m]; oProperties[aComplexTypes[l].name + "/" + oComplexTypeProp.name] = oComplexTypeProp.type; } } } } } } else { sPropertyName = oProperty.name; sType = oProperty.type; if (oProperty.extensions) { for (var p = 0; p < oProperty.extensions.length; p += 1) { oPropExtension = oProperty.extensions[p]; if ((oPropExtension.name === "display-format") && (oPropExtension.value === "Date")) { sType = "Edm.Date"; } else { bExtensions = true; if (!oExtensions[sPropertyName]) { oExtensions[sPropertyName] = {}; } if (oPropExtension.namespace && !oExtensions[sPropertyName][oPropExtension.namespace]) { oExtensions[sPropertyName][oPropExtension.namespace] = {}; } oExtensions[sPropertyName][oPropExtension.namespace][oPropExtension.name] = oPropExtension.value; } } } oProperties[sPropertyName] = sType; } } } if (!oPropertyTypes[sNamespace + "." + oEntityType.name]) { oPropertyTypes[sNamespace + "." + oEntityType.name] = {}; } oPropertyTypes[sNamespace + "." + oEntityType.name] = oProperties; if (bExtensions) { if (!oPropertyExtensions[sNamespace + "." + oEntityType.name]) { bPropertyExtensions = true; } oPropertyExtensions[sNamespace + "." + oEntityType.name] = {}; oPropertyExtensions[sNamespace + "." + oEntityType.name] = oExtensions; } } } } if (bPropertyExtensions) { oReturn = { types : oPropertyTypes, extensions : oPropertyExtensions }; } return oReturn; }, /* * @static * @private */ setEdmTypes: function(vPropertyValues, oProperties, sTarget, oSchema) { function setEdmType(vValueIndex) { var oPropertyValue, sEdmType = ''; if (vPropertyValues[vValueIndex]) { oPropertyValue = vPropertyValues[vValueIndex]; if (oPropertyValue.Value && oPropertyValue.Value.Path) { sEdmType = AnnotationParser.getEdmType(oPropertyValue.Value.Path, oProperties, sTarget, oSchema); if (sEdmType) { vPropertyValues[vValueIndex].EdmType = sEdmType; } } else if (oPropertyValue.Path) { sEdmType = AnnotationParser.getEdmType(oPropertyValue.Path, oProperties, sTarget, oSchema); if (sEdmType) { vPropertyValues[vValueIndex].EdmType = sEdmType; } } else if (oPropertyValue.Facets) { vPropertyValues[vValueIndex].Facets = AnnotationParser.setEdmTypes(oPropertyValue.Facets, oProperties, sTarget, oSchema); } else if (oPropertyValue.Data) { vPropertyValues[vValueIndex].Data = AnnotationParser.setEdmTypes(oPropertyValue.Data, oProperties, sTarget, oSchema); } else if (vValueIndex === "Data") { vPropertyValues.Data = AnnotationParser.setEdmTypes(oPropertyValue, oProperties, sTarget, oSchema); } else if (oPropertyValue.Value && oPropertyValue.Value.Apply) { vPropertyValues[vValueIndex].Value.Apply.Parameters = AnnotationParser.setEdmTypes(oPropertyValue.Value.Apply.Parameters, oProperties, sTarget, oSchema); } else if (oPropertyValue.Value && oPropertyValue.Type && (oPropertyValue.Type === "Path")) { sEdmType = AnnotationParser.getEdmType(oPropertyValue.Value, oProperties, sTarget, oSchema); if (sEdmType) { vPropertyValues[vValueIndex].EdmType = sEdmType; } } } } if (Array.isArray(vPropertyValues)) { for (var iValueIndex = 0; iValueIndex < vPropertyValues.length; iValueIndex += 1) { setEdmType(iValueIndex); } } else { for (var sValueIndex in vPropertyValues) { setEdmType(sValueIndex); } } return vPropertyValues; }, /* * @static * @private */ getEdmType: function(sPath, oProperties, sTarget, oSchema) { var iPos = sPath.indexOf("/"); if (iPos > -1) { var sPropertyName = sPath.substr(0, iPos); var mNavProperty = AnnotationParser.findNavProperty(sTarget, sPropertyName); if (mNavProperty) { var mToEntityType = AnnotationParser._parserData.metadataInstance._getEntityTypeByNavPropertyObject(mNavProperty); if (mToEntityType) { sTarget = mToEntityType.entityType; sPath = sPath.substr(iPos + 1); } } } if ((sPath.charAt(0) === "@") && (sPath.indexOf(oSchema.Alias) === 1)) { sPath = sPath.slice(oSchema.Alias.length + 2); } if (sPath.indexOf("/") >= 0) { if (oProperties[sPath.slice(0, sPath.indexOf("/"))]) { sTarget = sPath.slice(0, sPath.indexOf("/")); sPath = sPath.slice(sPath.indexOf("/") + 1); } } return oProperties[sTarget] && oProperties[sTarget][sPath]; }, /* * Returns a map of key value pairs corresponding to the attributes of the given Node - * attributes named "Property", "Term" and "Qualifier" are ignored. * * @param {map} mAttributes - A map that may already contain attributes, this map will be filled and returned by this method * @param {Node} oNode - The node with the attributes * @return {map} A map containing the attributes as key/value pairs * @static * @private */ enrichFromPropertyValueAttributes: function(mAttributes, oNode) { var mIgnoredAttributes = { "Property" : true, "Qualifier": true, "Term": true, "xmlns" : true }; for (var i = 0; i < oNode.attributes.length; i += 1) { var sName = oNode.attributes[i].name; if (!mIgnoredAttributes[sName] && (sName.indexOf("xmlns:") !== 0)) { var sValue = oNode.attributes[i].value; // Special case: EnumMember can contain a space separated list of properties that must all have their // aliases replaced if (sName === "EnumMember" && sValue.indexOf(" ") > -1) { var aValues = sValue.split(" "); mAttributes[sName] = aValues.map(AnnotationParser.replaceWithAlias).join(" "); } else { mAttributes[sName] = AnnotationParser.replaceWithAlias(sValue); } } } return mAttributes; }, /* * Returns a property value object for the given nodes * * @param {XPathResult} oNodeList - As many nodes as should be checked for Record values * @return {object|object[]} The extracted values * @static * @private */ _getRecordValues: function(oNodeList) { var aNodeValues = []; var xPath = AnnotationParser._oXPath; for (var i = 0; i < oNodeList.length; ++i) { var oNode = xPath.nextNode(oNodeList, i); var vNodeValue = AnnotationParser.getPropertyValues(oNode); var sType = oNode.getAttribute("Type"); if (sType) { vNodeValue["RecordType"] = AnnotationParser.replaceWithAlias(sType); } aNodeValues.push(vNodeValue); } return aNodeValues; }, /* * Extracts the text value from all nodes in the given NodeList and puts them into an array * * @param {XPathResult} oNodeList - As many nodes as should be checked for Record values * @return {object[]} Array of values * @static * @private */ _getTextValues: function(oNodeList) { var aNodeValues = []; var xPath = AnnotationParser._oXPath; for (var i = 0; i < oNodeList.length; i += 1) { var oNode = xPath.nextNode(oNodeList, i); var oValue = {}; var sText = xPath.getNodeText(oNode); oValue[oNode.nodeName] = AnnotationParser._parserData.aliases ? AnnotationParser.replaceWithAlias(sText) : sText; aNodeValues.push(oValue); } return aNodeValues; }, /* * Returns the text value of a given node and does an alias replacement if neccessary. * * @param {Node} oNode - The Node of which the text value should be determined * @return {string} The text content * @static * @private */ _getTextValue: function(oNode) { var xPath = AnnotationParser._oXPath; var sValue = ""; if (oNode.nodeName in mAliasNodeIncludeList) { sValue = AnnotationParser.replaceWithAlias(xPath.getNodeText(oNode)); } else { sValue = xPath.getNodeText(oNode); } if (oNode.nodeName !== "String") { // Trim whitespace if it's not specified as string value sValue = sValue.trim(); } return sValue; }, /** * Determines the annotation value (static or dynamic expression) at the given element. * * @param {Element} oDocumentNode * The element * @param {string} [sAnnotationTarget] * The target path used to determine EDM types * @returns {object|object[]} * The annotation value * * @static * @private */ getPropertyValue: function(oDocumentNode, sAnnotationTarget) { var i; var xPath = AnnotationParser._oXPath; var vPropertyValue = oDocumentNode.nodeName === "Collection" ? [] : {}; if (oDocumentNode.hasChildNodes()) { // This is a complex value, check for child values var oRecordNodeList = xPath.selectNodes("./d:Record", oDocumentNode); var aRecordValues = AnnotationParser._getRecordValues(oRecordNodeList); var oCollectionRecordNodeList = xPath.selectNodes("./d:Collection/d:Record | ./d:Collection/d:If/d:Record", oDocumentNode); var aCollectionRecordValues = AnnotationParser._getRecordValues(oCollectionRecordNodeList); var aPropertyValues = aRecordValues.concat(aCollectionRecordValues); if (aPropertyValues.length > 0) { if (oCollectionRecordNodeList.length === 0 && oRecordNodeList.length > 0) { // Record without a collection, only ise the first one (there should be only one) vPropertyValue = aPropertyValues[0]; } else { vPropertyValue = aPropertyValues; } } else { var oCollectionNodes = xPath.selectNodes("./d:Collection/d:AnnotationPath | ./d:Collection/d:NavigationPropertyPath | ./d:Collection/d:PropertyPath", oDocumentNode); if (oCollectionNodes.length > 0) { vPropertyValue = AnnotationParser._getTextValues(oCollectionNodes); } else { var oChildNodes = xPath.selectNodes("./d:*[not(local-name() = \"Annotation\")]", oDocumentNode); if (oChildNodes.length > 0) { // Now get all values for child elements for (i = 0; i < oChildNodes.length; i++) { var oChildNode = xPath.nextNode(oChildNodes, i); var vValue; var sNodeName = oChildNode.nodeName; var sParentName = oChildNode.parentNode.nodeName; if (sNodeName === "Apply") { vValue = AnnotationParser.getApplyFunctions(oChildNode); } else { vValue = AnnotationParser.getPropertyValue(oChildNode); } // For dynamic expressions, add a Parameters Array so we can iterate over all parameters in // their order within the document if (mMultipleArgumentDynamicExpressions[sParentName]) { if (!Array.isArray(vPropertyValue)) { vPropertyValue = []; } var mValue = {}; mValue[sNodeName] = vValue; vPropertyValue.push(mValue); } else if (sNodeName === "Collection") { // Collections are lists by definition and thus should be parsed as arrays vPropertyValue = vValue; } else { if (vPropertyValue[sNodeName]) { Log.warning( "Annotation contained multiple " + sNodeName + " values. Only the last " + "one will be stored: " + xPath.getPath(oChildNode) ); } vPropertyValue[sNodeName] = vValue; } } AnnotationParser.enrichFromPropertyValueAttributes(vPropertyValue, oDocumentNode); } else if (oDocumentNode.nodeName in mTextNodeIncludeList) { vPropertyValue = AnnotationParser._getTextValue(oDocumentNode); // string } else { // e.g. <Term Name="..." Type="..."> AnnotationParser.enrichFromPropertyValueAttributes(vPropertyValue, oDocumentNode); } } } var oNestedAnnotations = xPath.selectNodes("./d:Annotation", oDocumentNode); if (oNestedAnnotations.length > 0) { for (i = 0; i < oNestedAnnotations.length; i++) { var oNestedAnnotationNode = xPath.nextNode(oNestedAnnotations, i); AnnotationParser._parseAnnotation(sAnnotationTarget, oNestedAnnotationNode, vPropertyValue); } } } else if (oDocumentNode.nodeName in mTextNodeIncludeList) { vPropertyValue = AnnotationParser._getTextValue(oDocumentNode); } else if (oDocumentNode.nodeName.toLowerCase() === "null") { vPropertyValue = null; } else { AnnotationParser.enrichFromPropertyValueAttributes(vPropertyValue, oDocumentNode); } return vPropertyValue; }, /* * Returns a map with all Annotation- and PropertyValue-elements of the given Node. The properties of the returned * map consist of the PropertyValue's "Property" attribute or the Annotation's "Term" attribute. * * @param {Element} oParentElement - The parent element in which to search * @returns {map} The collection of record values and annotations as a map * @static * @private */ getPropertyValues: function(oParentElement) { var mProperties = {}, i; var xPath = AnnotationParser._oXPath; var oAnnotationNodes = xPath.selectNodes("./d:Annotation", oParentElement); var oPropertyValueNodes = xPath.selectNodes("./d:PropertyValue", oParentElement); function getAssertText(oParentElement, sWhat, sName) { var oAnnotationTarget, oAnnotationTerm = oParentElement; while (oAnnotationTerm.nodeName !== "Annotation") { oAnnotationTerm = oAnnotationTerm.parentNode; } oAnnotationTarget = oAnnotationTerm.parentNode; return (sWhat + " '" + sName + "' is defined twice; " + "Source = " + AnnotationParser._parserData.url + ", Annotation Target = " + oAnnotationTarget.getAttribute("Target") + ", Term = " + oAnnotationTerm.getAttribute("Term")); } if (oAnnotationNodes.length === 0 && oPropertyValueNodes.length === 0) { mProperties = AnnotationParser.getPropertyValue(oParentElement); } else { for (i = 0; i < oAnnotationNodes.length; i++) { var oAnnotationNode = xPath.nextNode(oAnnotationNodes, i); var sTerm = AnnotationParser.replaceWithAlias(oAnnotationNode.getAttribute("Term")); // The following function definition inside the loop will be removed in non-debug builds. /* eslint-disable no-loop-func */ assert(!mProperties[sTerm], function () { return getAssertText(oParentElement, "Annotation", sTerm); }); /* eslint-enable no-loop-func */ mProperties[sTerm] = AnnotationParser.getPropertyValue(oAnnotationNode); } for (i = 0; i < oPropertyValueNodes.length; i++) { var oPropertyValueNode = xPath.nextNode(oPropertyValueNodes, i); var sPropertyName = oPropertyValueNode.getAttribute("Property"); // The following function definition inside the loop will be removed in non-debug builds. /* eslint-disable no-loop-func */ assert(!mProperties[sPropertyName], function () { return getAssertText(oParentElement, "Property", sPropertyName); }); /* eslint-enable no-loop-func */ mProperties[sPropertyName] = AnnotationParser.getPropertyValue(oPropertyValueNode); var oApplyNodes = xPath.selectNodes("./d:Apply", oPropertyValueNode); for (var n = 0; n < oApplyNodes.length; n += 1) { var oApplyNode = xPath.nextNode(oApplyNodes, n); mProperties[sPropertyName] = {}; mProperties[sPropertyName]['Apply'] = AnnotationParser.getApplyFunctions(oApplyNode); } } } return mProperties; }, /* * @static * @private */ getApplyFunctions: function(applyNode) { var xPath = AnnotationParser._oXPath; var mApply = { Name: applyNode.getAttribute('Function'), Parameters: [] }; var oParameterNodes = xPath.selectNodes("./d:*", applyNode); for (var i = 0; i < oParameterNodes.length; i += 1) { var oParameterNode = xPath.nextNode(oParameterNodes, i); var mParameter = { Type: oParameterNode.nodeName }; if (oParameterNode.nodeName === "Apply") { mParameter.Value = AnnotationParser.getApplyFunctions(oParameterNode); } else if (oParameterNode.nodeName === "LabeledElement") { mParameter.Value = AnnotationParser.getPropertyValue(oParameterNode); // Move the name attribute up one level to keep compatibility with earlier implementation mParameter.Name = mParameter.Value.Name; delete mParameter.Value.Name; } else if (mMultipleArgumentDynamicExpressions[oParameterNode.nodeName]) { mParameter.Value = AnnotationParser.getPropertyValue(oParameterNode); } else { mParameter.Value = xPath.getNodeText(oParameterNode); } mApply.Parameters.push(mParameter); } return mApply; }, /* * Returns true if the given path combined with the given entity-type is found in the * given metadata * * @param {string} sEntityType - The entity type to look for * @param {string} sPathValue - The path to look for * @returns {map|null} The NavigationProperty map as defined in the EntityType or null if nothing is found * @static * @private */ findNavProperty: function(sEntityType, sPathValue) { var oMetadata = AnnotationParser._parserData.serviceMetadata; for (var i = oMetadata.dataServices.schema.length - 1; i >= 0; i -= 1) { var oMetadataSchema = oMetadata.dataServices.schema[i]; if (oMetadataSchema.entityType) { var sNamespace = oMetadataSchema.namespace + "."; var aEntityTypes = oMetadataSchema.entityType; for (var k = aEntityTypes.length - 1; k >= 0; k -= 1) { if (sNamespace + aEntityTypes[k].name === sEntityType && aEntityTypes[k].navigationProperty) { for (var j = 0; j < aEntityTypes[k].navigationProperty.length; j += 1) { if (aEntityTypes[k].navigationProperty[j].name === sPathValue) { return aEntityTypes[k].navigationProperty[j]; } } } } } } return null; }, /* * Replaces the first alias found in the given string with the corresponding namespace as given * in "AnnotationParser._parserData.aliases". Ensure that the array * "AnnotationParser._parserData.aliasesByLength" contains all aliases ordered by its length * starting with the longest alias. This order is necessary to avoid wrong replacement if there * is an alias which is a prefix of another alias, for example "Common" and "SAP_Common". * * @param {string} sValue The string in which an alias should be replaced * @returns {string} The string with the alias replaced * @static * @private */ replaceWithAlias : function (sValue) { AnnotationParser._parserData.aliasesByLength.some(function (sAlias) { if (sValue.includes(sAlias + ".") && !sValue.includes("." + sAlias + ".")) { sValue = sValue.replace(sAlias + ".", AnnotationParser._parserData.aliases[sAlias] + "."); return true; } return false; }); return sValue; }, /* * @static * @private */ getXPath: function() { var xPath = {}; var mParserData = AnnotationParser._parserData; xPath = { setNameSpace : function(outNode) { return outNode; }, nsResolver : function(prefix) { var ns = { "edmx" : "http://docs.oasis-open.org/odata/ns/edmx", "d" : "http://docs.oasis-open.org/odata/ns/edm" }; return ns[prefix] || null; }, selectNodes : function(sPath, inNode) { var xmlNodes = mParserData.xmlDocument.evaluate(sPath, inNode, this.nsResolver, /* ORDERED_NODE_SNAPSHOT_TYPE: */ 7, null); xmlNodes.length = xmlNodes.snapshotLength; return xmlNodes; }, nextNode : function(node, item) { return node.snapshotItem(item); }, getNodeText : function(node) { return node.textContent; } }; xPath.getPath = function(oNode) { var sPath = ""; var sId = "getAttribute" in oNode ? oNode.getAttribute("id") : ""; var sTagName = oNode.tagName ? oNode.tagName : ""; if (sId) { // If node has an ID, use that sPath = 'id("' + sId + '")'; } else if (oNode instanceof Document) { sPath = "/"; } else if (sTagName.toLowerCase() === "body") { // If node is the body element, just return its tag name sPath = sTagName; } else if (oNode.parentNode) { // Count the position in the parent and get the path of the parent recursively var iPos = 1; for (var i = 0; i < oNode.parentNode.childNodes.length; ++i) { if (oNode.parentNode.childNodes[i] === oNode) { // Found the node inside its parent sPath = xPath.getPath(oNode.parentNode) + "/" + sTagName + "[" + iPos + "]"; break; } else if (oNode.parentNode.childNodes[i].nodeType === 1 && oNode.parentNode.childNodes[i].tagName === sTagName) { // In case there are other elements of the same kind, count them ++iPos; } } } else { Log.error("Wrong Input node - cannot find XPath to it: " + sTagName); } return sPath; }; return xPath; } }; return AnnotationParser; });