lib/odata/metadata.js (423 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ 'use strict'; /** @module odata/metadata */ var utils = require('./../utils.js'); var oDSxml = require('./../xml.js'); var odataHandler = require('./handler.js'); // imports var contains = utils.contains; var normalizeURI = utils.normalizeURI; var xmlAttributes = oDSxml.xmlAttributes; var xmlChildElements = oDSxml.xmlChildElements; var xmlFirstChildElement = oDSxml.xmlFirstChildElement; var xmlInnerText = oDSxml.xmlInnerText; var xmlLocalName = oDSxml.xmlLocalName; var xmlNamespaceURI = oDSxml.xmlNamespaceURI; var xmlNS = oDSxml.xmlNS; var xmlnsNS = oDSxml.xmlnsNS; var xmlParse = oDSxml.xmlParse; var ado = oDSxml.http + "docs.oasis-open.org/odata/"; // http://docs.oasis-open.org/odata/ var adoDs = ado + "ns"; // http://docs.oasis-open.org/odata/ns var edmxNs = adoDs + "/edmx"; // http://docs.oasis-open.org/odata/ns/edmx var edmNs1 = adoDs + "/edm"; // http://docs.oasis-open.org/odata/ns/edm var odataMetaXmlNs = adoDs + "/metadata"; // http://docs.oasis-open.org/odata/ns/metadata var MAX_DATA_SERVICE_VERSION = odataHandler.MAX_DATA_SERVICE_VERSION; var xmlMediaType = "application/xml"; /** Creates an object that describes an element in an schema. * @param {Array} attributes - List containing the names of the attributes allowed for this element. * @param {Array} elements - List containing the names of the child elements allowed for this element. * @param {Boolean} text - Flag indicating if the element's text value is of interest or not. * @param {String} ns - Namespace to which the element belongs to. * If a child element name ends with * then it is understood by the schema that that child element can appear 0 or more times. * @returns {Object} Object with attributes, elements, text, and ns fields. */ function schemaElement(attributes, elements, text, ns) { return { attributes: attributes, elements: elements, text: text || false, ns: ns }; } // It's assumed that all elements may have Documentation children and Annotation elements. // See http://docs.oasis-open.org/odata/odata/v4.0/cs01/part3-csdl/odata-v4.0-cs01-part3-csdl.html for a CSDL reference. var schema = { elements: { Action: schemaElement( /*attributes*/["Name", "IsBound", "EntitySetPath"], /*elements*/["ReturnType", "Parameter*", "Annotation*"] ), ActionImport: schemaElement( /*attributes*/["Name", "Action", "EntitySet", "Annotation*"] ), Annotation: schemaElement( /*attributes*/["Term", "Qualifier", "Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "AnnotationPath", "NavigationPropertyPath", "Path", "PropertyPath", "UrlRef"], /*elements*/["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"] ), AnnotationPath: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Annotations: schemaElement( /*attributes*/["Target", "Qualifier"], /*elements*/["Annotation*"] ), Apply: schemaElement( /*attributes*/["Function"], /*elements*/["String*", "Path*", "LabeledElement*", "Annotation*"] ), And: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Or: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Not: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Eq: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Ne: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Gt: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Ge: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Lt: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Le: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Binary: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Bool: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Cast: schemaElement( /*attributes*/["Type"], /*elements*/["Path*", "Annotation*"] ), Collection: schemaElement( /*attributes*/null, /*elements*/["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*"] ), ComplexType: schemaElement( /*attributes*/["Name", "BaseType", "Abstract", "OpenType"], /*elements*/["Property*", "NavigationProperty*", "Annotation*"] ), Date: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), DateTimeOffset: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Decimal: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Duration: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), EntityContainer: schemaElement( /*attributes*/["Name", "Extends"], /*elements*/["EntitySet*", "Singleton*", "ActionImport*", "FunctionImport*", "Annotation*"] ), EntitySet: schemaElement( /*attributes*/["Name", "EntityType", "IncludeInServiceDocument"], /*elements*/["NavigationPropertyBinding*", "Annotation*"] ), EntityType: schemaElement( /*attributes*/["Name", "BaseType", "Abstract", "OpenType", "HasStream"], /*elements*/["Key*", "Property*", "NavigationProperty*", "Annotation*"] ), EnumMember: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), EnumType: schemaElement( /*attributes*/["Name", "UnderlyingType", "IsFlags"], /*elements*/["Member*"] ), Float: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Function: schemaElement( /*attributes*/["Name", "IsBound", "IsComposable", "EntitySetPath"], /*elements*/["ReturnType", "Parameter*", "Annotation*"] ), FunctionImport: schemaElement( /*attributes*/["Name", "Function", "EntitySet", "IncludeInServiceDocument", "Annotation*"] ), Guid: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), If: schemaElement( /*attributes*/null, /*elements*/["Path*", "String*", "Annotation*"] ), Int: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), IsOf: schemaElement( /*attributes*/["Type", "MaxLength", "Precision", "Scale", "Unicode", "SRID", "DefaultValue", "Annotation*"], /*elements*/["Path*"] ), Key: schemaElement( /*attributes*/null, /*elements*/["PropertyRef*"] ), LabeledElement: schemaElement( /*attributes*/["Name"], /*elements*/["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"] ), LabeledElementReference: schemaElement( /*attributes*/["Term"], /*elements*/["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*"] ), Member: schemaElement( /*attributes*/["Name", "Value"], /*element*/["Annotation*"] ), NavigationProperty: schemaElement( /*attributes*/["Name", "Type", "Nullable", "Partner", "ContainsTarget"], /*elements*/["ReferentialConstraint*", "OnDelete*", "Annotation*"] ), NavigationPropertyBinding: schemaElement( /*attributes*/["Path", "Target"] ), NavigationPropertyPath: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Null: schemaElement( /*attributes*/null, /*elements*/["Annotation*"] ), OnDelete: schemaElement( /*attributes*/["Action"], /*elements*/["Annotation*"] ), Path: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Parameter: schemaElement( /*attributes*/["Name", "Type", "Nullable", "MaxLength", "Precision", "Scale", "SRID"], /*elements*/["Annotation*"] ), Property: schemaElement( /*attributes*/["Name", "Type", "Nullable", "MaxLength", "Precision", "Scale", "Unicode", "SRID", "DefaultValue"], /*elements*/["Annotation*"] ), PropertyPath: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), PropertyRef: schemaElement( /*attributes*/["Name", "Alias"] ), PropertyValue: schemaElement( /*attributes*/["Property", "Path"], /*elements*/["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"] ), Record: schemaElement( /*attributes*/null, /*Elements*/["PropertyValue*", "Property*", "Annotation*"] ), ReferentialConstraint: schemaElement( /*attributes*/["Property", "ReferencedProperty", "Annotation*"] ), ReturnType: schemaElement( /*attributes*/["Type", "Nullable", "MaxLength", "Precision", "Scale", "SRID"] ), String: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), Schema: schemaElement( /*attributes*/["Namespace", "Alias"], /*elements*/["Action*", "Annotations*", "Annotation*", "ComplexType*", "EntityContainer", "EntityType*", "EnumType*", "Function*", "Term*", "TypeDefinition*", "Annotation*"] ), Singleton: schemaElement( /*attributes*/["Name", "Type"], /*elements*/["NavigationPropertyBinding*", "Annotation*"] ), Term: schemaElement( /*attributes*/["Name", "Type", "BaseTerm", "DefaultValue ", "AppliesTo", "Nullable", "MaxLength", "Precision", "Scale", "SRID"], /*elements*/["Annotation*"] ), TimeOfDay: schemaElement( /*attributes*/null, /*elements*/null, /*text*/true ), TypeDefinition: schemaElement( /*attributes*/["Name", "UnderlyingType", "MaxLength", "Unicode", "Precision", "Scale", "SRID"], /*elements*/["Annotation*"] ), UrlRef: schemaElement( /*attributes*/null, /*elements*/["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"] ), // See http://msdn.microsoft.com/en-us/library/dd541238(v=prot.10) for an EDMX reference. Edmx: schemaElement( /*attributes*/["Version"], /*elements*/["DataServices", "Reference*"], /*text*/false, /*ns*/edmxNs ), DataServices: schemaElement( /*attributes*/["m:MaxDataServiceVersion", "m:DataServiceVersion"], /*elements*/["Schema*"], /*text*/false, /*ns*/edmxNs ), Reference: schemaElement( /*attributes*/["Uri"], /*elements*/["Include*", "IncludeAnnotations*", "Annotation*"] ), Include: schemaElement( /*attributes*/["Namespace", "Alias"] ), IncludeAnnotations: schemaElement( /*attributes*/["TermNamespace", "Qualifier", "TargetNamespace"] ) } }; /** Converts a Pascal-case identifier into a camel-case identifier. * @param {String} text - Text to convert. * @returns {String} Converted text. * If the text starts with multiple uppercase characters, it is left as-is. */ function scriptCase(text) { if (!text) { return text; } if (text.length > 1) { var firstTwo = text.substr(0, 2); if (firstTwo === firstTwo.toUpperCase()) { return text; } return text.charAt(0).toLowerCase() + text.substr(1); } return text.charAt(0).toLowerCase(); } /** Gets the schema node for the specified element. * @param {Object} parentSchema - Schema of the parent XML node of 'element'. * @param candidateName - XML element name to consider. * @returns {Object} The schema that describes the specified element; null if not found. */ function getChildSchema(parentSchema, candidateName) { var elements = parentSchema.elements; if (!elements) { return null; } var i, len; for (i = 0, len = elements.length; i < len; i++) { var elementName = elements[i]; var multipleElements = false; if (elementName.charAt(elementName.length - 1) === "*") { multipleElements = true; elementName = elementName.substr(0, elementName.length - 1); } if (candidateName === elementName) { var propertyName = scriptCase(elementName); return { isArray: multipleElements, propertyName: propertyName }; } } return null; } /** Checks whether the specifies namespace URI is one of the known CSDL namespace URIs. * @param {String} nsURI - Namespace URI to check. * @returns {Boolean} true if nsURI is a known CSDL namespace; false otherwise. */ function isEdmNamespace(nsURI) { return nsURI === edmNs1; } /** Parses a CSDL document. * @param element - DOM element to parse. * @returns {Object} An object describing the parsed element. */ function parseConceptualModelElement(element) { var localName = xmlLocalName(element); var nsURI = xmlNamespaceURI(element); var elementSchema = schema.elements[localName]; if (!elementSchema) { return null; } if (elementSchema.ns) { if (nsURI !== elementSchema.ns) { return null; } } else if (!isEdmNamespace(nsURI)) { return null; } var item = {}; var attributes = elementSchema.attributes || []; xmlAttributes(element, function (attribute) { var localName = xmlLocalName(attribute); var nsURI = xmlNamespaceURI(attribute); var value = attribute.value; // Don't do anything with xmlns attributes. if (nsURI === xmlnsNS) { return; } // Currently, only m: for metadata is supported as a prefix in the internal schema table, // un-prefixed element names imply one a CSDL element. var schemaName = null; if (isEdmNamespace(nsURI) || nsURI === null) { schemaName = ""; } else if (nsURI === odataMetaXmlNs) { schemaName = "m:"; } if (schemaName !== null) { schemaName += localName; if (contains(attributes, schemaName)) { item[scriptCase(localName)] = value; } } }); xmlChildElements(element, function (child) { var localName = xmlLocalName(child); var childSchema = getChildSchema(elementSchema, localName); if (childSchema) { if (childSchema.isArray) { var arr = item[childSchema.propertyName]; if (!arr) { arr = []; item[childSchema.propertyName] = arr; } arr.push(parseConceptualModelElement(child)); } else { item[childSchema.propertyName] = parseConceptualModelElement(child); } } }); if (elementSchema.text) { item.text = xmlInnerText(element); } return item; } /** Parses a metadata document. * @param handler - This handler. * @param {String} text - Metadata text. * @returns An object representation of the conceptual model. */ function metadataParser(handler, text) { var doc = xmlParse(text); var root = xmlFirstChildElement(doc); return parseConceptualModelElement(root) || undefined; } exports.metadataHandler = odataHandler.handler(metadataParser, null, xmlMediaType, MAX_DATA_SERVICE_VERSION); exports.schema = schema; exports.scriptCase = scriptCase; exports.getChildSchema = getChildSchema; exports.parseConceptualModelElement = parseConceptualModelElement; exports.metadataParser = metadataParser;