blob: a7ca00aec11d66d76876607aacf7043032b714b8 [file] [log] [blame]
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// odata-metadata.js
(function (window, undefined) {
var datajs = window.datajs || {};
var odata = window.OData || {};
// imports
var contains = datajs.contains;
var normalizeURI = datajs.normalizeURI;
var xmlAttributes = datajs.xmlAttributes;
var xmlChildElements = datajs.xmlChildElements;
var xmlFirstChildElement = datajs.xmlFirstChildElement;
var xmlInnerText = datajs.xmlInnerText;
var xmlLocalName = datajs.xmlLocalName;
var xmlNamespaceURI = datajs.xmlNamespaceURI;
var xmlNS = datajs.xmlNS;
var xmlnsNS = datajs.xmlnsNS;
var xmlParse = datajs.xmlParse;
var createAttributeExtension = odata.createAttributeExtension;
var createElementExtension = odata.createElementExtension;
var edmxNs = odata.edmxNs;
var edmNs1 = odata.edmNs1;
var edmNs1_1 = odata.edmNs1_1;
var edmNs1_2 = odata.edmNs1_2;
var edmNs2a = odata.edmNs2a;
var edmNs2b = odata.edmNs2b;
var edmNs3 = odata.edmNs3;
var handler = odata.handler;
var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
var odataMetaXmlNs = odata.odataMetaXmlNs;
var xmlMediaType = "application/xml";
// CONTENT START
var schemaElement = function (attributes, elements, text, ns) {
/// <summary>Creates an object that describes an element in an schema.</summary>
/// <param name="attributes" type="Array">List containing the names of the attributes allowed for this element.</param>
/// <param name="elements" type="Array">List containing the names of the child elements allowed for this element.</param>
/// <param name="text" type="Boolean">Flag indicating if the element's text value is of interest or not.</param>
/// <param name="ns" type="String">Namespace to which the element belongs to.</param>
/// <remarks>
/// If a child element name ends with * then it is understood by the schema that that child element can appear 0 or more times.
/// </remarks>
/// <returns type="Object">Object with attributes, elements, text, and ns fields.</returns>
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://msdn.microsoft.com/en-us/library/bb399292.aspx for a CSDL reference.
var schema = {
elements: {
Annotations: schemaElement(
/*attributes*/["Target", "Qualifier"],
/*elements*/["TypeAnnotation*", "ValueAnnotation*"]
),
Association: schemaElement(
/*attributes*/["Name"],
/*elements*/["End*", "ReferentialConstraint", "TypeAnnotation*", "ValueAnnotation*"]
),
AssociationSet: schemaElement(
/*attributes*/["Name", "Association"],
/*elements*/["End*", "TypeAnnotation*", "ValueAnnotation*"]
),
Binary: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Bool: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Collection: schemaElement(
/*attributes*/null,
/*elements*/["String*", "Int*", "Float*", "Decimal*", "Bool*", "DateTime*", "DateTimeOffset*", "Guid*", "Binary*", "Time*", "Collection*", "Record*"]
),
CollectionType: schemaElement(
/*attributes*/["ElementType", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "SRID"],
/*elements*/["CollectionType", "ReferenceType", "RowType", "TypeRef"]
),
ComplexType: schemaElement(
/*attributes*/["Name", "BaseType", "Abstract"],
/*elements*/["Property*", "TypeAnnotation*", "ValueAnnotation*"]
),
DateTime: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
DateTimeOffset: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Decimal: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
DefiningExpression: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Dependent: schemaElement(
/*attributes*/["Role"],
/*elements*/["PropertyRef*"]
),
Documentation: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
End: schemaElement(
/*attributes*/["Type", "Role", "Multiplicity", "EntitySet"],
/*elements*/["OnDelete"]
),
EntityContainer: schemaElement(
/*attributes*/["Name", "Extends"],
/*elements*/["EntitySet*", "AssociationSet*", "FunctionImport*", "TypeAnnotation*", "ValueAnnotation*"]
),
EntitySet: schemaElement(
/*attributes*/["Name", "EntityType"],
/*elements*/["TypeAnnotation*", "ValueAnnotation*"]
),
EntityType: schemaElement(
/*attributes*/["Name", "BaseType", "Abstract", "OpenType"],
/*elements*/["Key", "Property*", "NavigationProperty*", "TypeAnnotation*", "ValueAnnotation*"]
),
EnumType: schemaElement(
/*attributes*/["Name", "UnderlyingType", "IsFlags"],
/*elements*/["Member*"]
),
Float: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Function: schemaElement(
/*attributes*/["Name", "ReturnType"],
/*elements*/["Parameter*", "DefiningExpression", "ReturnType", "TypeAnnotation*", "ValueAnnotation*"]
),
FunctionImport: schemaElement(
/*attributes*/["Name", "ReturnType", "EntitySet", "IsSideEffecting", "IsComposable", "IsBindable", "EntitySetPath"],
/*elements*/["Parameter*", "ReturnType", "TypeAnnotation*", "ValueAnnotation*"]
),
Guid: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Int: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Key: schemaElement(
/*attributes*/null,
/*elements*/["PropertyRef*"]
),
LabeledElement: schemaElement(
/*attributes*/["Name"],
/*elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
),
Member: schemaElement(
/*attributes*/["Name", "Value"]
),
NavigationProperty: schemaElement(
/*attributes*/["Name", "Relationship", "ToRole", "FromRole", "ContainsTarget"],
/*elements*/["TypeAnnotation*", "ValueAnnotation*"]
),
Null: schemaElement(
/*attributes*/null,
/*elements*/null
),
OnDelete: schemaElement(
/*attributes*/["Action"]
),
Path: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Parameter: schemaElement(
/*attributes*/["Name", "Type", "Mode", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "ConcurrencyMode", "SRID"],
/*elements*/["CollectionType", "ReferenceType", "RowType", "TypeRef", "TypeAnnotation*", "ValueAnnotation*"]
),
Principal: schemaElement(
/*attributes*/["Role"],
/*elements*/["PropertyRef*"]
),
Property: schemaElement(
/*attributes*/["Name", "Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "ConcurrencyMode", "CollectionKind", "SRID"],
/*elements*/["CollectionType", "ReferenceType", "RowType", "TypeAnnotation*", "ValueAnnotation*"]
),
PropertyRef: schemaElement(
/*attributes*/["Name"]
),
PropertyValue: schemaElement(
/*attributes*/["Property", "Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time"],
/*Elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
),
ReferenceType: schemaElement(
/*attributes*/["Type"]
),
ReferentialConstraint: schemaElement(
/*attributes*/null,
/*elements*/["Principal", "Dependent"]
),
ReturnType: schemaElement(
/*attributes*/["ReturnType", "Type", "EntitySet"],
/*elements*/["CollectionType", "ReferenceType", "RowType"]
),
RowType: schemaElement(
/*elements*/["Property*"]
),
String: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Schema: schemaElement(
/*attributes*/["Namespace", "Alias"],
/*elements*/["Using*", "EntityContainer*", "EntityType*", "Association*", "ComplexType*", "Function*", "ValueTerm*", "Annotations*"]
),
Time: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
TypeAnnotation: schemaElement(
/*attributes*/["Term", "Qualifier"],
/*elements*/["PropertyValue*"]
),
TypeRef: schemaElement(
/*attributes*/["Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "SRID"]
),
Using: schemaElement(
/*attributes*/["Namespace", "Alias"]
),
ValueAnnotation: schemaElement(
/*attributes*/["Term", "Qualifier", "Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time"],
/*Elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
),
ValueTerm: schemaElement(
/*attributes*/["Name", "Type"],
/*elements*/["TypeAnnotation*", "ValueAnnotation*"]
),
// See http://msdn.microsoft.com/en-us/library/dd541238(v=prot.10) for an EDMX reference.
Edmx: schemaElement(
/*attributes*/["Version"],
/*elements*/["DataServices", "Reference*", "AnnotationsReference*"],
/*text*/false,
/*ns*/edmxNs
),
DataServices: schemaElement(
/*attributes*/null,
/*elements*/["Schema*"],
/*text*/false,
/*ns*/edmxNs
)
}
};
// See http://msdn.microsoft.com/en-us/library/ee373839.aspx for a feed customization reference.
var customizationAttributes = ["m:FC_ContentKind", "m:FC_KeepInContent", "m:FC_NsPrefix", "m:FC_NsUri", "m:FC_SourcePath", "m:FC_TargetPath"];
schema.elements.Property.attributes = schema.elements.Property.attributes.concat(customizationAttributes);
schema.elements.EntityType.attributes = schema.elements.EntityType.attributes.concat(customizationAttributes);
// See http://msdn.microsoft.com/en-us/library/dd541284(PROT.10).aspx for an EDMX reference.
schema.elements.Edmx = { attributes: ["Version"], elements: ["DataServices"], ns: edmxNs };
schema.elements.DataServices = { elements: ["Schema*"], ns: edmxNs };
// See http://msdn.microsoft.com/en-us/library/dd541233(v=PROT.10) for Conceptual Schema Definition Language Document for Data Services.
schema.elements.EntityContainer.attributes.push("m:IsDefaultEntityContainer");
schema.elements.Property.attributes.push("m:MimeType");
schema.elements.FunctionImport.attributes.push("m:HttpMethod");
schema.elements.FunctionImport.attributes.push("m:IsAlwaysBindable");
schema.elements.EntityType.attributes.push("m:HasStream");
schema.elements.DataServices.attributes = ["m:DataServiceVersion", "m:MaxDataServiceVersion"];
var scriptCase = function (text) {
/// <summary>Converts a Pascal-case identifier into a camel-case identifier.</summary>
/// <param name="text" type="String">Text to convert.</param>
/// <returns type="String">Converted text.</returns>
/// <remarks>If the text starts with multiple uppercase characters, it is left as-is.</remarks>
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();
};
var getChildSchema = function (parentSchema, candidateName) {
/// <summary>Gets the schema node for the specified element.</summary>
/// <param name="parentSchema" type="Object">Schema of the parent XML node of 'element'.</param>
/// <param name="candidateName">XML element name to consider.</param>
/// <returns type="Object">The schema that describes the specified element; null if not found.</returns>
if (candidateName === "Documentation") {
return { isArray: true, propertyName: "documentation" };
}
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;
};
// This regular expression is used to detect a feed customization element
// after we've normalized it into the 'm' prefix. It starts with m:FC_,
// followed by other characters, and ends with _ and a number.
// The captures are 0 - whole string, 1 - name as it appears in internal table.
var isFeedCustomizationNameRE = /^(m:FC_.*)_[0-9]+$/;
var isEdmNamespace = function (nsURI) {
/// <summary>Checks whether the specifies namespace URI is one of the known CSDL namespace URIs.</summary>
/// <param name="nsURI" type="String">Namespace URI to check.</param>
/// <returns type="Boolean">true if nsURI is a known CSDL namespace; false otherwise.</returns>
return nsURI === edmNs1 ||
nsURI === edmNs1_1 ||
nsURI === edmNs1_2 ||
nsURI === edmNs2a ||
nsURI === edmNs2b ||
nsURI === edmNs3;
};
var parseConceptualModelElement = function (element) {
/// <summary>Parses a CSDL document.</summary>
/// <param name="element">DOM element to parse.</param>
/// <returns type="Object">An object describing the parsed element.</returns>
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 extensions = [];
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;
var handled = false;
if (isEdmNamespace(nsURI) || nsURI === null) {
schemaName = "";
} else if (nsURI === odataMetaXmlNs) {
schemaName = "m:";
}
if (schemaName !== null) {
schemaName += localName;
// Feed customizations for complex types have additional
// attributes with a suffixed counter starting at '1', so
// take that into account when doing the lookup.
var match = isFeedCustomizationNameRE.exec(schemaName);
if (match) {
schemaName = match[1];
}
if (contains(attributes, schemaName)) {
handled = true;
item[scriptCase(localName)] = value;
}
}
if (!handled) {
extensions.push(createAttributeExtension(attribute));
}
});
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);
}
} else {
extensions.push(createElementExtension(child));
}
});
if (elementSchema.text) {
item.text = xmlInnerText(element);
}
if (extensions.length) {
item.extensions = extensions;
}
return item;
};
var metadataParser = function (handler, text) {
/// <summary>Parses a metadata document.</summary>
/// <param name="handler">This handler.</param>
/// <param name="text" type="String">Metadata text.</param>
/// <returns>An object representation of the conceptual model.</returns>
var doc = xmlParse(text);
var root = xmlFirstChildElement(doc);
return parseConceptualModelElement(root) || undefined;
};
odata.metadataHandler = handler(metadataParser, null, xmlMediaType, MAX_DATA_SERVICE_VERSION);
// DATAJS INTERNAL START
odata.schema = schema;
odata.scriptCase = scriptCase;
odata.getChildSchema = getChildSchema;
odata.parseConceptualModelElement = parseConceptualModelElement;
odata.metadataParser = metadataParser;
// DATAJS INTERNAL END
// CONTENT END
})(this);