Return-Path: X-Original-To: apmail-olingo-commits-archive@minotaur.apache.org Delivered-To: apmail-olingo-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 96F08116C1 for ; Thu, 12 Jun 2014 12:41:32 +0000 (UTC) Received: (qmail 22422 invoked by uid 500); 12 Jun 2014 12:41:32 -0000 Delivered-To: apmail-olingo-commits-archive@olingo.apache.org Received: (qmail 22343 invoked by uid 500); 12 Jun 2014 12:41:32 -0000 Mailing-List: contact commits-help@olingo.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@olingo.apache.org Delivered-To: mailing list commits@olingo.apache.org Received: (qmail 22249 invoked by uid 99); 12 Jun 2014 12:41:32 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 12 Jun 2014 12:41:32 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 2DAD648ADB; Thu, 12 Jun 2014 12:41:32 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: koblers@apache.org To: commits@olingo.apache.org Date: Thu, 12 Jun 2014 12:41:34 -0000 Message-Id: In-Reply-To: <4cea6fadc884447bb2889b44a3a5e6f1@git.apache.org> References: <4cea6fadc884447bb2889b44a3a5e6f1@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [3/6] [OLINGO-324] start JSON reader, add demo, is working version http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/54486148/datajs/demo/scripts/datajs-1.1.2.js ---------------------------------------------------------------------- diff --git a/datajs/demo/scripts/datajs-1.1.2.js b/datajs/demo/scripts/datajs-1.1.2.js new file mode 100644 index 0000000..7282713 --- /dev/null +++ b/datajs/demo/scripts/datajs-1.1.2.js @@ -0,0 +1,10577 @@ +// 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. + +// datajs.js + +(function (window, undefined) { + + var datajs = window.datajs || {}; + var odata = window.OData || {}; + + // AMD support + if (typeof define === 'function' && define.amd) { + define('datajs', datajs); + define('OData', odata); + } else { + window.datajs = datajs; + window.OData = odata; + } + + datajs.version = { + major: 1, + minor: 1, + build: 1 + }; + + + var activeXObject = function (progId) { + /// Creates a new ActiveXObject from the given progId. + /// + /// ProgId string of the desired ActiveXObject. + /// + /// + /// This function throws whatever exception might occur during the creation + /// of the ActiveXObject. + /// + /// + /// The ActiveXObject instance. Null if ActiveX is not supported by the + /// browser. + /// + if (window.ActiveXObject) { + return new window.ActiveXObject(progId); + } + return null; + }; + + var assigned = function (value) { + /// Checks whether the specified value is different from null and undefined. + /// Value to check. + /// true if the value is assigned; false otherwise. + return value !== null && value !== undefined; + }; + + var contains = function (arr, item) { + /// Checks whether the specified item is in the array. + /// Array to check in. + /// Item to look for. + /// true if the item is contained, false otherwise. + + var i, len; + for (i = 0, len = arr.length; i < len; i++) { + if (arr[i] === item) { + return true; + } + } + + return false; + }; + + var defined = function (a, b) { + /// Given two values, picks the first one that is not undefined. + /// First value. + /// Second value. + /// a if it's a defined value; else b. + return (a !== undefined) ? a : b; + }; + + var delay = function (callback) { + /// Delays the invocation of the specified function until execution unwinds. + /// Callback function. + if (arguments.length === 1) { + window.setTimeout(callback, 0); + return; + } + + var args = Array.prototype.slice.call(arguments, 1); + window.setTimeout(function () { + callback.apply(this, args); + }, 0); + }; + + + var extend = function (target, values) { + /// Extends the target with the specified values. + /// Object to add properties to. + /// Object with properties to add into target. + /// The target object. + + for (var name in values) { + target[name] = values[name]; + } + + return target; + }; + + var find = function (arr, callback) { + /// Returns the first item in the array that makes the callback function true. + /// Array to check in. + /// Callback function to invoke once per item in the array. + /// The first item that makes the callback return true; null otherwise or if the array is null. + + if (arr) { + var i, len; + for (i = 0, len = arr.length; i < len; i++) { + if (callback(arr[i])) { + return arr[i]; + } + } + } + return null; + }; + + var isArray = function (value) { + /// Checks whether the specified value is an array object. + /// Value to check. + /// true if the value is an array object; false otherwise. + + return Object.prototype.toString.call(value) === "[object Array]"; + }; + + var isDate = function (value) { + /// Checks whether the specified value is a Date object. + /// Value to check. + /// true if the value is a Date object; false otherwise. + + return Object.prototype.toString.call(value) === "[object Date]"; + }; + + var isObject = function (value) { + /// Tests whether a value is an object. + /// Value to test. + /// + /// Per javascript rules, null and array values are objects and will cause this function to return true. + /// + /// True is the value is an object; false otherwise. + + return typeof value === "object"; + }; + + var parseInt10 = function (value) { + /// Parses a value in base 10. + /// String value to parse. + /// The parsed value, NaN if not a valid value. + + return parseInt(value, 10); + }; + + var renameProperty = function (obj, oldName, newName) { + /// Renames a property in an object. + /// Object in which the property will be renamed. + /// Name of the property that will be renamed. + /// New name of the property. + /// + /// This function will not do anything if the object doesn't own a property with the specified old name. + /// + + if (obj.hasOwnProperty(oldName)) { + obj[newName] = obj[oldName]; + delete obj[oldName]; + } + }; + + var throwErrorCallback = function (error) { + /// Default error handler. + /// Error to handle. + throw error; + }; + + var trimString = function (str) { + /// Removes leading and trailing whitespaces from a string. + /// String to trim + /// The string with no leading or trailing whitespace. + + if (str.trim) { + return str.trim(); + } + + return str.replace(/^\s+|\s+$/g, ''); + }; + + var undefinedDefault = function (value, defaultValue) { + /// Returns a default value in place of undefined. + /// Value to check. + /// Value to return if value is undefined. + /// value if it's defined; defaultValue otherwise. + /// + /// This should only be used for cases where falsy values are valid; + /// otherwise the pattern should be 'x = (value) ? value : defaultValue;'. + /// + return (value !== undefined) ? value : defaultValue; + }; + + // Regular expression that splits a uri into its components: + // 0 - is the matched string. + // 1 - is the scheme. + // 2 - is the authority. + // 3 - is the path. + // 4 - is the query. + // 5 - is the fragment. + var uriRegEx = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/; + var uriPartNames = ["scheme", "authority", "path", "query", "fragment"]; + + var getURIInfo = function (uri) { + /// Gets information about the components of the specified URI. + /// URI to get information from. + /// + /// An object with an isAbsolute flag and part names (scheme, authority, etc.) if available. + /// + + var result = { isAbsolute: false }; + + if (uri) { + var matches = uriRegEx.exec(uri); + if (matches) { + var i, len; + for (i = 0, len = uriPartNames.length; i < len; i++) { + if (matches[i + 1]) { + result[uriPartNames[i]] = matches[i + 1]; + } + } + } + if (result.scheme) { + result.isAbsolute = true; + } + } + + return result; + }; + + var getURIFromInfo = function (uriInfo) { + /// Builds a URI string from its components. + /// An object with uri parts (scheme, authority, etc.). + /// URI string. + + return "".concat( + uriInfo.scheme || "", + uriInfo.authority || "", + uriInfo.path || "", + uriInfo.query || "", + uriInfo.fragment || ""); + }; + + // Regular expression that splits a uri authority into its subcomponents: + // 0 - is the matched string. + // 1 - is the userinfo subcomponent. + // 2 - is the host subcomponent. + // 3 - is the port component. + var uriAuthorityRegEx = /^\/{0,2}(?:([^@]*)@)?([^:]+)(?::{1}(\d+))?/; + + // Regular expression that matches percentage enconded octects (i.e %20 or %3A); + var pctEncodingRegEx = /%[0-9A-F]{2}/ig; + + var normalizeURICase = function (uri) { + /// Normalizes the casing of a URI. + /// URI to normalize, absolute or relative. + /// The URI normalized to lower case. + + var uriInfo = getURIInfo(uri); + var scheme = uriInfo.scheme; + var authority = uriInfo.authority; + + if (scheme) { + uriInfo.scheme = scheme.toLowerCase(); + if (authority) { + var matches = uriAuthorityRegEx.exec(authority); + if (matches) { + uriInfo.authority = "//" + + (matches[1] ? matches[1] + "@" : "") + + (matches[2].toLowerCase()) + + (matches[3] ? ":" + matches[3] : ""); + } + } + } + + uri = getURIFromInfo(uriInfo); + + return uri.replace(pctEncodingRegEx, function (str) { + return str.toLowerCase(); + }); + }; + + var normalizeURI = function (uri, base) { + /// Normalizes a possibly relative URI with a base URI. + /// URI to normalize, absolute or relative. + /// Base URI to compose with. + /// The composed URI if relative; the original one if absolute. + + if (!base) { + return uri; + } + + var uriInfo = getURIInfo(uri); + if (uriInfo.isAbsolute) { + return uri; + } + + var baseInfo = getURIInfo(base); + var normInfo = {}; + var path; + + if (uriInfo.authority) { + normInfo.authority = uriInfo.authority; + path = uriInfo.path; + normInfo.query = uriInfo.query; + } else { + if (!uriInfo.path) { + path = baseInfo.path; + normInfo.query = uriInfo.query || baseInfo.query; + } else { + if (uriInfo.path.charAt(0) === '/') { + path = uriInfo.path; + } else { + path = mergeUriPathWithBase(uriInfo.path, baseInfo.path); + } + normInfo.query = uriInfo.query; + } + normInfo.authority = baseInfo.authority; + } + + normInfo.path = removeDotsFromPath(path); + + normInfo.scheme = baseInfo.scheme; + normInfo.fragment = uriInfo.fragment; + + return getURIFromInfo(normInfo); + }; + + var mergeUriPathWithBase = function (uriPath, basePath) { + /// Merges the path of a relative URI and a base URI. + /// Base URI path. + /// A string with the merged path. + + var path = "/"; + var end; + + if (basePath) { + end = basePath.lastIndexOf("/"); + path = basePath.substring(0, end); + + if (path.charAt(path.length - 1) !== "/") { + path = path + "/"; + } + } + + return path + uriPath; + }; + + var removeDotsFromPath = function (path) { + /// Removes the special folders . and .. from a URI's path. + /// URI path component. + /// Path without any . and .. folders. + + var result = ""; + var segment = ""; + var end; + + while (path) { + if (path.indexOf("..") === 0 || path.indexOf(".") === 0) { + path = path.replace(/^\.\.?\/?/g, ""); + } else if (path.indexOf("/..") === 0) { + path = path.replace(/^\/\..\/?/g, "/"); + end = result.lastIndexOf("/"); + if (end === -1) { + result = ""; + } else { + result = result.substring(0, end); + } + } else if (path.indexOf("/.") === 0) { + path = path.replace(/^\/\.\/?/g, "/"); + } else { + segment = path; + end = path.indexOf("/", 1); + if (end !== -1) { + segment = path.substring(0, end); + } + result = result + segment; + path = path.replace(segment, ""); + } + } + return result; + }; + + var convertByteArrayToHexString = function (str) { + var arr = []; + if (window.atob === undefined) { + arr = decodeBase64(str); + } else { + var binaryStr = window.atob(str); + for (var i = 0; i < binaryStr.length; i++) { + arr.push(binaryStr.charCodeAt(i)); + } + } + var hexValue = ""; + var hexValues = "0123456789ABCDEF"; + for (var j = 0; j < arr.length; j++) { + var t = arr[j]; + hexValue += hexValues[t >> 4]; + hexValue += hexValues[t & 0x0F]; + } + return hexValue; + }; + + var decodeBase64 = function (str) { + var binaryString = ""; + for (var i = 0; i < str.length; i++) { + var base65IndexValue = getBase64IndexValue(str[i]); + var binaryValue = ""; + if (base65IndexValue !== null) { + binaryValue = base65IndexValue.toString(2); + binaryString += addBase64Padding(binaryValue); + } + } + var byteArray = []; + var numberOfBytes = parseInt(binaryString.length / 8, 10); + for (i = 0; i < numberOfBytes; i++) { + var intValue = parseInt(binaryString.substring(i * 8, (i + 1) * 8), 2); + byteArray.push(intValue); + } + return byteArray; + }; + + var getBase64IndexValue = function (character) { + var asciiCode = character.charCodeAt(0); + var asciiOfA = 65; + var differenceBetweenZanda = 6; + if (asciiCode >= 65 && asciiCode <= 90) { // between "A" and "Z" inclusive + return asciiCode - asciiOfA; + } else if (asciiCode >= 97 && asciiCode <= 122) { // between 'a' and 'z' inclusive + return asciiCode - asciiOfA - differenceBetweenZanda; + } else if (asciiCode >= 48 && asciiCode <= 57) { // between '0' and '9' inclusive + return asciiCode + 4; + } else if (character == "+") { + return 62; + } else if (character == "/") { + return 63; + } else { + return null; + } + }; + + var addBase64Padding = function (binaryString) { + while (binaryString.length < 6) { + binaryString = "0" + binaryString; + } + return binaryString; + }; + + + // URI prefixes to generate smaller code. + var http = "http://"; + var w3org = http + "www.w3.org/"; // http://www.w3.org/ + + var xhtmlNS = w3org + "1999/xhtml"; // http://www.w3.org/1999/xhtml + var xmlnsNS = w3org + "2000/xmlns/"; // http://www.w3.org/2000/xmlns/ + var xmlNS = w3org + "XML/1998/namespace"; // http://www.w3.org/XML/1998/namespace + + var mozillaParserErroNS = http + "www.mozilla.org/newlayout/xml/parsererror.xml"; + + var hasLeadingOrTrailingWhitespace = function (text) { + /// Checks whether the specified string has leading or trailing spaces. + /// String to check. + /// true if text has any leading or trailing whitespace; false otherwise. + + var re = /(^\s)|(\s$)/; + return re.test(text); + }; + + var isWhitespace = function (text) { + /// Determines whether the specified text is empty or whitespace. + /// Value to inspect. + /// true if the text value is empty or all whitespace; false otherwise. + + var ws = /^\s*$/; + return text === null || ws.test(text); + }; + + var isWhitespacePreserveContext = function (domElement) { + /// Determines whether the specified element has xml:space='preserve' applied. + /// Element to inspect. + /// Whether xml:space='preserve' is in effect. + + while (domElement !== null && domElement.nodeType === 1) { + var val = xmlAttributeValue(domElement, "space", xmlNS); + if (val === "preserve") { + return true; + } else if (val === "default") { + break; + } else { + domElement = domElement.parentNode; + } + } + + return false; + }; + + var isXmlNSDeclaration = function (domAttribute) { + /// Determines whether the attribute is a XML namespace declaration. + /// Element to inspect. + /// + /// True if the attribute is a namespace declaration (its name is 'xmlns' or starts with 'xmlns:'; false otherwise. + /// + + var nodeName = domAttribute.nodeName; + return nodeName == "xmlns" || nodeName.indexOf("xmlns:") === 0; + }; + + var safeSetProperty = function (obj, name, value) { + /// Safely set as property in an object by invoking obj.setProperty. + /// Object that exposes a setProperty method. + /// Property name. + /// Property value. + + try { + obj.setProperty(name, value); + } catch (_) { } + }; + + var msXmlDom3 = function () { + /// Creates an configures new MSXML 3.0 ActiveX object. + /// + /// This function throws any exception that occurs during the creation + /// of the MSXML 3.0 ActiveX object. + /// New MSXML 3.0 ActiveX object. + + var msxml3 = activeXObject("Msxml2.DOMDocument.3.0"); + if (msxml3) { + safeSetProperty(msxml3, "ProhibitDTD", true); + safeSetProperty(msxml3, "MaxElementDepth", 256); + safeSetProperty(msxml3, "AllowDocumentFunction", false); + safeSetProperty(msxml3, "AllowXsltScript", false); + } + return msxml3; + }; + + var msXmlDom = function () { + /// Creates an configures new MSXML 6.0 or MSXML 3.0 ActiveX object. + /// + /// This function will try to create a new MSXML 6.0 ActiveX object. If it fails then + /// it will fallback to create a new MSXML 3.0 ActiveX object. Any exception that + /// happens during the creation of the MSXML 6.0 will be handled by the function while + /// the ones that happend during the creation of the MSXML 3.0 will be thrown. + /// New MSXML 3.0 ActiveX object. + + try { + var msxml = activeXObject("Msxml2.DOMDocument.6.0"); + if (msxml) { + msxml.async = true; + } + return msxml; + } catch (_) { + return msXmlDom3(); + } + }; + + var msXmlParse = function (text) { + /// Parses an XML string using the MSXML DOM. + /// + /// This function throws any exception that occurs during the creation + /// of the MSXML ActiveX object. It also will throw an exception + /// in case of a parsing error. + /// New MSXML DOMDocument node representing the parsed XML string. + + var dom = msXmlDom(); + if (!dom) { + return null; + } + + dom.loadXML(text); + var parseError = dom.parseError; + if (parseError.errorCode !== 0) { + xmlThrowParserError(parseError.reason, parseError.srcText, text); + } + return dom; + }; + + var xmlThrowParserError = function (exceptionOrReason, srcText, errorXmlText) { + /// Throws a new exception containing XML parsing error information. + /// + /// String indicatin the reason of the parsing failure or + /// Object detailing the parsing error. + /// + /// + /// String indicating the part of the XML string that caused the parsing error. + /// + /// XML string for wich the parsing failed. + + if (typeof exceptionOrReason === "string") { + exceptionOrReason = { message: exceptionOrReason }; + } + throw extend(exceptionOrReason, { srcText: srcText || "", errorXmlText: errorXmlText || "" }); + }; + + var xmlParse = function (text) { + /// Returns an XML DOM document from the specified text. + /// Document text. + /// XML DOM document. + /// This function will throw an exception in case of a parse error. + + var domParser = window.DOMParser && new window.DOMParser(); + var dom; + + if (!domParser) { + dom = msXmlParse(text); + if (!dom) { + xmlThrowParserError("XML DOM parser not supported"); + } + return dom; + } + + try { + dom = domParser.parseFromString(text, "text/xml"); + } catch (e) { + xmlThrowParserError(e, "", text); + } + + var element = dom.documentElement; + var nsURI = element.namespaceURI; + var localName = xmlLocalName(element); + + // Firefox reports errors by returing the DOM for an xml document describing the problem. + if (localName === "parsererror" && nsURI === mozillaParserErroNS) { + var srcTextElement = xmlFirstChildElement(element, mozillaParserErroNS, "sourcetext"); + var srcText = srcTextElement ? xmlNodeValue(srcTextElement) : ""; + xmlThrowParserError(xmlInnerText(element) || "", srcText, text); + } + + // Chrome (and maybe other webkit based browsers) report errors by injecting a header with an error message. + // The error may be localized, so instead we simply check for a header as the + // top element or descendant child of the document. + if (localName === "h3" && nsURI === xhtmlNS || xmlFirstDescendantElement(element, xhtmlNS, "h3")) { + var reason = ""; + var siblings = []; + var cursor = element.firstChild; + while (cursor) { + if (cursor.nodeType === 1) { + reason += xmlInnerText(cursor) || ""; + } + siblings.push(cursor.nextSibling); + cursor = cursor.firstChild || siblings.shift(); + } + reason += xmlInnerText(element) || ""; + xmlThrowParserError(reason, "", text); + } + + return dom; + }; + + var xmlQualifiedName = function (prefix, name) { + /// Builds a XML qualified name string in the form of "prefix:name". + /// Prefix string. + /// Name string to qualify with the prefix. + /// Qualified name. + + return prefix ? prefix + ":" + name : name; + }; + + var xmlAppendText = function (domNode, textNode) { + /// Appends a text node into the specified DOM element node. + /// DOM node for the element. + /// Text to append as a child of element. + if (hasLeadingOrTrailingWhitespace(textNode.data)) { + var attr = xmlAttributeNode(domNode, xmlNS, "space"); + if (!attr) { + attr = xmlNewAttribute(domNode.ownerDocument, xmlNS, xmlQualifiedName("xml", "space")); + xmlAppendChild(domNode, attr); + } + attr.value = "preserve"; + } + domNode.appendChild(textNode); + return domNode; + }; + + var xmlAttributes = function (element, onAttributeCallback) { + /// Iterates through the XML element's attributes and invokes the callback function for each one. + /// Wrapped element to iterate over. + /// Callback function to invoke with wrapped attribute nodes. + + var attributes = element.attributes; + var i, len; + for (i = 0, len = attributes.length; i < len; i++) { + onAttributeCallback(attributes.item(i)); + } + }; + + var xmlAttributeValue = function (domNode, localName, nsURI) { + /// Returns the value of a DOM element's attribute. + /// DOM node for the owning element. + /// Local name of the attribute. + /// Namespace URI of the attribute. + /// The attribute value, null if not found. + + var attribute = xmlAttributeNode(domNode, localName, nsURI); + return attribute ? xmlNodeValue(attribute) : null; + }; + + var xmlAttributeNode = function (domNode, localName, nsURI) { + /// Gets an attribute node from a DOM element. + /// DOM node for the owning element. + /// Local name of the attribute. + /// Namespace URI of the attribute. + /// The attribute node, null if not found. + + var attributes = domNode.attributes; + if (attributes.getNamedItemNS) { + return attributes.getNamedItemNS(nsURI || null, localName); + } + + return attributes.getQualifiedItem(localName, nsURI) || null; + }; + + var xmlBaseURI = function (domNode, baseURI) { + /// Gets the value of the xml:base attribute on the specified element. + /// Element to get xml:base attribute value from. + /// Base URI used to normalize the value of the xml:base attribute. + /// Value of the xml:base attribute if found; the baseURI or null otherwise. + + var base = xmlAttributeNode(domNode, "base", xmlNS); + return (base ? normalizeURI(base.value, baseURI) : baseURI) || null; + }; + + + var xmlChildElements = function (domNode, onElementCallback) { + /// Iterates through the XML element's child DOM elements and invokes the callback function for each one. + /// DOM Node containing the DOM elements to iterate over. + /// Callback function to invoke for each child DOM element. + + xmlTraverse(domNode, /*recursive*/false, function (child) { + if (child.nodeType === 1) { + onElementCallback(child); + } + // continue traversing. + return true; + }); + }; + + var xmlFindElementByPath = function (root, namespaceURI, path) { + /// Gets the descendant element under root that corresponds to the specified path and namespace URI. + /// DOM element node from which to get the descendant element. + /// The namespace URI of the element to match. + /// Path to the desired descendant element. + /// The element specified by path and namespace URI. + /// + /// All the elements in the path are matched against namespaceURI. + /// The function will stop searching on the first element that doesn't match the namespace and the path. + /// + + var parts = path.split("/"); + var i, len; + for (i = 0, len = parts.length; i < len; i++) { + root = root && xmlFirstChildElement(root, namespaceURI, parts[i]); + } + return root || null; + }; + + var xmlFindNodeByPath = function (root, namespaceURI, path) { + /// Gets the DOM element or DOM attribute node under root that corresponds to the specified path and namespace URI. + /// DOM element node from which to get the descendant node. + /// The namespace URI of the node to match. + /// Path to the desired descendant node. + /// The node specified by path and namespace URI. + /// + /// This function will traverse the path and match each node associated to a path segement against the namespace URI. + /// The traversal stops when the whole path has been exahusted or a node that doesn't belogong the specified namespace is encountered. + /// + /// The last segment of the path may be decorated with a starting @ character to indicate that the desired node is a DOM attribute. + /// + + var lastSegmentStart = path.lastIndexOf("/"); + var nodePath = path.substring(lastSegmentStart + 1); + var parentPath = path.substring(0, lastSegmentStart); + + var node = parentPath ? xmlFindElementByPath(root, namespaceURI, parentPath) : root; + if (node) { + if (nodePath.charAt(0) === "@") { + return xmlAttributeNode(node, nodePath.substring(1), namespaceURI); + } + return xmlFirstChildElement(node, namespaceURI, nodePath); + } + return null; + }; + + var xmlFirstChildElement = function (domNode, namespaceURI, localName) { + /// Returns the first child DOM element under the specified DOM node that matches the specified namespace URI and local name. + /// DOM node from which the child DOM element is going to be retrieved. + /// The namespace URI of the element to match. + /// Name of the element to match. + /// The node's first child DOM element that matches the specified namespace URI and local name; null otherwise. + + return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/false); + }; + + var xmlFirstDescendantElement = function (domNode, namespaceURI, localName) { + /// Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name. + /// DOM node from which the descendant DOM element is going to be retrieved. + /// The namespace URI of the element to match. + /// Name of the element to match. + /// The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise. + + if (domNode.getElementsByTagNameNS) { + var result = domNode.getElementsByTagNameNS(namespaceURI, localName); + return result.length > 0 ? result[0] : null; + } + return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/true); + }; + + var xmlFirstElementMaybeRecursive = function (domNode, namespaceURI, localName, recursive) { + /// Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name. + /// DOM node from which the descendant DOM element is going to be retrieved. + /// The namespace URI of the element to match. + /// Name of the element to match. + /// + /// True if the search should include all the descendants of the DOM node. + /// False if the search should be scoped only to the direct children of the DOM node. + /// + /// The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise. + + var firstElement = null; + xmlTraverse(domNode, recursive, function (child) { + if (child.nodeType === 1) { + var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(child) === namespaceURI; + var isExpectedNodeName = !localName || xmlLocalName(child) === localName; + + if (isExpectedNamespace && isExpectedNodeName) { + firstElement = child; + } + } + return firstElement === null; + }); + return firstElement; + }; + + var xmlInnerText = function (xmlElement) { + /// Gets the concatenated value of all immediate child text and CDATA nodes for the specified element. + /// Element to get values for. + /// Text for all direct children. + + var result = null; + var root = (xmlElement.nodeType === 9 && xmlElement.documentElement) ? xmlElement.documentElement : xmlElement; + var whitespaceAlreadyRemoved = root.ownerDocument.preserveWhiteSpace === false; + var whitespacePreserveContext; + + xmlTraverse(root, false, function (child) { + if (child.nodeType === 3 || child.nodeType === 4) { + // isElementContentWhitespace indicates that this is 'ignorable whitespace', + // but it's not defined by all browsers, and does not honor xml:space='preserve' + // in some implementations. + // + // If we can't tell either way, we walk up the tree to figure out whether + // xml:space is set to preserve; otherwise we discard pure-whitespace. + // + // For example 1. The space between and is usually 'ignorable'. + var text = xmlNodeValue(child); + var shouldInclude = whitespaceAlreadyRemoved || !isWhitespace(text); + if (!shouldInclude) { + // Walk up the tree to figure out whether we are in xml:space='preserve' context + // for the cursor (needs to happen only once). + if (whitespacePreserveContext === undefined) { + whitespacePreserveContext = isWhitespacePreserveContext(root); + } + + shouldInclude = whitespacePreserveContext; + } + + if (shouldInclude) { + if (!result) { + result = text; + } else { + result += text; + } + } + } + // Continue traversing? + return true; + }); + return result; + }; + + var xmlLocalName = function (domNode) { + /// Returns the localName of a XML node. + /// DOM node to get the value from. + /// localName of domNode. + + return domNode.localName || domNode.baseName; + }; + + var xmlNamespaceURI = function (domNode) { + /// Returns the namespace URI of a XML node. + /// DOM node to get the value from. + /// Namespace URI of domNode. + + return domNode.namespaceURI || null; + }; + + var xmlNodeValue = function (domNode) { + /// Returns the value or the inner text of a XML node. + /// DOM node to get the value from. + /// Value of the domNode or the inner text if domNode represents a DOM element node. + + if (domNode.nodeType === 1) { + return xmlInnerText(domNode); + } + return domNode.nodeValue; + }; + + var xmlTraverse = function (domNode, recursive, onChildCallback) { + /// Walks through the descendants of the domNode and invokes a callback for each node. + /// DOM node whose descendants are going to be traversed. + /// + /// True if the traversal should include all the descenants of the DOM node. + /// False if the traversal should be scoped only to the direct children of the DOM node. + /// + /// Namespace URI of node. + + var subtrees = []; + var child = domNode.firstChild; + var proceed = true; + while (child && proceed) { + proceed = onChildCallback(child); + if (proceed) { + if (recursive && child.firstChild) { + subtrees.push(child.firstChild); + } + child = child.nextSibling || subtrees.shift(); + } + } + }; + + var xmlSiblingElement = function (domNode, namespaceURI, localName) { + /// Returns the next sibling DOM element of the specified DOM node. + /// DOM node from which the next sibling is going to be retrieved. + /// The namespace URI of the element to match. + /// Name of the element to match. + /// The node's next sibling DOM element, null if there is none. + + var sibling = domNode.nextSibling; + while (sibling) { + if (sibling.nodeType === 1) { + var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(sibling) === namespaceURI; + var isExpectedNodeName = !localName || xmlLocalName(sibling) === localName; + + if (isExpectedNamespace && isExpectedNodeName) { + return sibling; + } + } + sibling = sibling.nextSibling; + } + return null; + }; + + var xmlDom = function () { + /// Creates a new empty DOM document node. + /// New DOM document node. + /// + /// This function will first try to create a native DOM document using + /// the browsers createDocument function. If the browser doesn't + /// support this but supports ActiveXObject, then an attempt to create + /// an MSXML 6.0 DOM will be made. If this attempt fails too, then an attempt + /// for creating an MXSML 3.0 DOM will be made. If this last attemp fails or + /// the browser doesn't support ActiveXObject then an exception will be thrown. + /// + + var implementation = window.document.implementation; + return (implementation && implementation.createDocument) ? + implementation.createDocument(null, null, null) : + msXmlDom(); + }; + + var xmlAppendChildren = function (parent, children) { + /// Appends a collection of child nodes or string values to a parent DOM node. + /// DOM node to which the children will be appended. + /// Array containing DOM nodes or string values that will be appended to the parent. + /// The parent with the appended children or string values. + /// + /// If a value in the children collection is a string, then a new DOM text node is going to be created + /// for it and then appended to the parent. + /// + + if (!isArray(children)) { + return xmlAppendChild(parent, children); + } + + var i, len; + for (i = 0, len = children.length; i < len; i++) { + children[i] && xmlAppendChild(parent, children[i]); + } + return parent; + }; + + var xmlAppendChild = function (parent, child) { + /// Appends a child node or a string value to a parent DOM node. + /// DOM node to which the child will be appended. + /// Child DOM node or string value to append to the parent. + /// The parent with the appended child or string value. + /// + /// If child is a string value, then a new DOM text node is going to be created + /// for it and then appended to the parent. + /// + + if (child) { + if (typeof child === "string") { + return xmlAppendText(parent, xmlNewText(parent.ownerDocument, child)); + } + if (child.nodeType === 2) { + parent.setAttributeNodeNS ? parent.setAttributeNodeNS(child) : parent.setAttributeNode(child); + } else { + parent.appendChild(child); + } + } + return parent; + }; + + var xmlNewAttribute = function (dom, namespaceURI, qualifiedName, value) { + /// Creates a new DOM attribute node. + /// DOM document used to create the attribute. + /// Namespace prefix. + /// Namespace URI. + /// DOM attribute node for the namespace declaration. + + var attribute = + dom.createAttributeNS && dom.createAttributeNS(namespaceURI, qualifiedName) || + dom.createNode(2, qualifiedName, namespaceURI || undefined); + + attribute.value = value || ""; + return attribute; + }; + + var xmlNewElement = function (dom, nampespaceURI, qualifiedName, children) { + /// Creates a new DOM element node. + /// DOM document used to create the DOM element. + /// Namespace URI of the new DOM element. + /// Qualified name in the form of "prefix:name" of the new DOM element. + /// + /// Collection of child DOM nodes or string values that are going to be appended to the new DOM element. + /// + /// New DOM element. + /// + /// If a value in the children collection is a string, then a new DOM text node is going to be created + /// for it and then appended to the new DOM element. + /// + + var element = + dom.createElementNS && dom.createElementNS(nampespaceURI, qualifiedName) || + dom.createNode(1, qualifiedName, nampespaceURI || undefined); + + return xmlAppendChildren(element, children || []); + }; + + var xmlNewNSDeclaration = function (dom, namespaceURI, prefix) { + /// Creates a namespace declaration attribute. + /// DOM document used to create the attribute. + /// Namespace URI. + /// Namespace prefix. + /// DOM attribute node for the namespace declaration. + + return xmlNewAttribute(dom, xmlnsNS, xmlQualifiedName("xmlns", prefix), namespaceURI); + }; + + var xmlNewFragment = function (dom, text) { + /// Creates a new DOM document fragment node for the specified xml text. + /// DOM document from which the fragment node is going to be created. + /// XML text to be represented by the XmlFragment. + /// New DOM document fragment object. + + var value = "" + text + ""; + var tempDom = xmlParse(value); + var tempRoot = tempDom.documentElement; + var imported = ("importNode" in dom) ? dom.importNode(tempRoot, true) : tempRoot; + var fragment = dom.createDocumentFragment(); + + var importedChild = imported.firstChild; + while (importedChild) { + fragment.appendChild(importedChild); + importedChild = importedChild.nextSibling; + } + return fragment; + }; + + var xmlNewText = function (dom, text) { + /// Creates new DOM text node. + /// DOM document used to create the text node. + /// Text value for the DOM text node. + /// DOM text node. + + return dom.createTextNode(text); + }; + + var xmlNewNodeByPath = function (dom, root, namespaceURI, prefix, path) { + /// Creates a new DOM element or DOM attribute node as specified by path and appends it to the DOM tree pointed by root. + /// DOM document used to create the new node. + /// DOM element node used as root of the subtree on which the new nodes are going to be created. + /// Namespace URI of the new DOM element or attribute. + /// Prefix used to qualify the name of the new DOM element or attribute. + /// Path string describing the location of the new DOM element or attribute from the root element. + /// DOM element or attribute node for the last segment of the path. + /// + /// This function will traverse the path and will create a new DOM element with the specified namespace URI and prefix + /// for each segment that doesn't have a matching element under root. + /// + /// The last segment of the path may be decorated with a starting @ character. In this case a new DOM attribute node + /// will be created. + /// + + var name = ""; + var parts = path.split("/"); + var xmlFindNode = xmlFirstChildElement; + var xmlNewNode = xmlNewElement; + var xmlNode = root; + + var i, len; + for (i = 0, len = parts.length; i < len; i++) { + name = parts[i]; + if (name.charAt(0) === "@") { + name = name.substring(1); + xmlFindNode = xmlAttributeNode; + xmlNewNode = xmlNewAttribute; + } + + var childNode = xmlFindNode(xmlNode, namespaceURI, name); + if (!childNode) { + childNode = xmlNewNode(dom, namespaceURI, xmlQualifiedName(prefix, name)); + xmlAppendChild(xmlNode, childNode); + } + xmlNode = childNode; + } + return xmlNode; + }; + + var xmlSerialize = function (domNode) { + /// + /// Returns the text representation of the document to which the specified node belongs. + /// + /// Wrapped element in the document to serialize. + /// Serialized document. + + var xmlSerializer = window.XMLSerializer; + if (xmlSerializer) { + var serializer = new xmlSerializer(); + return serializer.serializeToString(domNode); + } + + if (domNode.xml) { + return domNode.xml; + } + + throw { message: "XML serialization unsupported" }; + }; + + var xmlSerializeDescendants = function (domNode) { + /// Returns the XML representation of the all the descendants of the node. + /// Node to serialize. + /// The XML representation of all the descendants of the node. + + var children = domNode.childNodes; + var i, len = children.length; + if (len === 0) { + return ""; + } + + // Some implementations of the XMLSerializer don't deal very well with fragments that + // don't have a DOMElement as their first child. The work around is to wrap all the + // nodes in a dummy root node named "c", serialize it and then just extract the text between + // the and the substrings. + + var dom = domNode.ownerDocument; + var fragment = dom.createDocumentFragment(); + var fragmentRoot = dom.createElement("c"); + + fragment.appendChild(fragmentRoot); + // Move the children to the fragment tree. + for (i = 0; i < len; i++) { + fragmentRoot.appendChild(children[i]); + } + + var xml = xmlSerialize(fragment); + xml = xml.substr(3, xml.length - 7); + + // Move the children back to the original dom tree. + for (i = 0; i < len; i++) { + domNode.appendChild(fragmentRoot.childNodes[i]); + } + + return xml; + }; + + var xmlSerializeNode = function (domNode) { + /// Returns the XML representation of the node and all its descendants. + /// Node to serialize. + /// The XML representation of the node and all its descendants. + + var xml = domNode.xml; + if (xml !== undefined) { + return xml; + } + + if (window.XMLSerializer) { + var serializer = new window.XMLSerializer(); + return serializer.serializeToString(domNode); + } + + throw { message: "XML serialization unsupported" }; + }; + + + + + var forwardCall = function (thisValue, name, returnValue) { + /// Creates a new function to forward a call. + /// Value to use as the 'this' object. + /// Name of function to forward to. + /// Return value for the forward call (helps keep identity when chaining calls). + /// A new function that will forward a call. + + return function () { + thisValue[name].apply(thisValue, arguments); + return returnValue; + }; + }; + + var DjsDeferred = function () { + /// Initializes a new DjsDeferred object. + /// + /// Compability Note A - Ordering of callbacks through chained 'then' invocations + /// + /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A + /// implies that .then() returns a distinct object. + //// + /// For compatibility with http://api.jquery.com/category/deferred-object/ + /// we return this same object. This affects ordering, as + /// the jQuery version will fire callbacks in registration + /// order regardless of whether they occur on the result + /// or the original object. + /// + /// Compability Note B - Fulfillment value + /// + /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A + /// implies that the result of a success callback is the + /// fulfillment value of the object and is received by + /// other success callbacks that are chained. + /// + /// For compatibility with http://api.jquery.com/category/deferred-object/ + /// we disregard this value instead. + /// + + this._arguments = undefined; + this._done = undefined; + this._fail = undefined; + this._resolved = false; + this._rejected = false; + }; + + DjsDeferred.prototype = { + then: function (fulfilledHandler, errorHandler /*, progressHandler */) { + /// Adds success and error callbacks for this deferred object. + /// Success callback. + /// Error callback. + /// See Compatibility Note A. + + if (fulfilledHandler) { + if (!this._done) { + this._done = [fulfilledHandler]; + } else { + this._done.push(fulfilledHandler); + } + } + + if (errorHandler) { + if (!this._fail) { + this._fail = [errorHandler]; + } else { + this._fail.push(errorHandler); + } + } + + //// See Compatibility Note A in the DjsDeferred constructor. + //// if (!this._next) { + //// this._next = createDeferred(); + //// } + //// return this._next.promise(); + + if (this._resolved) { + this.resolve.apply(this, this._arguments); + } else if (this._rejected) { + this.reject.apply(this, this._arguments); + } + + return this; + }, + + resolve: function (/* args */) { + /// Invokes success callbacks for this deferred object. + /// All arguments are forwarded to success callbacks. + + + if (this._done) { + var i, len; + for (i = 0, len = this._done.length; i < len; i++) { + //// See Compability Note B - Fulfillment value. + //// var nextValue = + this._done[i].apply(null, arguments); + } + + //// See Compatibility Note A in the DjsDeferred constructor. + //// this._next.resolve(nextValue); + //// delete this._next; + + this._done = undefined; + this._resolved = false; + this._arguments = undefined; + } else { + this._resolved = true; + this._arguments = arguments; + } + }, + + reject: function (/* args */) { + /// Invokes error callbacks for this deferred object. + /// All arguments are forwarded to error callbacks. + if (this._fail) { + var i, len; + for (i = 0, len = this._fail.length; i < len; i++) { + this._fail[i].apply(null, arguments); + } + + this._fail = undefined; + this._rejected = false; + this._arguments = undefined; + } else { + this._rejected = true; + this._arguments = arguments; + } + }, + + promise: function () { + /// Returns a version of this object that has only the read-only methods available. + /// An object with only the promise object. + + var result = {}; + result.then = forwardCall(this, "then", result); + return result; + } + }; + + var createDeferred = function () { + /// Creates a deferred object. + /// + /// A new deferred object. If jQuery is installed, then a jQuery + /// Deferred object is returned, which provides a superset of features. + /// + + if (window.jQuery && window.jQuery.Deferred) { + return new window.jQuery.Deferred(); + } else { + return new DjsDeferred(); + } + }; + + + + + var dataItemTypeName = function (value, metadata) { + /// Gets the type name of a data item value that belongs to a feed, an entry, a complex type property, or a collection property. + /// Value of the data item from which the type name is going to be retrieved. + /// Object containing metadata about the data tiem. + /// + /// This function will first try to get the type name from the data item's value itself if it is an object with a __metadata property; otherwise + /// it will try to recover it from the metadata. If both attempts fail, it will return null. + /// + /// Data item type name; null if the type name cannot be found within the value or the metadata + + var valueTypeName = ((value && value.__metadata) || {}).type; + return valueTypeName || (metadata ? metadata.type : null); + }; + + var EDM = "Edm."; + var EDM_BINARY = EDM + "Binary"; + var EDM_BOOLEAN = EDM + "Boolean"; + var EDM_BYTE = EDM + "Byte"; + var EDM_DATETIME = EDM + "DateTime"; + var EDM_DATETIMEOFFSET = EDM + "DateTimeOffset"; + var EDM_DECIMAL = EDM + "Decimal"; + var EDM_DOUBLE = EDM + "Double"; + var EDM_GUID = EDM + "Guid"; + var EDM_INT16 = EDM + "Int16"; + var EDM_INT32 = EDM + "Int32"; + var EDM_INT64 = EDM + "Int64"; + var EDM_SBYTE = EDM + "SByte"; + var EDM_SINGLE = EDM + "Single"; + var EDM_STRING = EDM + "String"; + var EDM_TIME = EDM + "Time"; + + var EDM_GEOGRAPHY = EDM + "Geography"; + var EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY + "Point"; + var EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY + "LineString"; + var EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY + "Polygon"; + var EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY + "Collection"; + var EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY + "MultiPolygon"; + var EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY + "MultiLineString"; + var EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY + "MultiPoint"; + + var EDM_GEOMETRY = EDM + "Geometry"; + var EDM_GEOMETRY_POINT = EDM_GEOMETRY + "Point"; + var EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY + "LineString"; + var EDM_GEOMETRY_POLYGON = EDM_GEOMETRY + "Polygon"; + var EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY + "Collection"; + var EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY + "MultiPolygon"; + var EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY + "MultiLineString"; + var EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY + "MultiPoint"; + + var GEOJSON_POINT = "Point"; + var GEOJSON_LINESTRING = "LineString"; + var GEOJSON_POLYGON = "Polygon"; + var GEOJSON_MULTIPOINT = "MultiPoint"; + var GEOJSON_MULTILINESTRING = "MultiLineString"; + var GEOJSON_MULTIPOLYGON = "MultiPolygon"; + var GEOJSON_GEOMETRYCOLLECTION = "GeometryCollection"; + + var primitiveEdmTypes = [ + EDM_STRING, + EDM_INT32, + EDM_INT64, + EDM_BOOLEAN, + EDM_DOUBLE, + EDM_SINGLE, + EDM_DATETIME, + EDM_DATETIMEOFFSET, + EDM_TIME, + EDM_DECIMAL, + EDM_GUID, + EDM_BYTE, + EDM_INT16, + EDM_SBYTE, + EDM_BINARY + ]; + + var geometryEdmTypes = [ + EDM_GEOMETRY, + EDM_GEOMETRY_POINT, + EDM_GEOMETRY_LINESTRING, + EDM_GEOMETRY_POLYGON, + EDM_GEOMETRY_COLLECTION, + EDM_GEOMETRY_MULTIPOLYGON, + EDM_GEOMETRY_MULTILINESTRING, + EDM_GEOMETRY_MULTIPOINT + ]; + + var geographyEdmTypes = [ + EDM_GEOGRAPHY, + EDM_GEOGRAPHY_POINT, + EDM_GEOGRAPHY_LINESTRING, + EDM_GEOGRAPHY_POLYGON, + EDM_GEOGRAPHY_COLLECTION, + EDM_GEOGRAPHY_MULTIPOLYGON, + EDM_GEOGRAPHY_MULTILINESTRING, + EDM_GEOGRAPHY_MULTIPOINT + ]; + + var forEachSchema = function (metadata, callback) { + /// Invokes a function once per schema in metadata. + /// Metadata store; one of edmx, schema, or an array of any of them. + /// Callback function to invoke once per schema. + /// + /// The first truthy value to be returned from the callback; null or the last falsy value otherwise. + /// + + if (!metadata) { + return null; + } + + if (isArray(metadata)) { + var i, len, result; + for (i = 0, len = metadata.length; i < len; i++) { + result = forEachSchema(metadata[i], callback); + if (result) { + return result; + } + } + + return null; + } else { + if (metadata.dataServices) { + return forEachSchema(metadata.dataServices.schema, callback); + } + + return callback(metadata); + } + }; + + var formatMilliseconds = function (ms, ns) { + /// Formats a millisecond and a nanosecond value into a single string. + /// Number of milliseconds to format. + /// Number of nanoseconds to format. + /// Formatted text. + /// If the value is already as string it's returned as-is. + + // Avoid generating milliseconds if not necessary. + if (ms === 0) { + ms = ""; + } else { + ms = "." + formatNumberWidth(ms.toString(), 3); + } + if (ns > 0) { + if (ms === "") { + ms = ".000"; + } + ms += formatNumberWidth(ns.toString(), 4); + } + return ms; + }; + + var formatDateTimeOffset = function (value) { + /// Formats a DateTime or DateTimeOffset value a string. + /// Value to format. + /// Formatted text. + /// If the value is already as string it's returned as-is. + + if (typeof value === "string") { + return value; + } + + var hasOffset = isDateTimeOffset(value); + var offset = getCanonicalTimezone(value.__offset); + if (hasOffset && offset !== "Z") { + // We're about to change the value, so make a copy. + value = new Date(value.valueOf()); + + var timezone = parseTimezone(offset); + var hours = value.getUTCHours() + (timezone.d * timezone.h); + var minutes = value.getUTCMinutes() + (timezone.d * timezone.m); + + value.setUTCHours(hours, minutes); + } else if (!hasOffset) { + // Don't suffix a 'Z' for Edm.DateTime values. + offset = ""; + } + + var year = value.getUTCFullYear(); + var month = value.getUTCMonth() + 1; + var sign = ""; + if (year <= 0) { + year = -(year - 1); + sign = "-"; + } + + var ms = formatMilliseconds(value.getUTCMilliseconds(), value.__ns); + + return sign + + formatNumberWidth(year, 4) + "-" + + formatNumberWidth(month, 2) + "-" + + formatNumberWidth(value.getUTCDate(), 2) + "T" + + formatNumberWidth(value.getUTCHours(), 2) + ":" + + formatNumberWidth(value.getUTCMinutes(), 2) + ":" + + formatNumberWidth(value.getUTCSeconds(), 2) + + ms + offset; + }; + + var formatDuration = function (value) { + /// Converts a duration to a string in xsd:duration format. + /// Object with ms and __edmType properties. + /// String representation of the time object in xsd:duration format. + + var ms = value.ms; + + var sign = ""; + if (ms < 0) { + sign = "-"; + ms = -ms; + } + + var days = Math.floor(ms / 86400000); + ms -= 86400000 * days; + var hours = Math.floor(ms / 3600000); + ms -= 3600000 * hours; + var minutes = Math.floor(ms / 60000); + ms -= 60000 * minutes; + var seconds = Math.floor(ms / 1000); + ms -= seconds * 1000; + + return sign + "P" + + formatNumberWidth(days, 2) + "DT" + + formatNumberWidth(hours, 2) + "H" + + formatNumberWidth(minutes, 2) + "M" + + formatNumberWidth(seconds, 2) + + formatMilliseconds(ms, value.ns) + "S"; + }; + + var formatNumberWidth = function (value, width, append) { + /// Formats the specified value to the given width. + /// Number to format (non-negative). + /// Minimum width for number. + /// Flag indicating if the value is padded at the beginning (false) or at the end (true). + /// Text representation. + var result = value.toString(10); + while (result.length < width) { + if (append) { + result += "0"; + } else { + result = "0" + result; + } + } + + return result; + }; + + var getCanonicalTimezone = function (timezone) { + /// Gets the canonical timezone representation. + /// Timezone representation. + /// An 'Z' string if the timezone is absent or 0; the timezone otherwise. + + return (!timezone || timezone === "Z" || timezone === "+00:00" || timezone === "-00:00") ? "Z" : timezone; + }; + + var getCollectionType = function (typeName) { + /// Gets the type of a collection type name. + /// Type name of the collection. + /// Type of the collection; null if the type name is not a collection type. + + if (typeof typeName === "string") { + var end = typeName.indexOf(")", 10); + if (typeName.indexOf("Collection(") === 0 && end > 0) { + return typeName.substring(11, end); + } + } + return null; + }; + + var invokeRequest = function (request, success, error, handler, httpClient, context) { + /// Sends a request containing OData payload to a server. + /// Object that represents the request to be sent.. + /// Callback for a successful read operation. + /// Callback for handling errors. + /// Handler for data serialization. + /// HTTP client layer. + /// Context used for processing the request + + return httpClient.request(request, function (response) { + try { + if (response.headers) { + normalizeHeaders(response.headers); + } + + if (response.data === undefined && response.statusCode !== 204) { + handler.read(response, context); + } + } catch (err) { + if (err.request === undefined) { + err.request = request; + } + if (err.response === undefined) { + err.response = response; + } + error(err); + return; + } + + success(response.data, response); + }, error); + }; + + var isBatch = function (value) { + /// Tests whether a value is a batch object in the library's internal representation. + /// Value to test. + /// True is the value is a batch object; false otherwise. + + return isComplex(value) && isArray(value.__batchRequests); + }; + + // Regular expression used for testing and parsing for a collection type. + var collectionTypeRE = /Collection\((.*)\)/; + + var isCollection = function (value, typeName) { + /// Tests whether a value is a collection value in the library's internal representation. + /// Value to test. + /// Type name of the value. This is used to disambiguate from a collection property value. + /// True is the value is a feed value; false otherwise. + + var colData = value && value.results || value; + return !!colData && + (isCollectionType(typeName)) || + (!typeName && isArray(colData) && !isComplex(colData[0])); + }; + + var isCollectionType = function (typeName) { + /// Checks whether the specified type name is a collection type. + /// Name of type to check. + /// True if the type is the name of a collection type; false otherwise. + return collectionTypeRE.test(typeName); + }; + + var isComplex = function (value) { + /// Tests whether a value is a complex type value in the library's internal representation. + /// Value to test. + /// True is the value is a complex type value; false otherwise. + + return !!value && + isObject(value) && + !isArray(value) && + !isDate(value); + }; + + var isDateTimeOffset = function (value) { + /// Checks whether a Date object is DateTimeOffset value + /// Value to check. + /// true if the value is a DateTimeOffset, false otherwise. + return (value.__edmType === "Edm.DateTimeOffset" || (!value.__edmType && value.__offset)); + }; + + var isDeferred = function (value) { + /// Tests whether a value is a deferred navigation property in the library's internal representation. + /// Value to test. + /// True is the value is a deferred navigation property; false otherwise. + + if (!value && !isComplex(value)) { + return false; + } + var metadata = value.__metadata || {}; + var deferred = value.__deferred || {}; + return !metadata.type && !!deferred.uri; + }; + + var isEntry = function (value) { + /// Tests whether a value is an entry object in the library's internal representation. + /// Value to test. + /// True is the value is an entry object; false otherwise. + + return isComplex(value) && value.__metadata && "uri" in value.__metadata; + }; + + var isFeed = function (value, typeName) { + /// Tests whether a value is a feed value in the library's internal representation. + /// Value to test. + /// Type name of the value. This is used to disambiguate from a collection property value. + /// True is the value is a feed value; false otherwise. + + var feedData = value && value.results || value; + return isArray(feedData) && ( + (!isCollectionType(typeName)) && + (isComplex(feedData[0])) + ); + }; + + var isGeographyEdmType = function (typeName) { + /// Checks whether the specified type name is a geography EDM type. + /// Name of type to check. + /// True if the type is a geography EDM type; false otherwise. + + return contains(geographyEdmTypes, typeName); + }; + + var isGeometryEdmType = function (typeName) { + /// Checks whether the specified type name is a geometry EDM type. + /// Name of type to check. + /// True if the type is a geometry EDM type; false otherwise. + + return contains(geometryEdmTypes, typeName); + }; + + var isNamedStream = function (value) { + /// Tests whether a value is a named stream value in the library's internal representation. + /// Value to test. + /// True is the value is a named stream; false otherwise. + + if (!value && !isComplex(value)) { + return false; + } + var metadata = value.__metadata; + var mediaResource = value.__mediaresource; + return !metadata && !!mediaResource && !!mediaResource.media_src; + }; + + var isPrimitive = function (value) { + /// Tests whether a value is a primitive type value in the library's internal representation. + /// Value to test. + /// + /// Date objects are considered primitive types by the library. + /// + /// True is the value is a primitive type value. + + return isDate(value) || + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean"; + }; + + var isPrimitiveEdmType = function (typeName) { + /// Checks whether the specified type name is a primitive EDM type. + /// Name of type to check. + /// True if the type is a primitive EDM type; false otherwise. + + return contains(primitiveEdmTypes, typeName); + }; + + var navigationPropertyKind = function (value, propertyModel) { + /// Gets the kind of a navigation property value. + /// Value of the navigation property. + /// + /// Object that describes the navigation property in an OData conceptual schema. + /// + /// + /// The returned string is as follows + /// + /// String value describing the kind of the navigation property; null if the kind cannot be determined. + + if (isDeferred(value)) { + return "deferred"; + } + if (isEntry(value)) { + return "entry"; + } + if (isFeed(value)) { + return "feed"; + } + if (propertyModel && propertyModel.relationship) { + if (value === null || value === undefined || !isFeed(value)) { + return "entry"; + } + return "feed"; + } + return null; + }; + + var lookupProperty = function (properties, name) { + /// Looks up a property by name. + /// Array of property objects as per EDM metadata. + /// Name to look for. + /// The property object; null if not found. + + return find(properties, function (property) { + return property.name === name; + }); + }; + + var lookupInMetadata = function (name, metadata, kind) { + /// Looks up a type object by name. + /// Name, possibly null or empty. + /// Metadata store; one of edmx, schema, or an array of any of them. + /// Kind of object to look for as per EDM metadata. + /// An type description if the name is found; null otherwise. + + return (name) ? forEachSchema(metadata, function (schema) { + return lookupInSchema(name, schema, kind); + }) : null; + }; + + var lookupEntitySet = function (entitySets, name) { + /// Looks up a entity set by name. + /// Array of entity set objects as per EDM metadata. + /// Name to look for. + /// The entity set object; null if not found. + + return find(entitySets, function (entitySet) { + return entitySet.name === name; + }); + }; + + var lookupComplexType = function (name, metadata) { + /// Looks up a complex type object by name. + /// Name, possibly null or empty. + /// Metadata store; one of edmx, schema, or an array of any of them. + /// A complex type description if the name is found; null otherwise. + + return lookupInMetadata(name, metadata, "complexType"); + }; + + var lookupEntityType = function (name, metadata) { + /// Looks up an entity type object by name. + /// Name, possibly null or empty. + /// Metadata store; one of edmx, schema, or an array of any of them. + /// An entity type description if the name is found; null otherwise. + + return lookupInMetadata(name, metadata, "entityType"); + }; + + var lookupDefaultEntityContainer = function (metadata) { + /// Looks up an + /// Name, possibly null or empty. + /// Metadata store; one of edmx, schema, or an array of any of them. + /// An entity container description if the name is found; null otherwise. + + return forEachSchema(metadata, function (schema) { + return find(schema.entityContainer, function (container) { + return parseBool(container.isDefaultEntityContainer); + }); + }); + }; + + var lookupEntityContainer = function (name, metadata) { + /// Looks up an entity container object by name. + /// Name, possibly null or empty. + /// Metadata store; one of edmx, schema, or an array of any of them. + /// An entity container description if the name is found; null otherwise. + + return lookupInMetadata(name, metadata, "entityContainer"); + }; + + var lookupFunctionImport = function (functionImports, name) { + /// Looks up a function import by name. + /// Array of function import objects as per EDM metadata. + /// Name to look for. + /// The entity set object; null if not found. + + return find(functionImports, function (functionImport) { + return functionImport.name === name; + }); + }; + + var lookupNavigationPropertyType = function (navigationProperty, metadata) { + /// Looks up the target entity type for a navigation property. + /// + /// + /// The entity type name for the specified property, null if not found. + + var result = null; + if (navigationProperty) { + var rel = navigationProperty.relationship; + var association = forEachSchema(metadata, function (schema) { + // The name should be the namespace qualified name in 'ns'.'type' format. + var nameOnly = removeNamespace(schema["namespace"], rel); + var associations = schema.association; + if (nameOnly && associations) { + var i, len; + for (i = 0, len = associations.length; i < len; i++) { + if (associations[i].name === nameOnly) { + return associations[i]; + } + } + } + return null; + }); + + if (association) { + var end = association.end[0]; + if (end.role !== navigationProperty.toRole) { + end = association.end[1]; + // For metadata to be valid, end.role === navigationProperty.toRole now. + } + result = end.type; + } + } + return result; + }; + + var lookupNavigationPropertyEntitySet = function (navigationProperty, sourceEntitySetName, metadata) { + /// Looks up the target entityset name for a navigation property. + /// + /// + /// The entityset name for the specified property, null if not found. + + if (navigationProperty) { + var rel = navigationProperty.relationship; + var associationSet = forEachSchema(metadata, function (schema) { + var containers = schema.entityContainer; + for (var i = 0; i < containers.length; i++) { + var associationSets = containers[i].associationSet; + if (associationSets) { + for (var j = 0; j < associationSets.length; j++) { + if (associationSets[j].association == rel) { + return associationSets[j]; + } + } + } + } + return null; + }); + if (associationSet && associationSet.end[0] && associationSet.end[1]) { + return (associationSet.end[0].entitySet == sourceEntitySetName) ? associationSet.end[1].entitySet : associationSet.end[0].entitySet; + } + } + return null; + }; + + var getEntitySetInfo = function (entitySetName, metadata) { + /// Gets the entitySet info, container name and functionImports for an entitySet + /// + /// + /// The info about the entitySet. + + var info = forEachSchema(metadata, function (schema) { + var containers = schem