Guest User

datajs with JSON Date format support for odata4j

a guest
Mar 2nd, 2012
897
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Copyright (c) Microsoft.  All rights reserved.
  2. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
  3. // files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
  4. // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
  5. // Software is furnished to do so, subject to the following conditions:
  6. //
  7. // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  8. //
  9. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  10. // WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  11. // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  12. // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  13.  
  14. // datajs.js
  15.  
  16. (function (window, undefined) {
  17.     if (!window.datajs) {
  18.         window.datajs = {};
  19.     }
  20.  
  21.     if (!window.OData) {
  22.         window.OData = {};
  23.     }
  24.  
  25.     var datajs = window.datajs;
  26.     var odata = window.OData;
  27.    
  28.  
  29.     // Provides an enumeration of possible kinds of payloads.
  30.     var PAYLOADTYPE_BATCH = "b";
  31.     var PAYLOADTYPE_COMPLEXTYPE = "c";
  32.     var PAYLOADTYPE_ENTRY = "entry";        // This is used when building the payload.
  33.     var PAYLOADTYPE_FEEDORLINKS = "f";
  34.     var PAYLOADTYPE_PRIMITIVETYPE = "p";
  35.     var PAYLOADTYPE_SVCDOC = "s";
  36.     var PAYLOADTYPE_UNKNOWN = "u";
  37.     var PAYLOADTYPE_NONE = "n";
  38.  
  39.     // Provides an enumeration of possible kinds of properties.
  40.     var PROPERTYKIND_COMPLEX = "c";
  41.     var PROPERTYKIND_DEFERRED = "d";
  42.     var PROPERTYKIND_INLINE = "i";
  43.     var PROPERTYKIND_PRIMITIVE = "p";
  44.     var PROPERTYKIND_NONE = "n";
  45.  
  46.     var assigned = function (value) {
  47.         /// <summary>Checks whether the specified value is different from null and undefined.</summary>
  48.         /// <param name="value" mayBeNull="true" optional="true">Value to check.</param>
  49.         /// <returns type="Boolean">true if the value is assigned; false otherwise.</returns>
  50.         return value !== null && value !== undefined;
  51.     };
  52.  
  53.     var contains = function (arr, item) {
  54.         /// <summary>Checks whether the specified item is in the array.</summary>
  55.         /// <param name="arr" type="Array" optional="false" mayBeNull="false">Array to check in.</param>
  56.         /// <param name="item">Item to look for.</param>
  57.         /// <returns type="Boolean">true if the item is contained, false otherwise.</returns>
  58.  
  59.         var i, len;
  60.         for (i = 0, len = arr.length; i < len; i++) {
  61.             if (arr[i] === item) {
  62.                 return true;
  63.             }
  64.         }
  65.  
  66.         return false;
  67.     };
  68.  
  69.     var defined = function (a, b) {
  70.         /// <summary>Given two values, picks the first one that is not undefined.</summary>
  71.         /// <param name="a">First value.</param>
  72.         /// <param name="b">Second value.</param>
  73.         /// <returns>a if it's a defined value; else b.</returns>
  74.         return (a !== undefined) ? a : b;
  75.     };
  76.  
  77.     var delay = function (callback) {
  78.         /// <summary>Delays the invocation of the specified function until execution unwinds.</summary>
  79.         /// <param name="callback" type="Function">Callback function.</param>
  80.         if (arguments.length === 1) {
  81.             window.setTimeout(callback, 0);
  82.             return;
  83.         }
  84.  
  85.         var args = Array.prototype.slice.call(arguments, 1);
  86.         window.setTimeout(function () {
  87.             callback.apply(this, args);
  88.         }, 0);
  89.     };
  90.  
  91.  
  92.     var forEachSchema = function (metadata, callback) {
  93.         /// <summary>Invokes a function once per schema in metadata.</summary>
  94.         /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
  95.         /// <param name="callback" type="Function">Callback function to invoke once per schema.</param>
  96.         /// <returns>
  97.         /// The first truthy value to be returned from the callback; null or the last falsy value otherwise.
  98.         /// </returns>
  99.  
  100.         if (!metadata) {
  101.             return null;
  102.         }
  103.  
  104.         if (isArray(metadata)) {
  105.             var i, len, result;
  106.             for (i = 0, len = metadata.length; i < len; i++) {
  107.                 result = forEachSchema(metadata[i], callback);
  108.                 if (result) {
  109.                     return result;
  110.                 }
  111.             }
  112.  
  113.             return null;
  114.         } else {
  115.             if (metadata.dataServices) {
  116.                 return forEachSchema(metadata.dataServices.schema, callback);
  117.             }
  118.  
  119.             return callback(metadata);
  120.         }
  121.     };
  122.  
  123.     var isDateTimeOffset = function (value) {
  124.         /// <summary>Checks whether a Date object is DateTimeOffset value</summary>
  125.         /// <param name="value" type="Date" mayBeNull="false">Value to check.</param>
  126.         /// <returns type="Boolean">true if the value is a DateTimeOffset, false otherwise.</returns>
  127.         return (value.__edmType === "Edm.DateTimeOffset" || (!value.__edmType && value.__offset));
  128.     };
  129.  
  130.     var formatMilliseconds = function (ms, ns) {
  131.         /// <summary>Formats a millisecond and a nanosecond value into a single string.</summary>
  132.         /// <param name="ms" type="Number" mayBeNull="false">Number of milliseconds to format.</param>
  133.         /// <param name="ns" type="Number" mayBeNull="false">Number of nanoseconds to format.</param>
  134.         /// <returns type="String">Formatted text.</returns>
  135.         /// <remarks>If the value is already as string it's returned as-is.</remarks>
  136.  
  137.         // Avoid generating milliseconds if not necessary.
  138.         if (ms === 0) {
  139.             ms = "";
  140.         } else {
  141.             ms = "." + formatNumberWidth(ms.toString(), 3);
  142.         }
  143.         if (ns > 0) {
  144.             if (ms === "") {
  145.                 ms = ".000";
  146.             }
  147.             ms += formatNumberWidth(ns.toString(), 4);
  148.         }
  149.         return ms;
  150.     }
  151.  
  152.     var formatDateTimeOffset = function (value) {
  153.         /// <summary>Formats a DateTime or DateTimeOffset value a string.</summary>
  154.         /// <param name="value" type="Date" mayBeNull="false">Value to format.</param>
  155.         /// <returns type="String">Formatted text.</returns>
  156.         /// <remarks>If the value is already as string it's returned as-is.</remarks>
  157.  
  158.         if (typeof value === "string") {
  159.             return value;
  160.         }
  161.  
  162.         var hasOffset = isDateTimeOffset(value);
  163.         var offset = getCanonicalTimezone(value.__offset);
  164.         if (hasOffset && offset !== "Z") {
  165.             // We're about to change the value, so make a copy.
  166.             value = new Date(value.valueOf());
  167.  
  168.             var timezone = parseTimezone(offset);
  169.             var hours = value.getUTCHours() + (timezone.d * timezone.h);
  170.             var minutes = value.getMinutes() + (timezone.d * timezone.m);
  171.  
  172.             value.setUTCHours(hours, minutes);
  173.         } else if (!hasOffset) {
  174.             // Don't suffix a 'Z' for Edm.DateTime values.
  175.             offset = "";
  176.         }
  177.  
  178.         var year = value.getUTCFullYear();
  179.         var month = value.getUTCMonth() + 1;
  180.         var sign = "";
  181.         if (year <= 0) {
  182.             year = -(year - 1);
  183.             sign = "-";
  184.         }
  185.  
  186.         var ms = formatMilliseconds(value.getUTCMilliseconds(), value.__ns);
  187.  
  188.         return sign +
  189.             formatNumberWidth(year, 4) + "-" +
  190.             formatNumberWidth(month, 2) + "-" +
  191.             formatNumberWidth(value.getUTCDate(), 2) + "T" +
  192.             formatNumberWidth(value.getUTCHours(), 2) + ":" +
  193.             formatNumberWidth(value.getUTCMinutes(), 2) + ":" +
  194.             formatNumberWidth(value.getUTCSeconds(), 2) +
  195.             ms + offset;
  196.     };
  197.  
  198.     var formatDuration = function (value) {
  199.         /// <summary>Converts a duration to a string in xsd:duration format.</summary>
  200.         /// <param name="value" type="Object">Object with ms and __edmType properties.</param>
  201.         /// <returns type="String">String representation of the time object in xsd:duration format.</returns>
  202.  
  203.         var ms = value.ms;
  204.  
  205.         var sign = "";
  206.         if (ms < 0) {
  207.             sign = "-";
  208.             ms = -ms;
  209.         }
  210.  
  211.         var days = Math.floor(ms / 86400000);
  212.         ms -= 86400000 * days;
  213.         var hours = Math.floor(ms / 3600000);
  214.         ms -= 3600000 * hours;
  215.         var minutes = Math.floor(ms / 60000);
  216.         ms -= 60000 * minutes;
  217.         var seconds = Math.floor(ms / 1000);
  218.         ms -= seconds * 1000;
  219.  
  220.         return sign + "P" +
  221.                formatNumberWidth(days, 2) + "DT" +
  222.                formatNumberWidth(hours, 2) + "H" +
  223.                formatNumberWidth(minutes, 2) + "M" +
  224.                formatNumberWidth(seconds, 2) +
  225.                formatMilliseconds(ms, value.ns) + "S";
  226.     };
  227.  
  228.     var formatNumberWidth = function (value, width, append) {
  229.         /// <summary>Formats the specified value to the given width.</summary>
  230.         /// <param name="value" type="Number">Number to format (non-negative).</param>
  231.         /// <param name="width" type="Number">Minimum width for number.</param>
  232.         /// <param name="append" type="Boolean">Flag indicating if the value is padded at the beginning (false) or at the end (true).</param>
  233.         /// <returns type="String">Text representation.</returns>
  234.         var result = value.toString(10);
  235.         while (result.length < width) {
  236.             if (append) {
  237.                 result += "0";
  238.             } else {
  239.                 result = "0" + result;
  240.             }
  241.         }
  242.  
  243.         return result;
  244.     };
  245.  
  246.     var getCanonicalTimezone = function (timezone) {
  247.         /// <summary>Gets the canonical timezone representation.</summary>
  248.         /// <param name="timezone" type="String">Timezone representation.</param>
  249.         /// <returns type="String">An 'Z' string if the timezone is absent or 0; the timezone otherwise.</returns>
  250.  
  251.         return (!timezone || timezone === "Z" || timezone === "+00:00" || timezone === "-00:00") ? "Z" : timezone;
  252.     };
  253.  
  254.     var invokeRequest = function (request, success, error, handler, httpClient, context) {
  255.         /// <summary>Sends a request containing OData payload to a server.</summary>
  256.         /// <param name="request">Object that represents the request to be sent..</param>
  257.         /// <param name="success">Callback for a successful read operation.</param>
  258.         /// <param name="error">Callback for handling errors.</param>
  259.         /// <param name="handler">Handler for data serialization.</param>
  260.         /// <param name="httpClient">HTTP client layer.</param>
  261.         /// <param name="context">Context used for processing the request</param>
  262.         return httpClient.request(request, function (response) {
  263.             try {
  264.                 if (response.headers) {
  265.                     normalizeHeaders(response.headers);
  266.                 }
  267.  
  268.                 if (response.data === undefined) {
  269.                     handler.read(response, context);
  270.                 }
  271.             } catch (err) {
  272.                 if (err.request === undefined) {
  273.                     err.request = request;
  274.                 }
  275.                 if (err.response === undefined) {
  276.                     err.response = response;
  277.                 }
  278.                 error(err);
  279.                 return;
  280.             }
  281.  
  282.             success(response.data, response);
  283.         }, error);
  284.     };
  285.  
  286.     var isArray = function (value) {
  287.         /// <summary>Checks whether the specified value is an array object.</summary>
  288.         /// <param name="value">Value to check.</param>
  289.         /// <returns type="Boolean">true if the value is an array object; false otherwise.</returns>
  290.  
  291.         return Object.prototype.toString.call(value) === "[object Array]";
  292.     };
  293.  
  294.     var isDate = function (value) {
  295.         /// <summary>Checks whether the specified value is a Date object.</summary>
  296.         /// <param name="value">Value to check.</param>
  297.         /// <returns type="Boolean">true if the value is a Date object; false otherwise.</returns>
  298.  
  299.         return Object.prototype.toString.call(value) === "[object Date]";
  300.     };
  301.  
  302.     var lookupProperty = function (properties, name) {
  303.         /// <summary>Looks up a property by name.</summary>
  304.         /// <param name="properties" type="Array" mayBeNull="true">Array of property objects as per EDM metadata.</param>
  305.         /// <param name="name" type="String">Name to look for.</param>
  306.         /// <returns type="Object">The property object; null if not found.</returns>
  307.  
  308.         if (properties) {
  309.             var i, len;
  310.             for (i = 0, len = properties.length; i < len; i++) {
  311.                 if (properties[i].name === name) {
  312.                     return properties[i];
  313.                 }
  314.             }
  315.         }
  316.  
  317.         return null;
  318.     };
  319.  
  320.     var lookupTypeInMetadata = function (name, metadata, kind) {
  321.         /// <summary>Looks up a type object by name.</summary>
  322.         /// <param name="name" type="String">Name, possibly null or empty.</param>
  323.         /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
  324.         /// <param name="kind" type="String">Kind of type to look for; one of 'entityType' or 'complexType'.</param>
  325.         /// <returns>An type description if the name is found; null otherwise.</returns>
  326.  
  327.         return (name) ? forEachSchema(metadata, function (schema) {
  328.             return lookupTypeInSchema(name, schema, kind);
  329.         }) : null;
  330.     };
  331.  
  332.     var lookupComplexType = function (name, metadata) {
  333.         /// <summary>Looks up a complex type object by name.</summary>
  334.         /// <param name="name" type="String">Name, possibly null or empty.</param>
  335.         /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
  336.         /// <returns>A complex type description if the name is found; null otherwise.</returns>
  337.  
  338.         return lookupTypeInMetadata(name, metadata, "complexType");
  339.     };
  340.  
  341.     var lookupEntityType = function (name, metadata) {
  342.         /// <summary>Looks up an entity type object by name.</summary>
  343.         /// <param name="name" type="String">Name, possibly null or empty.</param>
  344.         /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
  345.         /// <returns>An entity type description if the name is found; null otherwise.</returns>
  346.  
  347.         return lookupTypeInMetadata(name, metadata, "entityType");
  348.     };
  349.  
  350.     ////    Commented out - metadata is largely optional and we don't rely on it to this extent.
  351.     ////    var lookupEntityTypeForNavigation = function (navigationProperty, metadata) {
  352.     ////        /// <summary>Looks up the target entity type for a navigation property.</summary>
  353.     ////        /// <param name="navigationProperty" type="Object"></param>
  354.     ////        /// <param name="metadata" type="Object"></param>
  355.     ////        /// <returns type="Object">The entity type metadata for the specified property, null if not found.</returns>
  356.     ////        var rel = navigationProperty.relationship;
  357.     ////        var association = forEachSchema(metadata, function (schema) {
  358.     ////            // The name should be the namespace qualified name in 'ns'.'type' format.
  359.     ////            var nameOnly = removeNamespace(schema["namespace"], rel);
  360.     ////            var associations = schema.association;
  361.     ////            if (nameOnly && associations) {
  362.     ////                var i, len;
  363.     ////                for (i = 0, len = associations.length; i < len; i++) {
  364.     ////                    if (associations[i].name === nameOnly) {
  365.     ////                        return associations[i];
  366.     ////                    }
  367.     ////                }
  368.     ////            }
  369.     ////        });
  370.     ////        var result = null;
  371.     ////        if (association) {
  372.     ////            var end = association.end[0];
  373.     ////            if (end.role !== navigationProperty.toRole) {
  374.     ////                end = association.end[1];
  375.     ////                // For metadata to be valid, end.role === navigationProperty.toRole now.
  376.     ////            }
  377.     ////            result = lookupEntityType(end.type, metadata);
  378.     ////        }
  379.     ////        return result;
  380.     ////    };
  381.  
  382.     var removeNamespace = function (ns, fullName) {
  383.         /// <summary>Given an expected namespace prefix, removes it from a full name.</summary>
  384.         /// <param name="ns" type="String">Expected namespace.</param>
  385.         /// <param name="fullName" type="String">Full name in 'ns'.'name' form.</param>
  386.         /// <returns type="String">The local name, null if it isn't found in the expected namespace.</returns>
  387.  
  388.         if (fullName.indexOf(ns) === 0 && fullName.charAt(ns.length) === ".") {
  389.             return fullName.substr(ns.length + 1);
  390.         }
  391.  
  392.         return null;
  393.     };
  394.  
  395.     var lookupTypeInSchema = function (name, metadata, kind) {
  396.         /// <summary>Looks up an entity type object by name.</summary>
  397.         /// <param name="name" type="String">Name (assigned).</param>
  398.         /// <param name="metadata">Metadata store; one of edmx, schema.</param>
  399.         /// <param name="kind" type="String">Kind of type to look for; one of 'entityType' or 'complexType'.</param>
  400.         /// <returns>An entity type description if the name is found; null otherwise.</returns>
  401.         /// <remarks>
  402.         /// metadata is considered an edmx object if it contains a dataServices object.
  403.         /// </remarks>
  404.  
  405.         if (metadata) {
  406.             // The name should be the namespace qualified name in 'ns'.'type' format.
  407.             var nameOnly = removeNamespace(metadata["namespace"], name);
  408.             var types = metadata[kind];
  409.             if (nameOnly && types) {
  410.                 var i, len;
  411.                 for (i = 0, len = types.length; i < len; i++) {
  412.                     if (types[i].name === nameOnly) {
  413.                         return types[i];
  414.                     }
  415.                 }
  416.             }
  417.         }
  418.  
  419.         return null;
  420.     };
  421.  
  422.     var normalHeaders = {
  423.         "accept": "Accept",
  424.         "content-type": "Content-Type",
  425.         "dataserviceversion": "DataServiceVersion",
  426.         "maxdataserviceversion": "MaxDataServiceVersion"
  427.     };
  428.  
  429.     var normalizeHeaders = function (headers) {
  430.         /// <summary>Normalizes headers so they can be found with consistent casing.</summary>
  431.         /// <param name="headers" type="Object">Dictionary of name/value pairs.</param>
  432.  
  433.         for (var name in headers) {
  434.             var lowerName = name.toLowerCase();
  435.             var normalName = normalHeaders[lowerName];
  436.             if (normalName && name !== normalName) {
  437.                 var val = headers[name];
  438.                 delete headers[name];
  439.                 headers[normalName] = val;
  440.             }
  441.         }
  442.     };
  443.  
  444.     var undefinedDefault = function (value, defaultValue) {
  445.         /// <summary>Returns a default value in place of undefined.</summary>
  446.         /// <param name="value" mayBeNull="true" optional="true">Value to check.</param>
  447.         /// <param name="defaultValue">Value to return if value is undefined.</param>
  448.         /// <returns>value if it's defined; defaultValue otherwise.</returns>
  449.         /// <remarks>
  450.         /// This should only be used for cases where falsy values are valid;
  451.         /// otherwise the pattern should be 'x = (value) ? value : defaultValue;'.
  452.         /// </remarks>
  453.         return (value !== undefined) ? value : defaultValue;
  454.     };
  455.  
  456.     var parseInt10 = function (value) {
  457.         /// <summary>Parses a value in base 10.</summary>
  458.         /// <param name="value" type="String">String value to parse.</param>
  459.         /// <returns type="Number">The parsed value, NaN if not a valid value.</returns>
  460.  
  461.         return parseInt(value, 10);
  462.     };
  463.  
  464.  
  465.     // The captured indices for this expression are:
  466.     // 0       - complete input
  467.     // 1       - direction
  468.     // 2,3,4   - years, months, days
  469.     // 5,6,7,8 - hours, minutes, seconds, miliseconds
  470.  
  471.     var parseTimeRE = /^([+-])?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?/;
  472.  
  473.     var parseDuration = function (duration) {
  474.         /// <summary>Parses a string in xsd:duration format.</summary>
  475.         /// <param name="duration" type="String">Duration value.</param>
  476.         /// <remarks>
  477.         /// This method will throw an exception if the input string has a year or a month component.
  478.         /// </remarks>
  479.         /// <returns type="Object">Object representing the time</returns>
  480.  
  481.         var parts = parseTimeRE.exec(duration);
  482.  
  483.         if (parts === null) {
  484.             throw { message: "Invalid duration value." };
  485.         }
  486.  
  487.         var years = parts[2] || "0";
  488.         var months = parts[3] || "0";
  489.         var days = parseInt10(parts[4] || 0);
  490.         var hours = parseInt10(parts[5] || 0);
  491.         var minutes = parseInt10(parts[6] || 0);
  492.         var seconds = parseFloat(parts[7] || 0);
  493.  
  494.         if (years !== "0" || months !== "0") {
  495.             throw { message: "Unsupported duration value." };
  496.         }
  497.  
  498.         var ms = parts[8];
  499.         var ns = 0;
  500.         if (!ms) {
  501.             ms = 0;
  502.         } else {
  503.             if (ms.length > 7) {
  504.                 throw { message: "Cannot parse duration value to given precision." };
  505.             }
  506.  
  507.             ns = formatNumberWidth(ms.substring(3), 4, true);
  508.             ms = formatNumberWidth(ms.substring(0, 3), 3, true);
  509.  
  510.             ms = parseInt10(ms);
  511.             ns = parseInt10(ns);
  512.         }
  513.  
  514.         ms += seconds * 1000 + minutes * 60000 + hours * 3600000 + days * 86400000;
  515.  
  516.         if (parts[1] === "-") {
  517.             ms = -ms;
  518.         }
  519.  
  520.         var result = { ms: ms, __edmType: "Edm.Time" };
  521.  
  522.         if (ns) {
  523.             result.ns = ns;
  524.         }
  525.         return result;
  526.     };
  527.  
  528.     var parseTimezone = function (timezone) {
  529.         /// <summary>Parses a timezone description in (+|-)nn:nn format.</summary>
  530.         /// <param name="timezone" type="String">Timezone offset.</param>
  531.         /// <returns type="Object">
  532.         /// An object with a (d)irection property of 1 for + and -1 for -,
  533.         /// offset (h)ours and offset (m)inutes.
  534.         /// </returns>
  535.  
  536.         var direction = timezone.substring(0, 1);
  537.         direction = (direction === "+") ? 1 : -1;
  538.  
  539.         var offsetHours = parseInt10(timezone.substring(1));
  540.         var offsetMinutes = parseInt10(timezone.substring(timezone.indexOf(":") + 1));
  541.         return { d: direction, h: offsetHours, m: offsetMinutes };
  542.     };
  543.  
  544.     var payloadTypeOf = function (data) {
  545.         /// <summary>Determines the kind of payload applicable for the specified value.</summary>
  546.         /// <param name="value">Value to check.</param>
  547.         /// <returns type="String">One of the values declared on the payloadType object.</returns>
  548.  
  549.         switch (typeof (data)) {
  550.             case "object":
  551.                 if (!data) {
  552.                     return PAYLOADTYPE_NONE;
  553.                 }
  554.                 if (isArray(data) || isArray(data.results)) {
  555.                     return PAYLOADTYPE_FEEDORLINKS;
  556.                 }
  557.                 if (data.__metadata && data.__metadata.uri !== undefined) {
  558.                     return PAYLOADTYPE_ENTRY;
  559.                 }
  560.                 if (isArray(data.EntitySets)) {
  561.                     return PAYLOADTYPE_SVCDOC;
  562.                 }
  563.                 if (isArray(data.__batchRequests)) {
  564.                     return PAYLOADTYPE_BATCH;
  565.                 }
  566.                 if (isDate(data)) {
  567.                     return PAYLOADTYPE_PRIMITIVETYPE;
  568.                 }
  569.  
  570.                 return PAYLOADTYPE_COMPLEXTYPE;
  571.  
  572.             case "string":
  573.             case "number":
  574.             case "boolean":
  575.                 return PAYLOADTYPE_PRIMITIVETYPE;
  576.         }
  577.  
  578.         return PAYLOADTYPE_UNKNOWN;
  579.     };
  580.  
  581.     var prepareRequest = function (request, handler, context) {
  582.         /// <summary>Prepares a request object so that it can be sent through the network.</summary>
  583.         /// <param name="request">Object that represents the request to be sent.</param>
  584.         /// <param name="handler">Handler for data serialization</param>
  585.         /// <param name="context">Context used for preparing the request</param>
  586.  
  587.         // Default to GET if no method has been specified.    
  588.         if (!request.method) {
  589.             request.method = "GET";
  590.         }
  591.  
  592.         if (!request.headers) {
  593.             request.headers = {};
  594.         } else {
  595.             normalizeHeaders(request.headers);
  596.         }
  597.  
  598.         if (request.headers.Accept === undefined) {
  599.             request.headers.Accept = handler.accept;
  600.         }
  601.  
  602.         if (assigned(request.data) && request.body === undefined) {
  603.             handler.write(request, context);
  604.         }
  605.     };
  606.  
  607.     var propertyKindOf = function (value) {
  608.         /// <summary>Determines the kind of property for the specified value.</summary>
  609.         /// <param name="value">Value to check.</param>
  610.         /// <returns type="String">One of the values declared on the propertyKind object.</returns>
  611.  
  612.         switch (payloadTypeOf(value)) {
  613.             case PAYLOADTYPE_COMPLEXTYPE:
  614.                 if (value.__deferred && value.__deferred.uri) {
  615.                     return PROPERTYKIND_DEFERRED;
  616.                 }
  617.  
  618.                 return PROPERTYKIND_COMPLEX;
  619.  
  620.             case PAYLOADTYPE_FEEDORLINKS:
  621.             case PAYLOADTYPE_ENTRY:
  622.                 return PROPERTYKIND_INLINE;
  623.  
  624.             case PAYLOADTYPE_PRIMITIVETYPE:
  625.                 return PROPERTYKIND_PRIMITIVE;
  626.         }
  627.         return PROPERTYKIND_NONE;
  628.     };
  629.  
  630.     var throwErrorCallback = function (error) {
  631.         /// <summary>Default error handler.</summary>
  632.         /// <param name="error" type="Object">Error to handle.</param>
  633.         throw error;
  634.     };
  635.  
  636.     var trimString = function (str) {
  637.         /// <summary>Removes leading and trailing whitespaces from a string.</summary>
  638.         /// <param name="str" type="String" optional="false" mayBeNull="false">String to trim</param>
  639.         /// <returns type="String">The string with no leading or trailing whitespace.</returns>
  640.  
  641.         if (str.trim) {
  642.             return str.trim();
  643.         }
  644.  
  645.         return str.replace(/^\s+|\s+$/g, '');
  646.     };
  647.  
  648.     // Regular expression that splits a uri into its components:
  649.     // 0 - is the matched string.
  650.     // 1 - is the scheme.
  651.     // 2 - is the authority.
  652.     // 3 - is the path.
  653.     // 4 - is the query.
  654.     // 5 - is the fragment.
  655.     var uriRegEx = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/;
  656.     var uriPartNames = ["scheme", "authority", "path", "query", "fragment"];
  657.  
  658.     var getURIInfo = function (uri) {
  659.         /// <summary>Gets information about the components of the specified URI.</summary>
  660.         /// <param name="uri" type="String">URI to get information from.</param>
  661.         /// <returns type="Object">
  662.         /// An object with an isAbsolute flag and part names (scheme, authority, etc.) if available.
  663.         /// </returns>
  664.  
  665.         var result = { isAbsolute: false };
  666.  
  667.         if (uri) {
  668.             var matches = uriRegEx.exec(uri);
  669.             if (matches) {
  670.                 var i, len;
  671.                 for (i = 0, len = uriPartNames.length; i < len; i++) {
  672.                     if (matches[i + 1]) {
  673.                         result[uriPartNames[i]] = matches[i + 1];
  674.                     }
  675.                 }
  676.             }
  677.             if (result.scheme) {
  678.                 result.isAbsolute = true;
  679.             }
  680.         }
  681.  
  682.         return result;
  683.     };
  684.  
  685.     var getURIFromInfo = function (uriInfo) {
  686.         /// <summary>Builds a URI string from its components.</summary>
  687.         /// <param name="uriInfo" type="Object"> An object with uri parts (scheme, authority, etc.).</param>
  688.         /// <returns type="String">URI string.</returns>
  689.  
  690.         return "".concat(
  691.             uriInfo.scheme || "",
  692.             uriInfo.authority || "",
  693.             uriInfo.path || "",
  694.             uriInfo.query || "",
  695.             uriInfo.fragment || "");
  696.     };
  697.  
  698.     // Regular expression that splits a uri authority into its subcomponents:
  699.     // 0 - is the matched string.
  700.     // 1 - is the userinfo subcomponent.
  701.     // 2 - is the host subcomponent.
  702.     // 3 - is the port component.
  703.     var uriAuthorityRegEx = /^\/{0,2}(?:([^@]*)@)?([^:]+)(?::{1}(\d+))?/;
  704.  
  705.     // Regular expression that matches percentage enconded octects (i.e %20 or %3A);
  706.     var pctEncodingRegEx = /%[0-9A-F]{2}/ig;
  707.  
  708.     var normalizeURICase = function (uri) {
  709.         /// <summary>Normalizes the casing of a URI.</summary>
  710.         /// <param name="uri" type="String">URI to normalize, absolute or relative.</param>
  711.         /// <returns type="String">The URI normalized to lower case.</returns>
  712.  
  713.         var uriInfo = getURIInfo(uri);
  714.         var scheme = uriInfo.scheme;
  715.         var authority = uriInfo.authority;
  716.  
  717.         if (scheme) {
  718.             uriInfo.scheme = scheme.toLowerCase();
  719.             if (authority) {
  720.                 var matches = uriAuthorityRegEx.exec(authority);
  721.                 if (matches) {
  722.                     uriInfo.authority = "//" +
  723.                     (matches[1] ? matches[1] + "@" : "") +
  724.                     (matches[2].toLowerCase()) +
  725.                     (matches[3] ? ":" + matches[3] : "");
  726.                 }
  727.             }
  728.         }
  729.  
  730.         uri = getURIFromInfo(uriInfo);
  731.  
  732.         return uri.replace(pctEncodingRegEx, function (str) {
  733.             return str.toLowerCase();
  734.         });
  735.     };
  736.  
  737.     var normalizeURI = function (uri, base) {
  738.         /// <summary>Normalizes a possibly relative URI with a base URI.</summary>
  739.         /// <param name="uri" type="String">URI to normalize, absolute or relative.</param>
  740.         /// <param name="base" type="String" mayBeNull="true">Base URI to compose with.</param>
  741.         /// <returns type="String">The composed URI if relative; the original one if absolute.</returns>
  742.  
  743.         if (!base) {
  744.             return uri;
  745.         }
  746.  
  747.         var uriInfo = getURIInfo(uri);
  748.         if (uriInfo.isAbsolute) {
  749.             return uri;
  750.         }
  751.  
  752.         var baseInfo = getURIInfo(base);
  753.         var normInfo = {};
  754.         var path;
  755.  
  756.         if (uriInfo.authority) {
  757.             normInfo.authority = uriInfo.authority;
  758.             path = uriInfo.path;
  759.             normInfo.query = uriInfo.query;
  760.         } else {
  761.             if (!uriInfo.path) {
  762.                 path = baseInfo.path;
  763.                 normInfo.query = uriInfo.query || baseInfo.query;
  764.             } else {
  765.                 if (uriInfo.path.charAt(0) === '/') {
  766.                     path = uriInfo.path;
  767.                 } else {
  768.                     path = mergeUriPathWithBase(uriInfo.path, baseInfo.path);
  769.                 }
  770.                 normInfo.query = uriInfo.query;
  771.             }
  772.             normInfo.authority = baseInfo.authority;
  773.         }
  774.  
  775.         normInfo.path = removeDotsFromPath(path);
  776.  
  777.         normInfo.scheme = baseInfo.scheme;
  778.         normInfo.fragment = uriInfo.fragment;
  779.  
  780.         return getURIFromInfo(normInfo);
  781.     };
  782.  
  783.     var mergeUriPathWithBase = function (uriPath, basePath) {
  784.         /// <summary>Merges the path of a relative URI and a base URI.</summary>
  785.         /// <param name="uriPath" type="String>Relative URI path.</param>
  786.         /// <param name="basePath" type="String">Base URI path.</param>
  787.         /// <returns type="String">A string with the merged path.</returns>
  788.  
  789.         var path = "/";
  790.         var end;
  791.  
  792.         if (basePath) {
  793.             end = basePath.lastIndexOf("/");
  794.             path = basePath.substring(0, end);
  795.  
  796.             if (path.charAt(path.length - 1) !== "/") {
  797.                 path = path + "/";
  798.             }
  799.         }
  800.  
  801.         return path + uriPath;
  802.     };
  803.  
  804.     var removeDotsFromPath = function (path) {
  805.         /// <summary>Removes the special folders . and .. from a URI's path.</summary>
  806.         /// <param name="path" type="string">URI path component.</param>
  807.         /// <returns type="String">Path without any . and .. folders.</returns>
  808.  
  809.         var result = "";
  810.         var segment = "";
  811.         var end;
  812.  
  813.         while (path) {
  814.             if (path.indexOf("..") === 0 || path.indexOf(".") === 0) {
  815.                 path = path.replace(/^\.\.?\/?/g, "");
  816.             } else if (path.indexOf("/..") === 0) {
  817.                 path = path.replace(/^\/\..\/?/g, "/");
  818.                 end = result.lastIndexOf("/");
  819.                 if (end === -1) {
  820.                     result = "";
  821.                 } else {
  822.                     result = result.substring(0, end);
  823.                 }
  824.             } else if (path.indexOf("/.") === 0) {
  825.                 path = path.replace(/^\/\.\/?/g, "/");
  826.             } else {
  827.                 segment = path;
  828.                 end = path.indexOf("/", 1);
  829.                 if (end !== -1) {
  830.                     segment = path.substring(0, end);
  831.                 }
  832.                 result = result + segment;
  833.                 path = path.replace(segment, "");
  834.             }
  835.         }
  836.         return result;
  837.     };
  838.  
  839.  
  840.     var ticks = 0;
  841.  
  842.     var canUseJSONP = function (request) {
  843.         /// <summary>
  844.         /// Checks whether the specified request can be satisfied with a JSONP request.
  845.         /// </summary>
  846.         /// <param name="request">Request object to check.</param>
  847.         /// <returns type="Boolean">true if the request can be satisfied; false otherwise.</returns>
  848.  
  849.         // Requests that 'degrade' without changing their meaning by going through JSONP
  850.         // are considered usable.
  851.         //
  852.         // We allow data to come in a different format, as the servers SHOULD honor the Accept
  853.         // request but may in practice return content with a different MIME type.
  854.         if (request.method && request.method !== "GET") {
  855.             return false;
  856.         }
  857.  
  858.         return true;
  859.     };
  860.  
  861.     var createIFrame = function (url) {
  862.         /// <summary>Creates an IFRAME tag for loading the JSONP script</summary>
  863.         /// <param name="url" type="String">The source URL of the script</param>
  864.         /// <returns type="HTMLElement">The IFRAME tag</returns>
  865.         var iframe = window.document.createElement("IFRAME");
  866.         iframe.style.display = "none";
  867.  
  868.         var attributeEncodedUrl = url.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/\</g, "&lt;");
  869.         var html = "<html><head><script type=\"text/javascript\" src=\"" + attributeEncodedUrl + "\"><\/script><\/head><body><\/body><\/html>";
  870.  
  871.         var body = window.document.getElementsByTagName("BODY")[0];
  872.         body.appendChild(iframe);
  873.  
  874.         writeHtmlToIFrame(iframe, html);
  875.         return iframe;
  876.     };
  877.  
  878.     var createXmlHttpRequest = function () {
  879.         /// <summary>Creates a XmlHttpRequest object.</summary>
  880.         /// <returns type="XmlHttpRequest">XmlHttpRequest object.</returns>
  881.         if (window.XMLHttpRequest) {
  882.             return new window.XMLHttpRequest();
  883.         }
  884.         var exception;
  885.         if (window.ActiveXObject) {
  886.             try {
  887.                 return new window.ActiveXObject("Msxml2.XMLHTTP.6.0");
  888.             } catch (_) {
  889.                 try {
  890.                     return new window.ActiveXObject("Msxml2.XMLHTTP.3.0");
  891.                 } catch (e) {
  892.                     exception = e;
  893.                 }
  894.             }
  895.         } else {
  896.             exception = { message: "XMLHttpRequest not supported" };
  897.         }
  898.         throw exception;
  899.     };
  900.  
  901.     var isAbsoluteUrl = function (url) {
  902.         /// <summary>Checks whether the specified URL is an absolute URL.</summary>
  903.         /// <param name="url" type="String">URL to check.</param>
  904.         /// <returns type="Boolean">true if the url is an absolute URL; false otherwise.</returns>
  905.  
  906.         return url.indexOf("http://") === 0 ||
  907.             url.indexOf("https://") === 0 ||
  908.             url.indexOf("file://") === 0;
  909.     };
  910.  
  911.     var isLocalUrl = function (url) {
  912.         /// <summary>Checks whether the specified URL is local to the current context.</summary>
  913.         /// <param name="url" type="String">URL to check.</param>
  914.         /// <returns type="Boolean">true if the url is a local URL; false otherwise.</returns>
  915.  
  916.         if (!isAbsoluteUrl(url)) {
  917.             return true;
  918.         }
  919.  
  920.         // URL-embedded username and password will not be recognized as same-origin URLs.
  921.         var location = window.location;
  922.         var locationDomain = location.protocol + "//" + location.host + "/";
  923.         return (url.indexOf(locationDomain) === 0);
  924.     };
  925.  
  926.     var removeCallback = function (name, tick) {
  927.         /// <summary>Removes a callback used for a JSONP request.</summary>
  928.         /// <param name="name" type="String">Function name to remove.</param>
  929.         /// <param name="tick" type="Number">Tick count used on the callback.</param>
  930.         try {
  931.             delete window[name];
  932.         } catch (err) {
  933.             window[name] = undefined;
  934.             if (tick === ticks - 1) {
  935.                 ticks -= 1;
  936.             }
  937.         }
  938.     };
  939.  
  940.     var removeIFrame = function (iframe) {
  941.         /// <summary>Removes an iframe.</summary>
  942.         /// <param name="iframe" type="Object">The iframe to remove.</param>
  943.         /// <returns type="Object">Null value to be assigned to iframe reference.</returns>
  944.         if (iframe) {
  945.             writeHtmlToIFrame(iframe, "");
  946.             iframe.parentNode.removeChild(iframe);
  947.         }
  948.  
  949.         return null;
  950.     };
  951.  
  952.     var readResponseHeaders = function (xhr, headers) {
  953.         /// <summary>Reads response headers into array.</summary>
  954.         /// <param name="xhr" type="XMLHttpRequest">HTTP request with response available.</param>
  955.         /// <param name="headers" type="Array">Target array to fill with name/value pairs.</param>
  956.  
  957.         var responseHeaders = xhr.getAllResponseHeaders().split(/\r?\n/);
  958.         var i, len;
  959.         for (i = 0, len = responseHeaders.length; i < len; i++) {
  960.             if (responseHeaders[i]) {
  961.                 var header = responseHeaders[i].split(": ");
  962.                 headers[header[0]] = header[1];
  963.             }
  964.         }
  965.     };
  966.  
  967.     var writeHtmlToIFrame = function (iframe, html) {
  968.         /// <summary>Writes HTML to an IFRAME document.</summary>
  969.         /// <param name="iframe" type="HTMLElement">The IFRAME element to write to.</param>
  970.         /// <param name="html" type="String">The HTML to write.</param>
  971.         var frameDocument = (iframe.contentWindow) ? iframe.contentWindow.document : iframe.contentDocument.document;
  972.         frameDocument.open();
  973.         frameDocument.write(html);
  974.         frameDocument.close();
  975.     };
  976.  
  977.     odata.defaultHttpClient = {
  978.         callbackParameterName: "$callback",
  979.  
  980.         formatQueryString: "$format=json",
  981.  
  982.         enableJsonpCallback: false,
  983.  
  984.         request: function (request, success, error) {
  985.             /// <summary>Performs a network request.</summary>
  986.             /// <param name="request" type="Object">Request description.</request>
  987.             /// <param name="success" type="Function">Success callback with the response object.</param>
  988.             /// <param name="error" type="Function">Error callback with an error object.</param>
  989.             /// <returns type="Object">Object with an 'abort' method for the operation.</returns>
  990.  
  991.             var result = {};
  992.             var xhr = null;
  993.             var done = false;
  994.             var iframe;
  995.  
  996.             result.abort = function () {
  997.                 iframe = removeIFrame(iframe);
  998.                 if (done) {
  999.                     return;
  1000.                 }
  1001.  
  1002.                 done = true;
  1003.                 if (xhr) {
  1004.                     xhr.abort();
  1005.                     xhr = null;
  1006.                 }
  1007.  
  1008.                 error({ message: "Request aborted" });
  1009.             };
  1010.  
  1011.             var handleTimeout = function () {
  1012.                 iframe = removeIFrame(iframe);
  1013.                 if (!done) {
  1014.                     done = true;
  1015.                     xhr = null;
  1016.                     error({ message: "Request timed out" });
  1017.                 }
  1018.             };
  1019.  
  1020.             var name;
  1021.             var url = request.requestUri;
  1022.             var enableJsonpCallback = defined(request.enableJsonpCallback, this.enableJsonpCallback);
  1023.             var callbackParameterName = defined(request.callbackParameterName, this.callbackParameterName);
  1024.             var formatQueryString = defined(request.formatQueryString, this.formatQueryString);
  1025.             if (!enableJsonpCallback || isLocalUrl(url)) {
  1026.  
  1027.                 xhr = createXmlHttpRequest();
  1028.                 xhr.onreadystatechange = function () {
  1029.                     if (done || xhr === null || xhr.readyState !== 4) {
  1030.                         return;
  1031.                     }
  1032.  
  1033.                     // Workaround for XHR behavior on IE.
  1034.                     var statusText = xhr.statusText;
  1035.                     var statusCode = xhr.status;
  1036.                     if (statusCode === 1223) {
  1037.                         statusCode = 204;
  1038.                         statusText = "No Content";
  1039.                     }
  1040.  
  1041.                     var headers = [];
  1042.                     readResponseHeaders(xhr, headers);
  1043.  
  1044.                     var response = { requestUri: url, statusCode: statusCode, statusText: statusText, headers: headers, body: xhr.responseText };
  1045.  
  1046.                     done = true;
  1047.                     xhr = null;
  1048.                     if (statusCode >= 200 && statusCode <= 299) {
  1049.                         success(response);
  1050.                     } else {
  1051.                         error({ message: "HTTP request failed", request: request, response: response });
  1052.                     }
  1053.                 };
  1054.  
  1055.                 xhr.open(request.method || "GET", url, true, request.user, request.password);
  1056.  
  1057.                 // Set the name/value pairs.
  1058.                 if (request.headers) {
  1059.                     for (name in request.headers) {
  1060.                         xhr.setRequestHeader(name, request.headers[name]);
  1061.                     }
  1062.                 }
  1063.  
  1064.                 // Set the timeout if available.
  1065.                 if (request.timeoutMS) {
  1066.                     xhr.timeout = request.timeoutMS;
  1067.                     xhr.ontimeout = handleTimeout;
  1068.                 }
  1069.  
  1070.                 xhr.send(request.body);
  1071.             } else {
  1072.                 if (!canUseJSONP(request)) {
  1073.                     throw { message: "Request is not local and cannot be done through JSONP." };
  1074.                 }
  1075.  
  1076.                 var tick = ticks;
  1077.                 ticks += 1;
  1078.                 var tickText = tick.toString();
  1079.                 var succeeded = false;
  1080.                 var timeoutId;
  1081.                 name = "handleJSONP_" + tickText;
  1082.                 window[name] = function (data) {
  1083.                     iframe = removeIFrame(iframe);
  1084.                     if (!done) {
  1085.                         succeeded = true;
  1086.                         window.clearTimeout(timeoutId);
  1087.                         removeCallback(name, tick);
  1088.  
  1089.                         // Workaround for IE8 and below where trying to access data.constructor after the IFRAME has been removed
  1090.                         // throws an "unknown exception"
  1091.                         if (window.ActiveXObject && !window.DOMParser) {
  1092.                             data = window.JSON.parse(window.JSON.stringify(data));
  1093.                         }
  1094.  
  1095.                         // Call the success callback in the context of the parent window, instead of the IFRAME
  1096.                         delay(success, { body: data, statusCode: 200, headers: { "Content-Type": "application/json"} });
  1097.                     }
  1098.                 };
  1099.  
  1100.                 // Default to two minutes before timing out, 1000 ms * 60 * 2 = 120000.
  1101.                 var timeoutMS = (request.timeoutMS) ? request.timeoutMS : 120000;
  1102.                 timeoutId = window.setTimeout(handleTimeout, timeoutMS);
  1103.  
  1104.                 var queryStringParams = callbackParameterName + "=parent." + name;
  1105.                 if (this.formatQueryString) {
  1106.                     queryStringParams += "&" + formatQueryString;
  1107.                 }
  1108.  
  1109.                 var qIndex = url.indexOf("?");
  1110.                 if (qIndex === -1) {
  1111.                     url = url + "?" + queryStringParams;
  1112.                 } else if (qIndex === url.length - 1) {
  1113.                     url = url + queryStringParams;
  1114.                 } else {
  1115.                     url = url + "&" + queryStringParams;
  1116.                 }
  1117.  
  1118.                 iframe = createIFrame(url);
  1119.             }
  1120.  
  1121.             return result;
  1122.         }
  1123.     };
  1124.  
  1125.  
  1126.  
  1127.     var contentType = function (str) {
  1128.         /// <summary>Parses a string into an object with media type and properties.</summary>
  1129.         /// <param name="str" type="String">String with media type to parse.</param>
  1130.         /// <returns>null if the string is empty; an object with 'mediaType' and a 'properties' dictionary otherwise.</returns>
  1131.  
  1132.         if (!str) {
  1133.             return null;
  1134.         }
  1135.  
  1136.         var contentTypeParts = str.split(";");
  1137.         var properties = {};
  1138.  
  1139.         var i, len;
  1140.         for (i = 1, len = contentTypeParts.length; i < len; i++) {
  1141.             var contentTypeParams = contentTypeParts[i].split("=");
  1142.             properties[trimString(contentTypeParams[0])] = contentTypeParams[1];
  1143.         }
  1144.  
  1145.         return { mediaType: trimString(contentTypeParts[0]), properties: properties };
  1146.     };
  1147.  
  1148.     var contentTypeToString = function (contentType) {
  1149.         /// <summary>Serializes an object with media type and properties dictionary into a string.</summary>
  1150.         /// <param name="contentType">Object with media type and properties dictionary to serialize.</param>
  1151.         /// <returns>String representation of the media type object; undefined if contentType is null or undefined.</returns>
  1152.  
  1153.         if (!contentType) {
  1154.             return undefined;
  1155.         }
  1156.  
  1157.         var result = contentType.mediaType;
  1158.         var property;
  1159.         for (property in contentType.properties) {
  1160.             result += ";" + property + "=" + contentType.properties[property];
  1161.         }
  1162.         return result;
  1163.     };
  1164.  
  1165.     var createReadWriteContext = function (contentType, dataServiceVersion, context, handler) {
  1166.         /// <summary>Creates an object that is going to be used as the context for the handler's parser and serializer.</summary>
  1167.         /// <param name="contentType">Object with media type and properties dictionary.</param>
  1168.         /// <param name="dataServiceVersion" type="String">String indicating the version of the protocol to use.</param>
  1169.         /// <param name="context">Operation context.</param>
  1170.         /// <param name="handler">Handler object that is processing a resquest or response.</param>
  1171.         /// <returns>Context object.</returns>
  1172.  
  1173.         return {
  1174.             contentType: contentType,
  1175.             dataServiceVersion: dataServiceVersion,
  1176.             metadata: context ? context.metadata : null,
  1177.             context: context,
  1178.             handler: handler
  1179.         };
  1180.     };
  1181.  
  1182.     var fixRequestHeader = function (request, name, value) {
  1183.         /// <summary>Sets a request header's value. If the header has already a value other than undefined, null or empty string, then this method does nothing.</summary>
  1184.         /// <param name="request">Request object on which the header will be set.</param>
  1185.         /// <param name="name" type="String">Header name.</param>
  1186.         /// <param name="value" type="String">Header value.</param>
  1187.         if (!request) {
  1188.             return;
  1189.         }
  1190.  
  1191.         var headers = request.headers;
  1192.         if (!headers[name]) {
  1193.             headers[name] = value;
  1194.         }
  1195.     };
  1196.  
  1197.     var fixDataServiceVersion = function (context, version) {
  1198.         /// <summary>Sets the dataServiceVersion component of the context.</summary>
  1199.         /// <param name="context">Context object used for serialization.</param>
  1200.         /// <param name="version" type="String">Version value.</param>
  1201.         /// <remarks>
  1202.         /// If the component has already a value other than undefined, null or
  1203.         /// empty string, then this method does nothing.
  1204.         /// </remarks>
  1205.  
  1206.         if (!context.dataServiceVersion) {
  1207.             context.dataServiceVersion = version;
  1208.         }
  1209.     };
  1210.  
  1211.     var getRequestOrResponseHeader = function (requestOrResponse, name) {
  1212.         /// <summary>Gets the value of a request or response header.</summary>
  1213.         /// <param name="requestOrResponse">Object representing a request or a response.</param>
  1214.         /// <param name="name" type="String">Name of the header to retrieve.</param>
  1215.         /// <returns type="String">String value of the header; undefined if the header cannot be found.</returns>
  1216.  
  1217.         var headers = requestOrResponse.headers;
  1218.         return (headers && headers[name]) || undefined;
  1219.     };
  1220.  
  1221.     var getContentType = function (requestOrResponse) {
  1222.         /// <summary>Gets the value of the Content-Type header from a request or response.</summary>
  1223.         /// <param name="requestOrResponse">Object representing a request or a response.</param>
  1224.         /// <returns type="Object">Object with 'mediaType' and a 'properties' dictionary; null in case that the header is not found or doesn't have a value.</returns>
  1225.  
  1226.         return contentType(getRequestOrResponseHeader(requestOrResponse, "Content-Type"));
  1227.     };
  1228.  
  1229.     var versionRE = /^\s?(\d+\.\d+);?.*$/;
  1230.     var getDataServiceVersion = function (requestOrResponse) {
  1231.         /// <summary>Gets the value of the DataServiceVersion header from a request or response.</summary>
  1232.         /// <param name="requestOrResponse">Object representing a request or a response.</param>
  1233.         /// <returns type="String">Data service version; undefined if the header cannot be found.</returns>
  1234.  
  1235.         var value = getRequestOrResponseHeader(requestOrResponse, "DataServiceVersion");
  1236.         if (value) {
  1237.             var matches = versionRE.exec(value);
  1238.             if (matches && matches.length) {
  1239.                 return matches[1];
  1240.             }
  1241.         }
  1242.  
  1243.         // Fall through and return undefined.
  1244.     };
  1245.  
  1246.     var handlerAccepts = function (handler, cType) {
  1247.         /// <summary>Checks that a handler can process a particular mime type.</summary>
  1248.         /// <param name="handler">Handler object that is processing a resquest or response.</param>
  1249.         /// <param name="cType">Object with 'mediaType' and a 'properties' dictionary.</param>
  1250.         /// <returns type="Boolean">True if the handler can process the mime type; false otherwise.</returns>
  1251.  
  1252.         // The following check isn't as strict because if cType.mediaType = application/; it will match an accept value of "application/xml";
  1253.         // however in practice we don't not expect to see such "suffixed" mimeTypes for the handlers.
  1254.         return handler.accept.indexOf(cType.mediaType) >= 0;
  1255.     };
  1256.  
  1257.     var handlerRead = function (handler, parseCallback, response, context) {
  1258.         /// <summary>Invokes the parser associated with a handler for reading the payload of a HTTP response.</summary>
  1259.         /// <param name="handler">Handler object that is processing the response.</param>
  1260.         /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param>
  1261.         /// <param name="response">HTTP response whose payload is going to be processed.</param>
  1262.         /// <param name="context">Object used as the context for processing the response.</param>
  1263.         /// <returns type="Boolean">True if the handler processed the response payload and the response.data property was set; false otherwise.</returns>
  1264.  
  1265.         if (!response || !response.headers) {
  1266.             return false;
  1267.         }
  1268.  
  1269.         var cType = getContentType(response);
  1270.         var version = getDataServiceVersion(response) || "";
  1271.         var body = response.body;
  1272.  
  1273.         if (!assigned(body)) {
  1274.             return false;
  1275.         }
  1276.  
  1277.         if (handlerAccepts(handler, cType)) {
  1278.             var readContext = createReadWriteContext(cType, version, context, handler);
  1279.             readContext.response = response;
  1280.             response.data = parseCallback(handler, body, readContext);
  1281.             return response.data !== undefined;
  1282.         }
  1283.  
  1284.         return false;
  1285.     };
  1286.  
  1287.     var handlerWrite = function (handler, serializeCallback, request, context) {
  1288.         /// <summary>Invokes the serializer associated with a handler for generating the payload of a HTTP request.</summary>
  1289.         /// <param name="handler">Handler object that is processing the request.</param>
  1290.         /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param>
  1291.         /// <param name="response">HTTP request whose payload is going to be generated.</param>
  1292.         /// <param name="context">Object used as the context for serializing the request.</param>
  1293.         /// <returns type="Boolean">True if the handler serialized the request payload and the request.body property was set; false otherwise.</returns>
  1294.         if (!request || !request.headers) {
  1295.             return false;
  1296.         }
  1297.  
  1298.         var cType = getContentType(request);
  1299.         var version = getDataServiceVersion(request);
  1300.  
  1301.         if (!cType || handlerAccepts(handler, cType)) {
  1302.             var writeContext = createReadWriteContext(cType, version, context, handler);
  1303.             writeContext.request = request;
  1304.  
  1305.             request.body = serializeCallback(handler, request.data, writeContext);
  1306.  
  1307.             if (request.body !== undefined) {
  1308.                 fixRequestHeader(request, "DataServiceVersion", writeContext.dataServiceVersion || "1.0");
  1309.                 fixRequestHeader(request, "Content-Type", contentTypeToString(writeContext.contentType));
  1310.                 return true;
  1311.             }
  1312.         }
  1313.  
  1314.         return false;
  1315.     };
  1316.  
  1317.     var handler = function (parseCallback, serializeCallback, accept, maxDataServiceVersion) {
  1318.         /// <summary>Creates a handler object for processing HTTP requests and responses.</summary>
  1319.         /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param>
  1320.         /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param>
  1321.         /// <param name="accept" type="String">String containing a comma separated list of the mime types that this handler can work with.</param>
  1322.         /// <param name="maxDataServiceVersion" type="String">String indicating the highest version of the protocol that this handler can work with.</param>
  1323.         /// <returns type="Object">Handler object.</returns>
  1324.        
  1325.         return {
  1326.             accept: accept,
  1327.             maxDataServiceVersion: maxDataServiceVersion,
  1328.  
  1329.             read: function (response, context) {
  1330.                 return handlerRead(this, parseCallback, response, context);
  1331.             },
  1332.  
  1333.             write: function (request, context) {
  1334.                 return handlerWrite(this, serializeCallback, request, context);
  1335.             }
  1336.         };
  1337.     };
  1338.  
  1339.     var textParse = function (handler, body /*, context */) {
  1340.         return body;
  1341.     };
  1342.  
  1343.     var textSerialize = function (handler, data /*, context */) {
  1344.         if (assigned(data)) {
  1345.             return data.toString();
  1346.         } else {
  1347.             return undefined;
  1348.         }
  1349.     };
  1350.  
  1351.     odata.textHandler = handler(textParse, textSerialize, "text/plain", "2.0");
  1352.  
  1353.  
  1354.  
  1355.     var xmlMediaType = "application/xml";
  1356.  
  1357.     // URI prefixes to generate smaller code.
  1358.     var http = "http://";
  1359.     var w3org = http + "www.w3.org/";               // http://www.w3.org/
  1360.     var ado = http + "schemas.microsoft.com/ado/";  // http://schemas.microsoft.com/ado/
  1361.     var adoDs = ado + "2007/08/dataservices";       // http://schemas.microsoft.com/ado/2007/08/dataservices
  1362.  
  1363.     var xmlnsNS = w3org + "2000/xmlns/";            // http://www.w3.org/2000/xmlns/
  1364.     var xmlNS = w3org + "XML/1998/namespace";       // http://www.w3.org/XML/1998/namespace
  1365.     var edmxNs = ado + "2007/06/edmx";              // http://schemas.microsoft.com/ado/2007/06/edmx
  1366.     var edmNs = ado + "2008/09/edm";                // http://schemas.microsoft.com/ado/2008/09/edm
  1367.     var edmNs2 = ado + "2006/04/edm";               // http://schemas.microsoft.com/ado/2006/04/edm
  1368.     var edmNs3 = ado + "2007/05/edm";               // http://schemas.microsoft.com/ado/2007/05/edm
  1369.     var atomXmlNs = w3org + "2005/Atom";            // http://www.w3.org/2005/Atom
  1370.     var appXmlNs = w3org + "2007/app";              // http://www.w3.org/2007/app
  1371.     var odataXmlNs = adoDs;                         // http://schemas.microsoft.com/ado/2007/08/dataservices
  1372.     var odataMetaXmlNs = adoDs + "/metadata";       // http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
  1373.     var odataRelatedPrefix = adoDs + "/related/";   // http://schemas.microsoft.com/ado/2007/08/dataservices/related
  1374.     var odataScheme = adoDs + "/scheme";            // http://schemas.microsoft.com/ado/2007/08/dataservices/scheme
  1375.  
  1376.     var hasLeadingOrTrailingWhitespace = function (text) {
  1377.         /// <summary>Checks whether the specified string has leading or trailing spaces.</summary>
  1378.         /// <param name="text" type="String">String to check.</param>
  1379.         /// <returns type="Boolean">true if text has any leading or trailing whitespace; false otherwise.</returns>
  1380.  
  1381.         var re = /(^\s)|(\s$)/;
  1382.         return re.test(text);
  1383.     };
  1384.  
  1385.     var appendAsXml = function (domNode, xmlAsText) {
  1386.         /// <summary>Appends an XML text fragment into the specified DOM element node.</summary>
  1387.         /// <param name="domNode">DOM node for the parent element.</param>
  1388.         /// <param name="xmlAsText" type="String" mayBeNull="false">XML to append as a child of element.</param>
  1389.  
  1390.         var value = "<c>" + xmlAsText + "</c>";
  1391.         var parsed = xmlParse(value, null);
  1392.  
  1393.         var doc = domNode.ownerDocument;
  1394.         var imported = parsed.domNode;
  1395.         if ("importNode" in doc) {
  1396.             imported = doc.importNode(parsed.domNode, true);
  1397.         }
  1398.  
  1399.         var importedChild = imported.firstChild;
  1400.         while (importedChild) {
  1401.             domNode.appendChild(importedChild);
  1402.             importedChild = importedChild.nextSibling;
  1403.         }
  1404.     };
  1405.  
  1406.     var safeSetProperty = function (obj, name, value) {
  1407.         /// <summary>Safely set as property in an object by invoking obj.setProperty.</summary>
  1408.         /// <param name="obj">Object that exposes a setProperty method.</param>
  1409.         /// <param name="name" type="String" mayBeNull="false">Property name.</param>
  1410.         /// <param name="value">Property value.</param>
  1411.         try {
  1412.             obj.setProperty(name, value);
  1413.         } catch (_) { }
  1414.     };
  1415.  
  1416.     var createDomParser = function () {
  1417.         /// <summary>Creates a DOM parser object.</summary>
  1418.         /// <returns type="DOMParser">DOMParser object.</returns>
  1419.         var exception;
  1420.         var result;
  1421.         if (window.ActiveXObject) {
  1422.             try {
  1423.                 result = new ActiveXObject("Msxml2.DOMDocument.6.0");
  1424.                 result.async = false;
  1425.  
  1426.                 return result;
  1427.             } catch (_) {
  1428.                 try {
  1429.                     result = new ActiveXObject("Msxml2.DOMDocument.3.0");
  1430.  
  1431.                     result.async = false;
  1432.                     safeSetProperty(result, "ProhibitDTD", true);
  1433.                     safeSetProperty(result, "MaxElementDepth", 256);
  1434.                     safeSetProperty(result, "AllowDocumentFunction", false);
  1435.                     safeSetProperty(result, "AllowXsltScript", false);
  1436.  
  1437.                     return result;
  1438.                 } catch (e) {
  1439.                     exception = e;
  1440.                 }
  1441.             }
  1442.         } else {
  1443.             if (window.DOMParser) {
  1444.                 return new window.DOMParser();
  1445.             }
  1446.             exception = { message: "XML DOM parser not supported" };
  1447.         }
  1448.         throw exception;
  1449.     };
  1450.  
  1451.     var createAttributeExtension = function (wrappedNode) {
  1452.         /// <summary>Creates an extension object for the specified attribute.</summary>
  1453.         /// <param name="wrappedNode">Wrapped node for the attribute.</param>
  1454.         /// <returns type="Object">The new extension object.</returns>
  1455.  
  1456.         return {
  1457.             name: wrappedNode.localName,
  1458.             namespace: wrappedNode.nsURI,
  1459.             value: wrappedNode.domNode.value
  1460.         };
  1461.     };
  1462.  
  1463.     var createElementExtension = function (wrappedNode) {
  1464.         /// <summary>Creates an extension object for the specified element.</summary>
  1465.         /// <param name="wrappedNode">Wrapped node for the element.</param>
  1466.         /// <returns type="Object">The new extension object.</returns>
  1467.  
  1468.         var result = {
  1469.             name: wrappedNode.localName,
  1470.             namespace: wrappedNode.nsURI,
  1471.             value: getElementText(wrappedNode.domNode),
  1472.             attributes: [],
  1473.             children: []
  1474.         };
  1475.  
  1476.         xmlAttributes(wrappedNode, function (wrappedAttribute) {
  1477.             // Namespace declarations aren't processed, because every extension
  1478.             // is namespace-qualified.
  1479.             if (wrappedAttribute.nsURI !== xmlnsNS) {
  1480.                 result.attributes.push(createAttributeExtension(wrappedAttribute));
  1481.             }
  1482.         });
  1483.  
  1484.         xmlChildElements(wrappedNode, function (wrappedElement) {
  1485.             result.children.push(createElementExtension(wrappedElement));
  1486.         });
  1487.  
  1488.         return result;
  1489.     };
  1490.  
  1491.     var isWhitespace = function (text) {
  1492.         /// <summary>Determines whether the specified text is empty or whitespace.</summary>
  1493.         /// <param name="text" type="String">Value to inspect.</param>
  1494.         /// <returns type="Boolean">true if the text value is empty or all whitespace; false otherwise.</returns>
  1495.         var ws = /^\s*$/;
  1496.         return text === null || ws.test(text);
  1497.     };
  1498.  
  1499.     var isWhitespacePreserveContext = function (domElement) {
  1500.         /// <summary>Determines whether the specified element has xml:space='preserve' applied.</summary>
  1501.         /// <param name="domElement">Element to inspect.</param>
  1502.         /// <returns type="Boolean">Whether xml:space='preserve' is in effect.</returns>
  1503.         while (domElement !== null && domElement.nodeType === 1) {
  1504.             var val = xml_attribute(domElement, "space", xmlNS);
  1505.             if (val === "preserve") {
  1506.                 return true;
  1507.             } else if (val === "default") {
  1508.                 break;
  1509.             } else {
  1510.                 domElement = domElement.parentNode;
  1511.             }
  1512.         }
  1513.  
  1514.         return false;
  1515.     };
  1516.  
  1517.     var getElementText = function (domElement) {
  1518.         /// <summary>
  1519.         /// Gets the concatenated value of all immediate child text and CDATA nodes for the specified element.
  1520.         /// </summary>
  1521.         /// <param name="domElement">Element to get values for.</param>
  1522.         /// <returns type="String" mayBeNull="true">Text for all direct children.</returns>
  1523.  
  1524.         var result = null;
  1525.         var cursor = domElement.firstChild;
  1526.         var whitespaceAlreadyRemoved = domElement.ownerDocument.preserveWhiteSpace === false;
  1527.         var whitespacePreserveContext;
  1528.         while (cursor) {
  1529.             if (cursor.nodeType === 3 || cursor.nodeType === 4) {
  1530.                 // isElementContentWhitespace indicates that this is 'ignorable whitespace',
  1531.                 // but it's not defined by all browsers, and does not honor xml:space='preserve'
  1532.                 // in some implementations.
  1533.                 //
  1534.                 // If we can't tell either way, we walk up the tree to figure out whether
  1535.                 // xml:space is set to preserve; otherwise we discard pure-whitespace.
  1536.                 //
  1537.                 // For example <a>  <b>1</b></a>. The space between <a> and <b> is usually 'ignorable'.
  1538.                 var text = cursor.nodeValue;
  1539.                 var shouldInclude = whitespaceAlreadyRemoved || !isWhitespace(text);
  1540.                 if (!shouldInclude) {
  1541.                     // Walk up the tree to figure out whether we are in xml:space='preserve' context
  1542.                     // for the cursor (needs to happen only once).
  1543.                     if (whitespacePreserveContext === undefined) {
  1544.                         whitespacePreserveContext = isWhitespacePreserveContext(domElement);
  1545.                     }
  1546.  
  1547.                     shouldInclude = whitespacePreserveContext;
  1548.                 }
  1549.  
  1550.                 if (shouldInclude) {
  1551.                     if (!result) {
  1552.                         result = text;
  1553.                     } else {
  1554.                         result += text;
  1555.                     }
  1556.                 }
  1557.             }
  1558.  
  1559.             cursor = cursor.nextSibling;
  1560.         }
  1561.  
  1562.         return result;
  1563.     };
  1564.  
  1565.     var getSingleElementByTagNameNS = function (domNode, namespaceURI, localName) {
  1566.         /// <summary>Gets the first element under 'domNode' with the spacified name and namespace.</summary>
  1567.         /// <param name="domNode">DOM element node.</param>
  1568.         /// <param name="namespaceURI" type="String">The namespace URI of the element to match.</param>
  1569.         /// <param name="localName" type="String">The local name of the element to match.</param>
  1570.         /// <returns>The first element found, null if none.</returns>
  1571.         /// <remarks>namespaceURI should be a specific namespace, otherwise the behavior is unspecified.</remarks>
  1572.  
  1573.         var result;
  1574.         if (domNode.getElementsByTagNameNS) {
  1575.             result = domNode.getElementsByTagNameNS(namespaceURI, localName);
  1576.             if (result.length !== 0) {
  1577.                 return result[0];
  1578.             }
  1579.         } else {
  1580.             var child = domNode.firstChild;
  1581.             while (child) {
  1582.                 if (child.nodeType === 1 &&
  1583.                     xmlLocalName(child) === localName &&
  1584.                     child.namespaceURI === namespaceURI) {
  1585.                     return child;
  1586.                 }
  1587.  
  1588.                 child = child.nextSibling;
  1589.             }
  1590.         }
  1591.  
  1592.         return null;
  1593.     };
  1594.  
  1595.     var checkParserErrorElement = function (element, text) {
  1596.         /// <summary>Checks whether the specified element is a parser error element.</summary>
  1597.         /// <param name="element">Element to check.</param>
  1598.         /// <param name="text" type="String">Original text that was being parsed.</param>
  1599.         /// <remarks>
  1600.         /// DOMParser-based parsers don't throw an exception but instead return a
  1601.         /// specific document; this function checks this case and normalizes to
  1602.         /// an exception being thrown.
  1603.         /// </remarks>
  1604.  
  1605.         // Firefox reports errors with a specific element.
  1606.         var parserErrorNS = "http://www.mozilla.org/newlayout/xml/parsererror.xml";
  1607.         var wrappedElement = xml_wrapNode(element, "");
  1608.         if (wrappedElement.localName === "parsererror" && wrappedElement.nsURI === parserErrorNS) {
  1609.             var reason = getElementText(element);
  1610.             var sourceTextElement = getSingleElementByTagNameNS(wrappedElement, parserErrorNS, "sourcetext");
  1611.             var srcText = (sourceTextElement) ? sourceTextElement.nodeValue : "";
  1612.             throw { message: reason, errorXmlText: text, srcText: srcText };
  1613.         }
  1614.  
  1615.         // Chrome reports errors by injecting a header with an error message.
  1616.         // The error may be localized, so instead we simply check for a header as the
  1617.         // top element or first child of the document.
  1618.         var xhtmlNS = "http://www.w3.org/1999/xhtml";
  1619.         if (wrappedElement.localName === "h3" && wrappedElement.nsURI == xhtmlNS ||
  1620.             getSingleElementByTagNameNS(element, xhtmlNS, "h3")) {
  1621.             throw { message: xmlInnerText(element), errorXmlText: text, srcText: "" };
  1622.         }
  1623.     };
  1624.  
  1625.     var xmlAddNamespaceAttribute = function (domNode, name, attributeNamespace) {
  1626.         /// <summary>Adds a namespace declaration attribute to the specified element node.</summary>
  1627.         /// <param name="domNode">DOM node for the element.</param>
  1628.         /// <param name="domNode" type="String">Attribute name, eg: xmlns, xmlns:foo, xmlns:bar.</param>
  1629.         /// <param name="attributeNamespace" type="String">Namespace to associate.</param>
  1630.  
  1631.         var doc = domNode.ownerDocument;
  1632.         var attribute;
  1633.         if (doc.createAttributeNS) {
  1634.             attribute = doc.createAttributeNS(xmlnsNS, name);
  1635.         } else {
  1636.             attribute = doc.createNode(2, name, xmlnsNS);
  1637.         }
  1638.  
  1639.         attribute.nodeValue = attributeNamespace;
  1640.         domNode.setAttributeNode(attribute);
  1641.     };
  1642.  
  1643.     var xmlAppendPreserving = function (domNode, text) {
  1644.         /// <summary>Appends a text node into the specified DOM element node.</summary>
  1645.         /// <param name="domNode">DOM node for the element.</param>
  1646.         /// <param name="text" type="String" mayBeNull="false">Text to append as a child of element.</param>
  1647.  
  1648.         if (hasLeadingOrTrailingWhitespace(text)) {
  1649.             var attr = xmlNewDomAttribute(domNode, "space", xmlNS, "xml");
  1650.             attr.value = "preserve";
  1651.         }
  1652.  
  1653.         var textNode = domNode.ownerDocument.createTextNode(text);
  1654.         domNode.appendChild(textNode);
  1655.     };
  1656.  
  1657.     var xmlAttributes = function (element, onAttributeCallback) {
  1658.         /// <summary>Iterates through the XML element's attributes and invokes the callback function for each one.</summary>
  1659.         /// <param name="element">Wrapped element to iterate over.</param>
  1660.         /// <param name="onAttributeCallback" type="Function">Callback function to invoke with wrapped attribute nodes.</param>
  1661.  
  1662.         var attribute;
  1663.         var domNode = element.domNode;
  1664.         var i, len;
  1665.         for (i = 0, len = domNode.attributes.length; i < len; i++) {
  1666.             attribute = domNode.attributes.item(i);
  1667.             onAttributeCallback(xml_wrapNode(attribute));
  1668.         }
  1669.     };
  1670.  
  1671.     var xmlAttribute = function (element, localName, nsURI) {
  1672.         /// <summary>Returns the value of an xml element attribute.</summary>
  1673.         return xml_attribute(element.domNode, localName, nsURI);
  1674.     };
  1675.  
  1676.     var xmlAttributeNode = function (domNode, localName, nsURI) {
  1677.         /// <summary>Gets an attribute node from an element.</summary>
  1678.         /// <param name="domNode">DOM node for the parent element.</param>
  1679.         /// <param name="localName" type="String">Local name for the attribute.</param>
  1680.         /// <param name="nsURI" type="String">Namespace URI for the attribute.</param>
  1681.         /// <returns>The attribute node, null if not found.</returns>
  1682.  
  1683.         var attributes = domNode.attributes;
  1684.         if (attributes.getNamedItemNS) {
  1685.             return attributes.getNamedItemNS(nsURI, localName);
  1686.         }
  1687.  
  1688.         return attributes.getQualifiedItem(localName, nsURI);
  1689.     };
  1690.  
  1691.     var xmlChildElements = function (element, onElementCallback) {
  1692.         /// <summary>Iterates through the XML element's child elements and invokes the callback function for each one.</summary>
  1693.         /// <param name="element">Wrapped element to iterate over.</param>
  1694.         /// <param name="onElementCallback" type="Function">Callback function to invoke with wrapped element nodes.</param>
  1695.  
  1696.         var child = element.domNode.firstChild;
  1697.         var childBaseURI;
  1698.         while (child !== null) {
  1699.             if (child.nodeType === 1) {
  1700.                 childBaseURI = normalizeURI(xml_baseURI(child), element.baseURI);
  1701.                 onElementCallback(xml_wrapNode(child, childBaseURI));
  1702.             }
  1703.  
  1704.             child = child.nextSibling;
  1705.         }
  1706.     };
  1707.  
  1708.     var xmlFirstElement = function (element) {
  1709.         /// <summary>Returns the first child element (wrapped) of the specified element.</summary>
  1710.         /// <param name="element">Element to get first child for.</param>
  1711.         /// <returns>This first child element (wrapped), null if there is none.</returns>
  1712.  
  1713.         var child = element.domNode.firstChild;
  1714.         var childBaseURI;
  1715.         while (child !== null) {
  1716.             if (child.nodeType === 1) {
  1717.                 childBaseURI = normalizeURI(xml_baseURI(child), element.baseURI);
  1718.                 return xml_wrapNode(child, childBaseURI);
  1719.             }
  1720.  
  1721.             child = child.nextSibling;
  1722.         }
  1723.  
  1724.         return null;
  1725.     };
  1726.  
  1727.     var xmlInnerText = function (domNode) {
  1728.         /// <summary>Returns the text value of an XML element.</summary>
  1729.         /// <param name="domNode">DOM element</param>
  1730.         /// <returns type="String">
  1731.         /// The text content of the node or the concatenated text
  1732.         /// representing the node and its descendants; never null.
  1733.         /// </returns>
  1734.  
  1735.         var result = domNode.text;
  1736.         if (result !== undefined) {
  1737.             return result;
  1738.         }
  1739.  
  1740.         result = "";
  1741.  
  1742.         var cursor = domNode.firstChild;
  1743.         if (cursor) {
  1744.             do {
  1745.                 // Process the node.
  1746.                 if (cursor.nodeType === 3 || cursor.nodeType === 4) {
  1747.                     result += cursor.nodeValue;
  1748.                 }
  1749.  
  1750.                 // Advance the node.
  1751.                 var next = cursor.firstChild;
  1752.                 if (!next) {
  1753.                     while (cursor !== domNode) {
  1754.                         next = cursor.nextSibling;
  1755.                         if (next) {
  1756.                             cursor = next;
  1757.                             break;
  1758.                         } else {
  1759.                             cursor = cursor.parentNode;
  1760.                         }
  1761.                     }
  1762.                 } else {
  1763.                     cursor = next;
  1764.                 }
  1765.             } while (cursor !== domNode);
  1766.         }
  1767.  
  1768.         return result;
  1769.     };
  1770.  
  1771.     var xmlLocalName = function (node) {
  1772.         /// <summary>Returns the localName of a XML node.</summary>
  1773.         /// <param name="node">DOM node to get value for.</param>
  1774.         /// <returns type="String">localName of node.</returns>
  1775.  
  1776.         if (node.localName) {
  1777.             return node.localName;
  1778.         }
  1779.  
  1780.         return node.baseName;
  1781.     };
  1782.  
  1783.     var xmlNewDocument = function (name, nsURI) {
  1784.         /// <summary>Creates a new XML document.</summary>
  1785.         /// <param name="name" type="String">Local name of the root element.</param>
  1786.         /// <param name="nsURI" type="String">Namespace of the root element.</param>
  1787.         /// <returns>The wrapped root element of the document.</returns>
  1788.  
  1789.         var dom;
  1790.  
  1791.         if (window.ActiveXObject) {
  1792.             dom = createDomParser();
  1793.             dom.documentElement = dom.createNode(1, name, nsURI);
  1794.         }
  1795.         else if (window.document.implementation && window.document.implementation.createDocument) {
  1796.             dom = window.document.implementation.createDocument(nsURI, name, null);
  1797.         }
  1798.  
  1799.         return xml_wrapNode(dom.documentElement);
  1800.     };
  1801.  
  1802.     var xmlNewDomAttribute = function (domNode, localName, nsURI, nsPrefix) {
  1803.         /// <summary>Creates a new unwrapped DOM attribute.</summary>
  1804.         /// <param name="domNode">Parent element to which new node should be added.</param>
  1805.         /// <param name="localName" type="String">Local name for attribute.</param>
  1806.         /// <param name="nsURI" type="String">Namespace URI for the attribute.</param>
  1807.         /// <param name="nsPrefix" type="String">Namespace prefix for attribute to be created.</param>
  1808.         /// <returns>The created DOM attribute.</returns>
  1809.  
  1810.         var doc = domNode.ownerDocument;
  1811.         var attribute;
  1812.         localName = (nsPrefix) ? (nsPrefix + ":" + localName) : localName;
  1813.         if (doc.createAttributeNS) {
  1814.             attribute = doc.createAttributeNS(nsURI, localName);
  1815.             domNode.setAttributeNodeNS(attribute);
  1816.         } else {
  1817.             attribute = doc.createNode(2, localName, nsURI || undefined);
  1818.             domNode.setAttributeNode(attribute);
  1819.         }
  1820.  
  1821.         return attribute;
  1822.     };
  1823.  
  1824.     var xmlNewDomElement = function (domNode, localName, nsURI, nsPrefix) {
  1825.         /// <summary>Creates a new unwrapped DOM element.</summary>
  1826.         /// <param name="domNode">Parent element to which new node should be added.</param>
  1827.         /// <param name="localName" type="String">Local name for element.</param>
  1828.         /// <param name="nsURI" type="String">Namespace URI for the element.</param>
  1829.         /// <param name="nsPrefix" type="String">Namespace prefix for attribute to be created.</param>
  1830.         /// <returns>The created DOM element.</returns>
  1831.  
  1832.         var doc = domNode.ownerDocument;
  1833.         var element;
  1834.         localName = (nsPrefix) ? (nsPrefix + ":" + localName) : localName;
  1835.         if (doc.createElementNS) {
  1836.             element = doc.createElementNS(nsURI, localName);
  1837.         } else {
  1838.             element = doc.createNode(1, localName, nsURI || undefined);
  1839.         }
  1840.  
  1841.         domNode.appendChild(element);
  1842.         return element;
  1843.     };
  1844.  
  1845.     var xmlNewElement = function (parent, name, nsURI, value) {
  1846.         /// <summary>Creates a new wrapped element.</summary>
  1847.         /// <param name="parent">Wrapped parent element to which new node should be added.</param>
  1848.         /// <param name="name" type="String">Local name for element.</param>
  1849.         /// <param name="nsURI" type="String">Namespace URI for the element.</param>
  1850.         /// <param name="value" type="String" mayBeNull="true">Text value to append in element, if applicable.</param>
  1851.         /// <returns>The created wrapped element.</returns>
  1852.  
  1853.         var dom = parent.domNode.ownerDocument;
  1854.  
  1855.         var element;
  1856.         if (dom.createElementNS) {
  1857.             element = dom.createElementNS(nsURI, name);
  1858.         } else {
  1859.             element = dom.createNode(1, name, nsURI || undefined);
  1860.         }
  1861.  
  1862.         if (value) {
  1863.             xmlAppendPreserving(element, value);
  1864.         }
  1865.  
  1866.         parent.domNode.appendChild(element);
  1867.         return xml_wrapNode(element);
  1868.     };
  1869.  
  1870.     var xmlNewAttribute = function (element, name, nsURI, value) {
  1871.         /// <summary>Creates a new wrapped attribute.</summary>
  1872.         /// <param name="element">Wrapped parent element to which new node should be added.</param>
  1873.         /// <param name="name" type="String">Local name for element.</param>
  1874.         /// <param name="nsURI" type="String">Namespace URI for the element.</param>
  1875.         /// <param name="value" type="String">Node value for the attribute.</param>
  1876.         /// <returns>The created wrapped attribute.</returns>
  1877.  
  1878.         var dom = element.domNode.ownerDocument;
  1879.  
  1880.         var attribute;
  1881.         if (dom.createAttributeNS) {
  1882.             attribute = dom.createAttributeNS(nsURI, name);
  1883.             attribute.value = value;
  1884.             element.domNode.setAttributeNodeNS(attribute);
  1885.         } else {
  1886.             attribute = dom.createNode(2, name, nsURI || undefined);
  1887.             attribute.value = value;
  1888.             element.domNode.setAttributeNode(attribute);
  1889.         }
  1890.  
  1891.         return xml_wrapNode(attribute);
  1892.     };
  1893.  
  1894.     var xmlQualifyXmlTagName = function (name, prefix) {
  1895.         /// <summary>Qualifies an XML tag name with the specified prefix.</summary>
  1896.         /// <param name="name" type="String">Element name</param>
  1897.         /// <param name="prefix" type="String">Prefix to use, possibly null.</param>
  1898.         /// <returns type="String">The qualified name.</returns>
  1899.         /// <remarks>This operates at the lexical level - there is no awareness of in-scope prefixes.</remarks>
  1900.  
  1901.         if (prefix) {
  1902.             return prefix + ":" + name;
  1903.         }
  1904.  
  1905.         return name;
  1906.     };
  1907.  
  1908.     var xmlParse = function (text, baseURI) {
  1909.         /// <summary>Returns an XML document from the specified text.</summary>
  1910.         /// <param name="text" type="String">Document text.</param>
  1911.         /// <param name="baseURI" type="String">Base URI for the document.</param>
  1912.         /// <returns>The wrapped root element of the document.</returns>
  1913.  
  1914.         var root = xml_parse(text);
  1915.         var rootBaseURI = normalizeURI(xml_baseURI(root), baseURI);
  1916.         return xml_wrapNode(root, rootBaseURI);
  1917.     };
  1918.  
  1919.     var xmlSerialize = function (root) {
  1920.         /// <summary>
  1921.         /// Returns the text representation of the document to which the specified node belongs.
  1922.         /// </summary>
  1923.         /// <param name="root">Wrapped element in the document to serialize.</param>
  1924.         /// <returns type="String">Serialized document.</returns>
  1925.  
  1926.         var dom = root.domNode.ownerDocument;
  1927.         return xmlSerializeNode(dom);
  1928.     };
  1929.  
  1930.     var xmlSerializeChildren = function (domNode) {
  1931.         /// <summary>Returns the XML representation of the all the descendants of the node.</summary>
  1932.         /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param>
  1933.         /// <returns type="String">The XML representation of all the descendants of the node.</returns>
  1934.  
  1935.         var children = domNode.childNodes;
  1936.         var i, len = children.length;
  1937.         if (len === 0) {
  1938.             return "";
  1939.         }
  1940.  
  1941.         var fragment = domNode.ownerDocument.createDocumentFragment();
  1942.         for (i = 0; i < len; i++) {
  1943.             fragment.appendChild(children[i].cloneNode(true));
  1944.         }
  1945.  
  1946.         return xmlSerializeNode(fragment);
  1947.     };
  1948.  
  1949.     var xmlSerializeNode = function (domNode) {
  1950.         /// <summary>Returns the XML representation of the node and all its descendants.</summary>
  1951.         /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param>
  1952.         /// <returns type="String">The XML representation of the node and all its descendants.</returns>
  1953.  
  1954.         var xml = domNode.xml;
  1955.         if (xml !== undefined) {
  1956.             return xml;
  1957.         }
  1958.  
  1959.         if (window.XMLSerializer) {
  1960.             var serializer = new window.XMLSerializer();
  1961.             return serializer.serializeToString(domNode);
  1962.         }
  1963.  
  1964.         throw { message: "XML serialization unsupported" };
  1965.     };
  1966.  
  1967.     var xml_attribute = function (domNode, localName, nsURI) {
  1968.         /// <summary>Gets the value of a DOM attribute.</summary>
  1969.         /// <param name="domNode">DOM element to get attribute for.</param>
  1970.         /// <param name="localName" type="String">Name of the attribute to get.</param>
  1971.         /// <param name="nsURI" type="String">Namespace of the attribute to get.</param>
  1972.         /// <returns type="String">Value of the attribute if found; null otherwise.</returns>
  1973.  
  1974.         if (domNode.getAttributeNS) {
  1975.             return domNode.getAttributeNS(nsURI || null, localName);
  1976.         }
  1977.  
  1978.         // The method is not supported so we work with the attributes collection.
  1979.         var node = domNode.attributes.getQualifiedItem(localName, nsURI);
  1980.         if (node) {
  1981.             return node.value;
  1982.         }
  1983.  
  1984.         return null;
  1985.     };
  1986.  
  1987.     var xml_baseURI = function (domNode) {
  1988.         /// <summary>Gets the value of the xml:base attribute on the specified element.</summary>
  1989.         /// <param name="domNode">Element to get attribute value from.</param>
  1990.         /// <returns type="String">Value of the xml:base attribute if found; null otherwise.</returns>
  1991.  
  1992.         return xml_attribute(domNode, "base", xmlNS);
  1993.     };
  1994.  
  1995.     var xml_parse = function (text) {
  1996.         /// <summary>Returns an XML document from the specified text.</summary>
  1997.         /// <param name="text" type="String">Document text.</param>
  1998.         /// <returns>The root element of the document.</returns>
  1999.  
  2000.         var dom = createDomParser();
  2001.         if (dom.parseFromString) {
  2002.             dom = dom.parseFromString(text, "text/xml");
  2003.             checkParserErrorElement(dom.documentElement, text);
  2004.         } else {
  2005.             dom.loadXML(text);
  2006.             if (dom.parseError.errorCode !== 0) {
  2007.                 throw { message: dom.parseError.reason, errorXmlText: text, srcText: dom.parseError.srcText };
  2008.             }
  2009.         }
  2010.  
  2011.         return dom.documentElement;
  2012.     };
  2013.  
  2014.     var xml_wrapNode = function (domNode, baseURI) {
  2015.         /// <summary>Creates a wrapped DOM node.</summary>
  2016.         /// <param name="domNode">DOM node to wrap.</param>
  2017.         /// <param name="baseURI" type="String">Base URI in scope for the node.</param>
  2018.         /// <returns type="Object">
  2019.         /// An object with the wrapped domNode, information about its in-scope URI, and simple
  2020.         /// access to name and namespace.
  2021.         /// </returns>
  2022.  
  2023.         var nsURI = domNode.namespaceURI;
  2024.         var nodeName = domNode.nodeName;
  2025.  
  2026.         // MSXML 3.0 doesn't return a namespaceURI for xmlns attributes.
  2027.         if (!nsURI) {
  2028.             if (domNode.nodeType === 2 && (nodeName === "xmlns" || nodeName.indexOf("xmlns:", 0) === 0)) {
  2029.                 nsURI = xmlnsNS;
  2030.             } else {
  2031.                 nsURI = null;
  2032.             }
  2033.         }
  2034.  
  2035.         return {
  2036.             baseURI: baseURI,
  2037.             domNode: domNode,
  2038.             localName: xmlLocalName(domNode),
  2039.             nsURI: nsURI
  2040.         };
  2041.     };
  2042.  
  2043.     var readODataXmlDocument = function (xmlRoot) {
  2044.         /// <summary>Reads an OData link(s) producing an object model in return.</summary>
  2045.         /// <param name="xmlRoot">Top-level element to read.</param>
  2046.         /// <returns type="Object">The object model representing the specified element.</returns>
  2047.  
  2048.         if (xmlRoot.nsURI === odataXmlNs) {
  2049.             switch (xmlRoot.localName) {
  2050.                 case "links":
  2051.                     return readLinks(xmlRoot);
  2052.                 case "uri":
  2053.                     return readUri(xmlRoot);
  2054.             }
  2055.         }
  2056.         return undefined;
  2057.     };
  2058.  
  2059.     var readLinks = function (linksElement) {
  2060.         /// <summary>Deserializes an OData XML links element.</summary>
  2061.         /// <param name="linksElement">XML links element.</param>
  2062.         /// <returns type="Object">A new object representing the links collection.</returns>
  2063.  
  2064.         var uris = [];
  2065.  
  2066.         xmlChildElements(linksElement, function (child) {
  2067.             if (child.localName === "uri" && child.nsURI === odataXmlNs) {
  2068.                 var uri = readUri(child);
  2069.                 uris.push(uri);
  2070.             }
  2071.         });
  2072.  
  2073.         return { results: uris };
  2074.     };
  2075.  
  2076.     var readUri = function (uriElement) {
  2077.         /// <summary>Deserializes an OData XML uri element.</summary>
  2078.         /// <param name="uriElement">XML uri element.</param>
  2079.         /// <returns type="Object">A new object representing the uri.</returns>
  2080.  
  2081.         return { uri: xmlInnerText(uriElement.domNode) };
  2082.     };
  2083.  
  2084.     var writeODataXmlDocument = function (data) {
  2085.         /// <summary>Writes the specified data into an OData XML document.</summary>
  2086.         /// <param name="data">Data to write.</param>
  2087.         /// <returns>The root of the DOM tree built.</returns>
  2088.  
  2089.         if (payloadTypeOf(data) === PAYLOADTYPE_COMPLEXTYPE &&
  2090.            !data.__metadata && data.hasOwnProperty("uri")) {
  2091.             return writeUri(data);
  2092.         }
  2093.  
  2094.         // Allow for undefined to be returned.
  2095.     };
  2096.  
  2097.     var writeUri = function (data) {
  2098.         /// <summary>Writes the specified uri data into an OData XML uri document.</summary>
  2099.         /// <param name="data">Uri data to write in intermediate format.</param>
  2100.         /// <returns>The feed element of the DOM tree built.</returns>
  2101.  
  2102.         var uri = writeODataXmlNode(null, "uri", odataXmlNs);
  2103.         if (data.uri) {
  2104.             xmlAppendPreserving(uri.domNode, data.uri);
  2105.         }
  2106.  
  2107.         return uri;
  2108.     };
  2109.  
  2110.     var writeODataXmlNode = function (parent, name, nsURI) {
  2111.         /// <summary>Writes the root of an OData XML document, possibly under an existing element.</summary>
  2112.         /// <param name="parent" mayBeNull="true">Element under which to create a new element.</param>
  2113.         /// <param name="name">Name for the new element.</param>
  2114.         /// <param name="nsURI" type="String">Namespace URI for the element.</param>
  2115.         /// <returns>The created element.</returns>
  2116.  
  2117.         if (parent) {
  2118.             return xmlNewElement(parent, name, nsURI);
  2119.         }
  2120.         return xmlNewDocument(name, nsURI);
  2121.     };
  2122.  
  2123.     var xmlParser = function (handler, text) {
  2124.         /// <summary>Parses an OData XML document.</summary>
  2125.         /// <param name="handler">This handler.</param>
  2126.         /// <param name="text" type="String">Document text.</param>
  2127.         /// <returns>An object representation of the document; undefined if not applicable.</returns>
  2128.  
  2129.         if (text) {
  2130.             var root = xmlParse(text);
  2131.             if (root) {
  2132.                 return readODataXmlDocument(root);
  2133.             }
  2134.         }
  2135.  
  2136.         // Allow for undefined to be returned.
  2137.     };
  2138.  
  2139.     var xmlSerializer = function (handler, data, context) {
  2140.         /// <summary>Serializes an OData XML object into a document.</summary>
  2141.         /// <param name="handler">This handler.</param>
  2142.         /// <param name="data" type="Object">Representation of feed or entry.</param>
  2143.         /// <param name="context" type="Object">Object with parsing context.</param>
  2144.         /// <returns>A text representation of the data object; undefined if not applicable.</returns>
  2145.  
  2146.         var cType = context.contentType = context.contentType || contentType(xmlMediaType);
  2147.         var result = undefined;
  2148.         if (cType && cType.mediaType === xmlMediaType) {
  2149.             var xmlDoc = writeODataXmlDocument(data);
  2150.             if (xmlDoc) {
  2151.                 result = xmlSerialize(xmlDoc);
  2152.             }
  2153.         }
  2154.         return result;
  2155.     };
  2156.  
  2157.     odata.xmlHandler = handler(xmlParser, xmlSerializer, xmlMediaType, "2.0");
  2158.  
  2159.  
  2160.  
  2161.     var atomAcceptTypes = ["application/atom+xml", "application/atomsvc+xml", "application/xml"];
  2162.     var atomMediaType = atomAcceptTypes[0];
  2163.  
  2164.     var inlineTag = xmlQualifyXmlTagName("inline", "m");
  2165.     var propertiesTag = xmlQualifyXmlTagName("properties", "m");
  2166.     var propertyTypeAttribute = xmlQualifyXmlTagName("type", "m");
  2167.     var propertyNullAttribute = xmlQualifyXmlTagName("null", "m");
  2168.  
  2169.     // These are the namespaces that are not considered ATOM extension namespaces.
  2170.     var nonExtensionNamepaces = [atomXmlNs, appXmlNs, xmlNS, xmlnsNS];
  2171.  
  2172.     // These are entity property mapping paths that have well-known paths.
  2173.     var knownCustomizationPaths = {
  2174.         SyndicationAuthorEmail: "author/email",
  2175.         SyndicationAuthorName: "author/name",
  2176.         SyndicationAuthorUri: "author/uri",
  2177.         SyndicationContributorEmail: "contributor/email",
  2178.         SyndicationContributorName: "contributor/name",
  2179.         SyndicationContributorUri: "contributor/uri",
  2180.         SyndicationPublished: "published",
  2181.         SyndicationRights: "rights",
  2182.         SyndicationSummary: "summary",
  2183.         SyndicationTitle: "title",
  2184.         SyndicationUpdated: "updated"
  2185.     };
  2186.  
  2187.     var isExtensionNs = function (nsURI) {
  2188.         /// <summary>Checks whether the specified namespace is an extension namespace to ATOM.</summary>
  2189.         /// <param type="String" name="nsURI">Namespace to check.</param>
  2190.         /// <returns type="Boolean">true if nsURI is an extension namespace to ATOM; false otherwise.</returns>
  2191.  
  2192.         return !(contains(nonExtensionNamepaces, nsURI));
  2193.     };
  2194.  
  2195.     var propertyTypeDefaultConverter = function (propertyValue) {
  2196.         /// <summary>Does a no-op conversion on the specified value.</summary>
  2197.         /// <param name="propertyValue">Value to convert.</param>
  2198.         /// <returns>Original property value.</returns>
  2199.  
  2200.         return propertyValue;
  2201.     };
  2202.  
  2203.     var parseBool = function (propertyValue) {
  2204.         /// <summary>Parses a string into a boolean value.</summary>
  2205.         /// <param name="propertyValue">Value to parse.</param>
  2206.         /// <returns type="Boolean">true if the property value is 'true'; false otherwise.</returns>
  2207.  
  2208.         return propertyValue === "true";
  2209.     };
  2210.  
  2211.     // The captured indices for this expression are:
  2212.     // 0     - complete input
  2213.     // 1,2,3 - year with optional minus sign, month, day
  2214.     // 4,5,6 - hours, minutes, seconds
  2215.     // 7     - optional milliseconds
  2216.     // 8     - everything else (presumably offset information)
  2217.     var parseDateTimeRE = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(.*)$/;
  2218.  
  2219.     var parseDateTimeMaybeOffset = function (value, withOffset) {
  2220.         /// <summary>Parses a string into a DateTime value.</summary>
  2221.         /// <param name="value" type="String">Value to parse.</param>
  2222.         /// <param name="withOffset" type="Boolean">Whether offset is expected.</param>
  2223.         /// <returns type="Date">The parsed value.</returns>
  2224.  
  2225.         // We cannot parse this in cases of failure to match or if offset information is specified.
  2226.         var parts = parseDateTimeRE.exec(value);
  2227.         var offset = (parts) ? getCanonicalTimezone(parts[8]) : null;
  2228.  
  2229.         if (!parts || (!withOffset && offset !== "Z")) {
  2230.             throw { message: "Invalid date/time value" };
  2231.         }
  2232.  
  2233.         // Pre-parse years, account for year '0' being invalid in dateTime.
  2234.         var year = parseInt10(parts[1]);
  2235.         if (year <= 0) {
  2236.             year++;
  2237.         }
  2238.  
  2239.         // Pre-parse optional milliseconds, fill in default. Fail if value is too precise.
  2240.         var ms = parts[7];
  2241.         var ns = 0;
  2242.         if (!ms) {
  2243.             ms = 0;
  2244.         } else {
  2245.             if (ms.length > 7) {
  2246.                 throw { message: "Cannot parse date/time value to given precision." };
  2247.             }
  2248.  
  2249.             ns = formatNumberWidth(ms.substring(3), 4, true);
  2250.             ms = formatNumberWidth(ms.substring(0, 3), 3, true);
  2251.            
  2252.             ms = parseInt10(ms);
  2253.             ns = parseInt10(ns);
  2254.         }
  2255.  
  2256.         // Pre-parse other time components and offset them if necessary.
  2257.         var hours = parseInt10(parts[4]);
  2258.         var minutes = parseInt10(parts[5]);
  2259.         var seconds = parseInt10(parts[6]);
  2260.         if (offset !== "Z") {
  2261.             // The offset is reversed to get back the UTC date, which is
  2262.             // what the API will eventually have.
  2263.             var timezone = parseTimezone(offset);
  2264.             var direction = -(timezone.d);
  2265.             hours += timezone.h * direction;
  2266.             minutes += timezone.m * direction;
  2267.         }
  2268.  
  2269.         // Set the date and time separately with setFullYear, so years 0-99 aren't biased like in Date.UTC.
  2270.         var result = new Date();
  2271.         result.setUTCFullYear(
  2272.             year,                       // Year.
  2273.             parseInt10(parts[2]) - 1,   // Month (zero-based for Date.UTC and setFullYear).
  2274.             parseInt10(parts[3])        // Date.
  2275.             );
  2276.         result.setUTCHours(hours, minutes, seconds, ms);
  2277.  
  2278.         if (isNaN(result.valueOf())) {
  2279.             throw { message: "Invalid date/time value" };
  2280.         }
  2281.  
  2282.         if (withOffset) {
  2283.             result.__edmType = "Edm.DateTimeOffset";
  2284.             result.__offset = offset;
  2285.         }
  2286.  
  2287.         if (ns) {
  2288.             result.__ns = ns;
  2289.         }
  2290.  
  2291.         return result;
  2292.     };
  2293.  
  2294.     var parseDateTime = function (propertyValue) {
  2295.         /// <summary>Parses a string into a DateTime value.</summary>
  2296.         /// <param name="propertyValue" type="String">Value to parse.</param>
  2297.         /// <returns type="Date">The parsed value.</returns>
  2298.  
  2299.         return parseDateTimeMaybeOffset(propertyValue, false);
  2300.     };
  2301.  
  2302.     var parseDateTimeOffset = function (propertyValue) {
  2303.         /// <summary>Parses a string into a DateTimeOffset value.</summary>
  2304.         /// <param name="propertyValue" type="String">Value to parse.</param>
  2305.         /// <returns type="Date">The parsed value.</returns>
  2306.         /// <remarks>
  2307.         /// The resulting object is annotated with an __edmType property and
  2308.         /// an __offset property reflecting the original intended offset of
  2309.         /// the value. The time is adjusted for UTC time, as the current
  2310.         /// timezone-aware Date APIs will only work with the local timezone.
  2311.         /// </remarks>
  2312.  
  2313.         return parseDateTimeMaybeOffset(propertyValue, true);
  2314.     };
  2315.  
  2316.     // Property type converters are parsers that convert strings into typed values.
  2317.     var propertyTypeConverters = {
  2318.         "Edm.Boolean": parseBool,
  2319.         "Edm.Binary": propertyTypeDefaultConverter,
  2320.         "Edm.DateTime": parseDateTime,
  2321.         "Edm.DateTimeOffset": parseDateTimeOffset,
  2322.         "Edm.Time": parseDuration,
  2323.         "Edm.Decimal": propertyTypeDefaultConverter,
  2324.         "Edm.Guid": propertyTypeDefaultConverter,
  2325.         "Edm.String": propertyTypeDefaultConverter,
  2326.         "Edm.Byte": parseInt10,
  2327.         "Edm.Double": parseFloat,
  2328.         "Edm.Single": parseFloat,
  2329.         "Edm.Int16": parseInt10,
  2330.         "Edm.Int32": parseInt10,
  2331.         "Edm.Int64": propertyTypeDefaultConverter,
  2332.         "Edm.SByte": parseInt10
  2333.     };
  2334.  
  2335.     var formatToString = function (value) {
  2336.         /// <summary>Formats a value by invoking .toString() on it.</summary>
  2337.         /// <param name="value" mayBeNull="false">Value to format.</param>
  2338.         /// <returns type="String">Formatted text.</returns>
  2339.  
  2340.         return value.toString();
  2341.     };
  2342.  
  2343.     // Property type formatters are serializers that convert typed values into strings.
  2344.     var propertyTypeFormatters = {
  2345.         "Edm.Binary": formatToString,
  2346.         "Edm.Boolean": formatToString,
  2347.         "Edm.Byte": formatToString,
  2348.         "Edm.DateTime": formatDateTimeOffset,
  2349.         "Edm.DateTimeOffset": formatDateTimeOffset,
  2350.         "Edm.Decimal": formatToString,
  2351.         "Edm.Double": formatToString,
  2352.         "Edm.Guid": formatToString,
  2353.         "Edm.Int16": formatToString,
  2354.         "Edm.Int32": formatToString,
  2355.         "Edm.Int64": formatToString,
  2356.         "Edm.SByte": formatToString,
  2357.         "Edm.Single": formatToString,
  2358.         "Edm.String": formatToString,
  2359.         "Edm.Time": formatDuration
  2360.     };
  2361.  
  2362.     var isPrimitiveType = function (typeName) {
  2363.         /// <summary>Checks whether the specified type name is a primitive type.</summary>
  2364.         /// <param name="typeName" type="String" mayBeNull="true">Name of type to check.</param>
  2365.         /// <returns type="Boolean">
  2366.         /// true if the type is the name of a primitive type; false otherwise.
  2367.         /// </returns>
  2368.  
  2369.         return typeName && (propertyTypeConverters[typeName] !== undefined);
  2370.     };
  2371.  
  2372.     var convertFromAtomPropertyText = function (value, targetType) {
  2373.         /// <summary>Converts a text value to the specified target type.</summary>
  2374.         /// <param name="value" type="String" mayBeNull="true">Text value to convert.</param>
  2375.         /// <param name="targetType" type="String" mayBeNull="true">Name of type to convert from.</param>
  2376.         /// <returns>The converted value.</returns>
  2377.  
  2378.         if (value !== null && targetType) {
  2379.             var converter = propertyTypeConverters[targetType];
  2380.             if (converter) {
  2381.                 value = converter(value);
  2382.             }
  2383.         }
  2384.  
  2385.         return value;
  2386.     };
  2387.  
  2388.     var convertToAtomPropertyText = function (value, targetType) {
  2389.         /// <summary>Converts a typed value from the specified target type into a text value.</summary>
  2390.         /// <param name="value">Typed value to convert.</param>
  2391.         /// <param name="targetType" type="String" mayBeNull="true">Name of type to convert to.<param>
  2392.         /// <returns type="String">The converted value as text.</returns>
  2393.  
  2394.         if (value !== null && targetType) {
  2395.             if (isDate(value)) {
  2396.                 targetType = (isDateTimeOffset(value)) ? "Edm.DateTimeOffset" : "Edm.DateTime";
  2397.             }
  2398.  
  2399.             var converter = propertyTypeFormatters[targetType];
  2400.             if (converter) {
  2401.                 value = converter(value);
  2402.             }
  2403.         }
  2404.  
  2405.         return value;
  2406.     };
  2407.  
  2408.     var readAtomDocument = function (atomElement, metadata) {
  2409.         /// <summary>Reads an ATOM entry, feed or service document, producing an object model in return.</summary>
  2410.         /// <param name="atomElement">Top-level element to read.</param>
  2411.         /// <param name="metadata">Metadata that describes the conceptual schema.</param>
  2412.         /// <returns type="Object">The object model representing the specified element, undefined if the top-level element is not part of the ATOM specification.</returns>
  2413.  
  2414.         // Handle feed and entry elements.
  2415.         if (atomElement.nsURI === atomXmlNs) {
  2416.             switch (atomElement.localName) {
  2417.                 case "feed":
  2418.                     return readAtomFeed(atomElement, metadata);
  2419.                 case "entry":
  2420.                     return readAtomEntry(atomElement, metadata);
  2421.             }
  2422.         }
  2423.  
  2424.         // Handle service documents.
  2425.         if (atomElement.nsURI === appXmlNs && atomElement.localName === "service") {
  2426.             return readAtomServiceDocument(atomElement);
  2427.         }
  2428.  
  2429.         // Allow undefined to be returned.
  2430.     };
  2431.  
  2432.     var readAtomFeed = function (atomFeed, metadata) {
  2433.         /// <summary>Deserializes an ATOM feed element.</summary>
  2434.         /// <param name="atomFeed">ATOM feed element.</param>
  2435.         /// <param name="metadata">Metadata that describes the conceptual schema.</param>
  2436.         /// <returns type="Object">A new object representing the feed.</returns>
  2437.         var feedMetadata = {};
  2438.         var feed = {
  2439.             results: [],
  2440.             __metadata: feedMetadata
  2441.         };
  2442.  
  2443.         feedMetadata.feed_extensions = readAtomExtensionAttributes(atomFeed);
  2444.  
  2445.         xmlChildElements(atomFeed, function (feedChild) {
  2446.             switch (feedChild.nsURI) {
  2447.                 case atomXmlNs:
  2448.                     switch (feedChild.localName) {
  2449.                         case "id":
  2450.                             feedMetadata.uri = normalizeURI(xmlInnerText(feedChild.domNode), feedChild.baseURI);
  2451.                             feedMetadata.uri_extensions = readAtomExtensionAttributes(feedChild);
  2452.                             break;
  2453.                         case "title":
  2454.                             feedMetadata.title = xmlInnerText(feedChild.domNode);
  2455.                             feedMetadata.title_extensions = readAtomExtensionAttributes(feedChild);
  2456.                             break;
  2457.                         case "entry":
  2458.                             var entry = readAtomEntry(feedChild, metadata);
  2459.                             feed.results.push(entry);
  2460.                             break;
  2461.                         case "link":
  2462.                             readAtomFeedLink(feedChild, feed);
  2463.                             break;
  2464.                     }
  2465.                     return;
  2466.                 case odataMetaXmlNs:
  2467.                     if (feedChild.localName === "count") {
  2468.                         feed.__count = parseInt10(xmlInnerText(feedChild.domNode));
  2469.                         return;
  2470.                     }
  2471.                     break;
  2472.             }
  2473.  
  2474.             var extension = readAtomExtensionElement(feedChild);
  2475.             feedMetadata.feed_extensions.push(extension);
  2476.         });
  2477.  
  2478.         return feed;
  2479.     };
  2480.  
  2481.     var readAtomFeedLink = function (feedLinkElement, feed) {
  2482.         /// <summary>Reads an ATOM link element for a feed.</summary>
  2483.         /// <param name="feedLinkElement">Link element to read.</param>
  2484.         /// <param name="feed">Feed object to be annotated with information.</param>
  2485.  
  2486.         var link = readAtomLink(feedLinkElement);
  2487.         var feedMetadata = feed.__metadata;
  2488.         switch (link.rel) {
  2489.             case "next":
  2490.                 feed.__next = link.href;
  2491.                 feedMetadata.next_extensions = link.extensions;
  2492.                 break;
  2493.             case "self":
  2494.                 feedMetadata.self = link.href;
  2495.                 feedMetadata.self_extensions = link.extensions;
  2496.                 break;
  2497.         }
  2498.     };
  2499.  
  2500.     var readAtomLink = function (linkElement) {
  2501.         /// <summary>Reads an ATOM link element.</summary>
  2502.         /// <param name="linkElement">Link element to read.</param>
  2503.         /// <returns type="Object">A link element representation.</returns>
  2504.  
  2505.         var link = { extensions: [] };
  2506.         var linkExtension;
  2507.  
  2508.         xmlAttributes(linkElement, function (attribute) {
  2509.             if (!attribute.nsURI) {
  2510.                 switch (attribute.localName) {
  2511.                     case "href":
  2512.                         link.href = normalizeURI(attribute.domNode.nodeValue, linkElement.baseURI);
  2513.                         return;
  2514.                     case "type":
  2515.                     case "rel":
  2516.                         link[attribute.localName] = attribute.domNode.nodeValue;
  2517.                         return;
  2518.                 }
  2519.             }
  2520.  
  2521.             if (isExtensionNs(attribute.nsURI)) {
  2522.                 linkExtension = readAtomExtensionAttribute(attribute);
  2523.                 link.extensions.push(linkExtension);
  2524.             }
  2525.         });
  2526.  
  2527.         if (!link.href) {
  2528.             throw { error: "href attribute missing on link element", element: linkElement };
  2529.         }
  2530.  
  2531.         return link;
  2532.     };
  2533.  
  2534.     var readAtomExtensionElement = function (atomExtension) {
  2535.         /// <summary>Reads an ATOM extension element (an element not in the ATOM namespaces).</summary>
  2536.         /// <param name="atomExtension">ATOM extension element.</param>
  2537.         /// <returns type="Object">An extension element representation.</returns>
  2538.  
  2539.         var extension = {
  2540.             name: atomExtension.localName,
  2541.             namespaceURI: atomExtension.nsURI,
  2542.             attributes: readAtomExtensionAttributes(atomExtension),
  2543.             children: []
  2544.         };
  2545.  
  2546.         xmlChildElements(atomExtension, function (child) {
  2547.             var childExtension = readAtomExtensionElement(child);
  2548.             extension.children.push(childExtension);
  2549.         });
  2550.  
  2551.         if (extension.children.length === 0) {
  2552.             var text = xmlInnerText(atomExtension.domNode);
  2553.             if (text) {
  2554.                 extension.value = text;
  2555.             }
  2556.         }
  2557.  
  2558.         return extension;
  2559.     };
  2560.  
  2561.     var readAtomExtensionAttributes = function (xmlElement) {
  2562.         /// <summary>Reads ATOM extension attributes from an element.</summary>
  2563.         /// <param name="xmlElement">ATOM element with zero or more extension attributes.</param>
  2564.         /// <returns type="Array">An array of extension attribute representations.</returns>
  2565.  
  2566.         var extensions = [];
  2567.         xmlAttributes(xmlElement, function (attribute) {
  2568.             if (isExtensionNs(attribute.nsURI)) {
  2569.                 var extension = readAtomExtensionAttribute(attribute);
  2570.                 extensions.push(extension);
  2571.             }
  2572.         });
  2573.  
  2574.         return extensions;
  2575.     };
  2576.  
  2577.     var readAtomExtensionAttribute = function (attribute) {
  2578.         /// <summary>Reads an ATOM extension attribute into an object.</summary>
  2579.         /// <param name="attribute">ATOM extension attribute.</param>
  2580.         /// <returns type="Object">An object with the attribute information.</returns>
  2581.  
  2582.         return {
  2583.             name: attribute.localName,
  2584.             namespaceURI: attribute.nsURI,
  2585.             value: attribute.domNode.nodeValue
  2586.         };
  2587.     };
  2588.  
  2589.     var getObjectValueByPath = function (path, item) {
  2590.         /// <summary>Gets a slashed path value from the specified item.</summary>
  2591.         /// <param name="path" type="String">Property path to read ('/'-separated).</param>
  2592.         /// <param name="item" type="Object">Object to get value from.</param>
  2593.         /// <returns>The property value, possibly undefined if any path segment is missing.</returns>
  2594.  
  2595.         // Fast path.
  2596.         if (path.indexOf('/') === -1) {
  2597.             return item[path];
  2598.         } else {
  2599.             var parts = path.split('/');
  2600.             var i, len;
  2601.             for (i = 0, len = parts.length; i < len; i++) {
  2602.                 // Avoid traversing a null object.
  2603.                 if (item === null) {
  2604.                     return undefined;
  2605.                 }
  2606.  
  2607.                 item = item[parts[i]];
  2608.                 if (item === undefined) {
  2609.                     return item;
  2610.                 }
  2611.             }
  2612.  
  2613.             return item;
  2614.         }
  2615.     };
  2616.  
  2617.     var setObjectValueByPath = function (path, target, value, propertyType) {
  2618.         /// <summary>Sets a slashed path value on the specified target.</summary>
  2619.         /// <param name="path" type="String">Property path to set ('/'-separated).</param>
  2620.         /// <param name="target" type="Object">Object to set value on.</param>
  2621.         /// <param name="value">Value to set.</param>
  2622.         /// <param name="propertyType" type="String" optional="true">Property type to set in metadata.</param>
  2623.  
  2624.         // Fast path.
  2625.         var propertyName;
  2626.         if (path.indexOf('/') === -1) {
  2627.             target[path] = value;
  2628.             propertyName = path;
  2629.         } else {
  2630.             var parts = path.split('/');
  2631.             var i, len;
  2632.             for (i = 0, len = (parts.length - 1); i < len; i++) {
  2633.                 // We construct each step of the way if the property is missing;
  2634.                 // if it's already initialized to null, we stop further processing.
  2635.                 var next = target[parts[i]];
  2636.                 if (next === undefined) {
  2637.                     next = {};
  2638.                     target[parts[i]] = next;
  2639.                 } else if (next === null) {
  2640.                     return;
  2641.                 }
  2642.  
  2643.                 target = next;
  2644.             }
  2645.  
  2646.             propertyName = parts[i];
  2647.             target[propertyName] = value;
  2648.         }
  2649.  
  2650.         if (propertyType) {
  2651.             var metadata = target.__metadata = target.__metadata || {};
  2652.             var properties = metadata.properties = metadata.properties || {};
  2653.             var property = properties[propertyName] = properties[propertyName] || {};
  2654.             property.type = propertyType;
  2655.         }
  2656.     };
  2657.  
  2658.     var expandCustomizationPath = function (path) {
  2659.         /// <summary>Expands a customization path if it's well-known.</summary>
  2660.         /// <param name="path" type="String">Path to expand.</param>
  2661.         /// <returns type="String">Expanded path or 'path' otherwise.</returns>
  2662.  
  2663.         return knownCustomizationPaths[path] || path;
  2664.     };
  2665.  
  2666.     var getXmlPathValue = function (ns, xmlPath, element, readAsXml) {
  2667.         /// <summary>Returns the text value of the element or attribute at xmlPath.</summary>
  2668.         /// <param name="ns" type="String">Namespace for elements to match.</param>
  2669.         /// <param name="xmlPath" type="String">
  2670.         /// '/'-separated list of element names, possibly ending with a '@'-prefixed attribute name.
  2671.         /// </param>
  2672.         /// <param name="element">Root element.</param>
  2673.         /// <param name="readAsXml">Whether the value should be read as XHTML.</param>
  2674.         /// <returns type="String">The text value of the node found, undefined if none.</returns>
  2675.  
  2676.         var parts = xmlPath.split('/');
  2677.         var i, len;
  2678.         for (i = 0, len = parts.length; i < len; i++) {
  2679.             if (parts[i].charAt(0) === "@") {
  2680.                 return xml_attribute(element, parts[i].substring(1), ns);
  2681.             } else {
  2682.                 element = getSingleElementByTagNameNS(element, ns, parts[i]);
  2683.                 if (!element) {
  2684.                     return undefined;
  2685.                 }
  2686.             }
  2687.         }
  2688.  
  2689.         if (readAsXml) {
  2690.             // Treat per XHTML in http://tools.ietf.org/html/rfc4287#section-3.1.1, including the DIV
  2691.             // in the content.
  2692.             return xmlSerializeChildren(element);
  2693.         } else {
  2694.             return xmlInnerText(element);
  2695.         }
  2696.     };
  2697.  
  2698.     var setXmlPathValue = function (ns, nsPrefix, xmlPath, element, writeAsKind, value) {
  2699.         /// <summary>Sets the text value of the element or attribute at xmlPath.</summary>
  2700.         /// <param name="ns" type="String">Namespace for elements to match.</param>
  2701.         /// <param name="nsPrefix" type="String">Namespace prefix for elements to be created.</param>
  2702.         /// <param name="xmlPath" type="String">
  2703.         /// '/'-separated list of element names, possibly ending with a '@'-prefixed attribute name.
  2704.         /// </param>
  2705.         /// <param name="element">Root element.</param>
  2706.         /// <param name="writeAsKind" type="String">Whether the value should be written as text, html or xhtml.</param>
  2707.         /// <param name="value" type="String">The text value of the node to write.</param>
  2708.  
  2709.         var target = element;
  2710.         var parts = xmlPath.split('/');
  2711.         var i, len;
  2712.         for (i = 0, len = parts.length; i < len; i++) {
  2713.             var next;
  2714.             if (parts[i].charAt(0) === "@") {
  2715.                 next = xmlAttributeNode(target, parts[i].substring(1), ns);
  2716.                 if (!next) {
  2717.                     next = xmlNewDomAttribute(target, parts[i].substring(1), ns, nsPrefix);
  2718.                 }
  2719.             } else {
  2720.                 next = getSingleElementByTagNameNS(target, ns, parts[i]);
  2721.                 if (!next) {
  2722.                     next = xmlNewDomElement(target, parts[i], ns, nsPrefix);
  2723.                 }
  2724.             }
  2725.  
  2726.             target = next;
  2727.         }
  2728.  
  2729.         // Target can be an attribute (2) or an element (1).
  2730.         if (target.nodeType === 2) {
  2731.             target.value = value;
  2732.         } else {
  2733.             // The element should be empty at this point; we won't erase its contents.
  2734.             if (writeAsKind) {
  2735.                 target.setAttribute("type", writeAsKind);
  2736.             }
  2737.  
  2738.             if (writeAsKind === "xhtml") {
  2739.                 appendAsXml(target, value);
  2740.             } else {
  2741.                 xmlAppendPreserving(target, value);
  2742.             }
  2743.         }
  2744.     };
  2745.  
  2746.     var isElementEmpty = function (element) {
  2747.         /// <summary>Checks whether the specified XML element is empty.</summary>
  2748.         /// <param name="element">DOM element node to check.</param>
  2749.         /// <returns type="Boolean">true if the element is empty; false otherwise.</returns>
  2750.         /// <remarks>
  2751.         /// The element is considered empty if it doesn't have any attributes other than
  2752.         /// namespace declarations or type declarations and if it has no child nodes.
  2753.         /// </remarks>
  2754.  
  2755.         // If there are any child elements or text nodes, it's not empty.
  2756.         if (element.childNodes.length) {
  2757.             return false;
  2758.         }
  2759.  
  2760.         // If there are no attributes, then we know it's already empty.
  2761.         var attributes = element.attributes;
  2762.         var len = attributes.length;
  2763.         if (len === 0) {
  2764.             return true;
  2765.         }
  2766.  
  2767.         // Otherwise, we have to search for attributes that aren't namespace declarations.
  2768.         for (var i = 0; i < len; i++) {
  2769.             var attributeName = attributes[i].nodeName;
  2770.             if (attributeName !== "xmlns" && attributeName.indexOf("xmlns:") !== 0 && attributeName !== "m:type") {
  2771.                 return false;
  2772.             }
  2773.         }
  2774.  
  2775.         return true;
  2776.     };
  2777.     var removeXmlProperty = function (entryElement, propertyPath) {
  2778.         /// <summary>Removes a property from an entry.</summary>
  2779.         /// <param name="entryElement">XML element for an ATOM OData entry.</param>
  2780.         /// <param name="propertyPath" type="String">Property path to an element.</param>
  2781.  
  2782.         // Get the 'properties' node from 'content' or 'properties'.
  2783.         var propertiesElement = getSingleElementByTagNameNS(entryElement.domNode, odataMetaXmlNs, "properties");
  2784.         if (!propertiesElement) {
  2785.             var contentElement = getSingleElementByTagNameNS(entryElement.domNode, atomXmlNs, "content");
  2786.             if (contentElement) {
  2787.                 propertiesElement = getSingleElementByTagNameNS(contentElement, odataMetaXmlNs, "properties");
  2788.             }
  2789.         }
  2790.  
  2791.         if (propertiesElement) {
  2792.             // Traverse down to the parent of the property path.
  2793.             var propertyParentElement = propertiesElement;
  2794.             var parts = propertyPath.split("/");
  2795.             var i, len;
  2796.             for (i = 0, len = (parts.length - 1); i < len; i++) {
  2797.                 propertyParentElement = getSingleElementByTagNameNS(propertyParentElement, odataXmlNs, parts[i]);
  2798.                 if (!propertyParentElement) {
  2799.                     return;
  2800.                 }
  2801.             }
  2802.  
  2803.             // Remove the child from its parent.
  2804.             var propertyElement = getSingleElementByTagNameNS(propertyParentElement, odataXmlNs, parts[i]);
  2805.             if (propertyElement) {
  2806.                 propertyParentElement.removeChild(propertyElement);
  2807.             }
  2808.  
  2809.             // Remove empty elements up the parent chain.
  2810.             var candidate = propertyParentElement;
  2811.             while (candidate !== propertiesElement && isElementEmpty(candidate)) {
  2812.                 var parent = candidate.parentNode;
  2813.                 parent.removeChild(candidate);
  2814.                 candidate = parent;
  2815.             }
  2816.         }
  2817.     };
  2818.  
  2819.     var applyEntryCustomizationToEntry = function (customization, sourcePath, entryElement, entryObject, propertyType, suffix, context) {
  2820.         /// <summary>Applies a specific feed customization item to an entry.</summary>
  2821.         /// <param name="customization">Object with customization description.</param>
  2822.         /// <param name="sourcePath">Property path to map ('source' in the description).</param>
  2823.         /// <param name="entryElement">XML element for the entry that corresponds to the object being written.</param>
  2824.         /// <param name="entryObject">Object being written.</param>
  2825.         /// <param name="propertyType" type="String">Name of property type to write.</param>
  2826.         /// <param name="suffix" type="String">Suffix to feed customization properties.</param>
  2827.         /// <param name="context">Context used for serialization.</param>
  2828.  
  2829.         var targetPath = customization["FC_TargetPath" + suffix];
  2830.         var xmlPath = expandCustomizationPath(targetPath);
  2831.         var xmlNamespace = (targetPath !== xmlPath) ? atomXmlNs : customization["FC_NsUri" + suffix];
  2832.         var keepInContent = (customization["FC_KeepInContent" + suffix] === "true") ? true : false;
  2833.         var writeAsKind = customization["FC_ContentKind" + suffix];
  2834.         var prefix = customization["FC_NsPrefix" + suffix] || null;
  2835.  
  2836.         // Get the value to be written.
  2837.         var value = getObjectValueByPath(sourcePath, entryObject);
  2838.  
  2839.         // Special case: for null values, the 'property' should be left as it was generated.
  2840.         // undefined values will appear when metadata describe a property the object doesn't have.
  2841.         if (!assigned(value)) {
  2842.             return;
  2843.         }
  2844.  
  2845.         // Remove the property if it should not be kept in content.
  2846.         if (!keepInContent) {
  2847.             context.dataServiceVersion = "2.0";
  2848.             removeXmlProperty(entryElement, sourcePath);
  2849.         }
  2850.  
  2851.         // Set/create the subtree for the property path with the appropriate value.
  2852.         value = convertToAtomPropertyText(value, propertyType);
  2853.         setXmlPathValue(xmlNamespace, prefix, xmlPath, entryElement.domNode, writeAsKind, value);
  2854.     };
  2855.  
  2856.     var applyEntryCustomizationToObject = function (customization, sourcePath, entryElement, entryObject, propertyType, suffix) {
  2857.         /// <summary>Applies a specific feed customization item to an object.</summary>
  2858.         /// <param name="customization">Object with customization description.</param>
  2859.         /// <param name="sourcePath">Property path to set ('source' in the description).</param>
  2860.         /// <param name="entryElement">XML element for the entry that corresponds to the object being read.</param>
  2861.         /// <param name="entryObject">Object being read.</param>
  2862.         /// <param name="propertyType" type="String">Name of property type to set.</param>
  2863.         /// <param name="suffix" type="String">Suffix to feed customization properties.</param>
  2864.  
  2865.         // If keepInConent equals true then we do nothing as the object has been deserialized at this point.
  2866.         if (customization["FC_KeepInContent" + suffix] === "true") {
  2867.             return;
  2868.         }
  2869.         // An existing 'null' means that the property was set to null in the properties,
  2870.         // which overrides other items.
  2871.         if (getObjectValueByPath(sourcePath, entryObject) === null) {
  2872.             return;
  2873.         }
  2874.  
  2875.         var targetPath = customization["FC_TargetPath" + suffix];
  2876.         var xmlPath = expandCustomizationPath(targetPath);
  2877.         var xmlNamespace = (targetPath !== xmlPath) ? atomXmlNs : customization["FC_NsUri" + suffix];
  2878.         var readAsXhtml = (customization["FC_ContentKind" + suffix] === "xhtml");
  2879.         var value = getXmlPathValue(xmlNamespace, xmlPath, entryElement.domNode, readAsXhtml);
  2880.  
  2881.         // If the XML tree does not contain the necessary elements to read the value,
  2882.         // then it shouldn't be considered null, but rather ignored at all. This prevents
  2883.         // the customization from generating the object path down to the property.
  2884.         if (value === undefined) {
  2885.             return;
  2886.         }
  2887.  
  2888.         value = convertFromAtomPropertyText(value, propertyType);
  2889.  
  2890.         // Set the value on the object.
  2891.         setObjectValueByPath(sourcePath, entryObject, value, propertyType);
  2892.     };
  2893.  
  2894.     var lookupPropertyType = function (metadata, entityType, path) {
  2895.         /// <summary>Looks up the type of a property given its path in an entity type.</summary>
  2896.         /// <param name="metadata">Metadata in which to search for base and complex types.</param>
  2897.         /// <param name="entityType">Entity type to which property belongs.</param>
  2898.         /// <param name="path" type="String" mayBeNull="false">Property path to look at.</param>
  2899.         /// <returns type="String">The name of the property type; possibly null.</returns>
  2900.  
  2901.         var parts = path.split("/");
  2902.         var i, len;
  2903.         while (entityType) {
  2904.             // Keep track of the type being traversed, necessary for complex types.
  2905.             var traversedType = entityType;
  2906.  
  2907.             for (i = 0, len = parts.length; i < len; i++) {
  2908.                 // Traverse down the structure as necessary.
  2909.                 var properties = traversedType.property;
  2910.                 if (!properties) {
  2911.                     break;
  2912.                 }
  2913.  
  2914.                 // Find the property by scanning the property list (might be worth pre-processing).
  2915.                 var propertyFound = lookupProperty(properties, parts[i]);
  2916.                 if (!propertyFound) {
  2917.                     break;
  2918.                 }
  2919.  
  2920.                 var propertyType = propertyFound.type;
  2921.  
  2922.                 // We could in theory still be missing types, but that would
  2923.                 // be caused by a malformed path.
  2924.                 if (!propertyType || isPrimitiveType(propertyType)) {
  2925.                     return propertyType || null;
  2926.                 }
  2927.  
  2928.                 traversedType = lookupComplexType(propertyType, metadata);
  2929.                 if (!traversedType) {
  2930.                     return null;
  2931.                 }
  2932.             }
  2933.  
  2934.             // Traverse up the inheritance chain.
  2935.             entityType = lookupEntityType(entityType.baseType, metadata);
  2936.         }
  2937.  
  2938.         return null;
  2939.     };
  2940.  
  2941.     var applyMetadataToObject = function (entryElement, entryObject, metadata) {
  2942.         /// <summary>Applies feed customization properties to an object being read.</summary>
  2943.         /// <param name="entryElement">XML element for the entry that corresponds to the object being read.</param>
  2944.         /// <param name="entryObject">Object being read.</param>
  2945.         /// <param name="metadata">Metadata that describes the conceptual schema.</param>
  2946.  
  2947.         if (!metadata || metadata.length === 0) {
  2948.             return;
  2949.         }
  2950.  
  2951.         var typeName = entryObject.__metadata.type;
  2952.         while (typeName) {
  2953.             var entityType = lookupEntityType(typeName, metadata);
  2954.             if (!entityType) {
  2955.                 return;
  2956.             }
  2957.  
  2958.             // Apply all feed customizations from the entity and each of its properties.
  2959.             var propertyType;
  2960.             var source = entityType.FC_SourcePath;
  2961.             if (source) {
  2962.                 propertyType = lookupPropertyType(metadata, entityType, source);
  2963.                 applyEntryCustomizationToObject(entityType, source, entryElement, entryObject, propertyType, "");
  2964.             }
  2965.  
  2966.             var properties = entityType.property;
  2967.             if (properties) {
  2968.                 var i, len;
  2969.                 for (i = 0, len = properties.length; i < len; i++) {
  2970.                     var property = properties[i];
  2971.                     var suffixCounter = 0;
  2972.                     var suffix = "";
  2973.                     while (property["FC_TargetPath" + suffix]) {
  2974.                         source = property.name;
  2975.                         propertyType = property.type;
  2976.  
  2977.                         var sourcePath = property["FC_SourcePath" + suffix];
  2978.                         if (sourcePath) {
  2979.                             source += "/" + sourcePath;
  2980.                             propertyType = lookupPropertyType(metadata, entityType, source);
  2981.                         }
  2982.  
  2983.                         applyEntryCustomizationToObject(property, source, entryElement, entryObject, propertyType, suffix);
  2984.                         suffixCounter++;
  2985.                         suffix = "_" + suffixCounter;
  2986.                     }
  2987.                 }
  2988.             }
  2989.  
  2990.             // Apply feed customizations from base types.
  2991.             typeName = entityType.baseType;
  2992.         }
  2993.     };
  2994.  
  2995.     var readAtomEntry = function (atomEntry, metadata) {
  2996.         /// <summary>Reads an ATOM entry in OData format.</summary>
  2997.         /// <param name="atomEntry">XML element for the entry.</param>
  2998.         /// <param name="metadata">Metadata that describes the conceptual schema.</param>
  2999.         /// <returns type="Object">An object in payload representation format.</returns>
  3000.  
  3001.         var entryMetadata = {};
  3002.         var entry = {
  3003.             __metadata: entryMetadata
  3004.         };
  3005.  
  3006.         var etag = xmlAttribute(atomEntry, "etag", odataMetaXmlNs);
  3007.         if (etag) {
  3008.             entryMetadata.etag = etag;
  3009.         }
  3010.  
  3011.         xmlChildElements(atomEntry, function (entryChild) {
  3012.             if (entryChild.nsURI === atomXmlNs) {
  3013.                 switch (entryChild.localName) {
  3014.                     case "id":
  3015.                         entryMetadata.uri = normalizeURI(xmlInnerText(entryChild.domNode), entryChild.baseURI);
  3016.                         entryMetadata.uri_extensions = readAtomExtensionAttributes(entryChild);
  3017.                         break;
  3018.                     case "category":
  3019.                         readAtomEntryType(entryChild, entry, entryMetadata);
  3020.                         break;
  3021.                     case "content":
  3022.                         readAtomEntryContent(entryChild, entry);
  3023.                         break;
  3024.                     case "link":
  3025.                         readAtomEntryLink(entryChild, entry, metadata);
  3026.                         break;
  3027.                 }
  3028.             }
  3029.  
  3030.             if (entryChild.nsURI === odataMetaXmlNs && entryChild.localName === "properties") {
  3031.                 readAtomEntryStructuralObject(entryChild, entry, entryMetadata);
  3032.             }
  3033.         });
  3034.  
  3035.         // Apply feed customizations if applicable.
  3036.         applyMetadataToObject(atomEntry, entry, metadata);
  3037.  
  3038.         return entry;
  3039.     };
  3040.  
  3041.     var readAtomEntryType = function (atomCategory, entry, entryMetadata) {
  3042.         /// <summary>Reads type information from an ATOM category element.</summary>
  3043.         /// <param name="atomCategory">XML category element.</param>
  3044.         /// <param name="entry">Entry object to update with information.</param>
  3045.         /// <param name="entryMetadata">entry.__metadata, passed as an argument simply to help minify the code.</param>
  3046.  
  3047.         var scheme = xmlAttribute(atomCategory, "scheme");
  3048.         var term = xmlAttribute(atomCategory, "term");
  3049.  
  3050.         if (scheme === odataScheme) {
  3051.             if (entry.__metadata.type) {
  3052.                 throw { message: "Invalid AtomPub document: multiple category elements defining the entry type were encounterd withing an entry", element: atomCategory };
  3053.             }
  3054.  
  3055.             entryMetadata.type = term;
  3056.             entryMetadata.type_extensions = [];
  3057.  
  3058.             var typeExtension;
  3059.             xmlAttributes(atomCategory, function (attribute) {
  3060.                 if (!attribute.nsURI) {
  3061.                     if (attribute.localName !== "scheme" && attribute.localName !== "term") {
  3062.                         typeExtension = readAtomExtensionAttribute(attribute);
  3063.                         entryMetadata.type_extensions.push(typeExtension);
  3064.                     }
  3065.                 } else if (isExtensionNs(attribute.nsURI)) {
  3066.                     typeExtension = readAtomExtensionAttribute(attribute);
  3067.                     entryMetadata.type_extensions.push(typeExtension);
  3068.                 }
  3069.             });
  3070.         }
  3071.     };
  3072.  
  3073.     var readAtomEntryContent = function (atomEntryContent, entry) {
  3074.         /// <summary>Reads an ATOM content element.</summary>
  3075.         /// <param name="atomEntryContent">XML entry element.</param>
  3076.         /// <param name="entry">Entry object to update with information.</param>
  3077.  
  3078.         var src = xmlAttribute(atomEntryContent, "src");
  3079.         var type = xmlAttribute(atomEntryContent, "type");
  3080.         var entryMetadata = entry.__metadata;
  3081.  
  3082.         if (src) {
  3083.             if (!type) {
  3084.                 throw { message: "Invalid AtomPub document: content element must specify the type attribute if the src attribute is also specified", element: atomEntryContent };
  3085.             }
  3086.  
  3087.             entryMetadata.media_src = normalizeURI(src, atomEntryContent.baseURI);
  3088.             entryMetadata.content_type = type;
  3089.         }
  3090.  
  3091.         xmlChildElements(atomEntryContent, function (contentChild) {
  3092.             if (src) {
  3093.                 throw { message: "Invalid AtomPub document: content element must not have child elements if the src attribute is specified", element: atomEntryContent };
  3094.             }
  3095.  
  3096.             if (contentChild.nsURI === odataMetaXmlNs && contentChild.localName === "properties") {
  3097.                 readAtomEntryStructuralObject(contentChild, entry, entryMetadata);
  3098.             }
  3099.         });
  3100.     };
  3101.  
  3102.     var readAtomEntryEditMediaLink = function (link, entry, entryMetadata) {
  3103.         /// <summary>Reads an ATOM media link element.</summary>
  3104.         /// <param name="link">Link representation (not the XML element).</param>
  3105.         /// <param name="entry">Entry object to update with information.</param>
  3106.         /// <param name="entryMetadata">entry.__metadata, passed as an argument simply to help minify the code.</param>
  3107.  
  3108.         entryMetadata.edit_media = link.href;
  3109.         entryMetadata.edit_media_extensions = [];
  3110.  
  3111.         // Read the link extensions.
  3112.         var i, len;
  3113.         for (i = 0, len = link.extensions.length; i < len; i++) {
  3114.             if (link.extensions[i].namespaceURI === odataMetaXmlNs && link.extensions[i].name === "etag") {
  3115.                 entryMetadata.media_etag = link.extensions[i].value;
  3116.             } else {
  3117.                 entryMetadata.edit_media_extensions.push(link.extensions[i]);
  3118.             }
  3119.         }
  3120.     };
  3121.  
  3122.     var readAtomEntryLink = function (atomEntryLink, entry, metadata) {
  3123.         /// <summary>Reads a link element on an entry.</summary>
  3124.         /// <param name="atomEntryLink">'link' element on the entry.</param>
  3125.         /// <param name="entry">An object in payload representation format.</param>
  3126.         /// <param name="metadata">Metadata that describes the conceptual schema.</param>
  3127.  
  3128.         var link = readAtomLink(atomEntryLink);
  3129.         var entryMetadata = entry.__metadata;
  3130.         switch (link.rel) {
  3131.             case "self":
  3132.                 entryMetadata.self = link.href;
  3133.                 entryMetadata.self_link_extensions = link.extensions;
  3134.                 break;
  3135.             case "edit":
  3136.                 entryMetadata.edit = link.href;
  3137.                 entryMetadata.edit_link_extensions = link.extensions;
  3138.                 break;
  3139.             case "edit-media":
  3140.                 readAtomEntryEditMediaLink(link, entry, entryMetadata);
  3141.                 break;
  3142.             default:
  3143.                 if (link.rel.indexOf(odataRelatedPrefix) === 0) {
  3144.                     readAtomEntryDeferredProperty(atomEntryLink, link, entry, metadata);
  3145.                 }
  3146.                 break;
  3147.         }
  3148.     };
  3149.  
  3150.     var readAtomEntryDeferredProperty = function (atomEntryLink, link, entry, metadata) {
  3151.         /// <summary>Reads a potentially-deferred property on an entry.</summary>
  3152.         /// <param name="atomEntryLink">'link' element on the entry.</param>
  3153.         /// <param name="link">Parsed link object.</param>
  3154.         /// <param name="entry">An object in payload representation format.</param>
  3155.         /// <param name="metadata">Metadata that describes the conceptual schema.</param>
  3156.  
  3157.         var propertyName = link.rel.substring(odataRelatedPrefix.length);
  3158.  
  3159.         // undefined is used as a flag that inline data was not found (as opposed to
  3160.         // found with a value or with null).
  3161.         var inlineData = undefined;
  3162.  
  3163.         // Get any inline data.
  3164.         xmlChildElements(atomEntryLink, function (child) {
  3165.             if (child.nsURI === odataMetaXmlNs && child.localName === "inline") {
  3166.                 var inlineRoot = xmlFirstElement(child);
  3167.                 if (inlineRoot) {
  3168.                     inlineData = readAtomDocument(inlineRoot, metadata);
  3169.                 } else {
  3170.                     inlineData = null;
  3171.                 }
  3172.             }
  3173.         });
  3174.  
  3175.         // If the link has no inline content, we consider it deferred.
  3176.         if (inlineData === undefined) {
  3177.             inlineData = { __deferred: { uri: link.href} };
  3178.         }
  3179.  
  3180.         // Set the property value on the entry object.
  3181.         entry[propertyName] = inlineData;
  3182.  
  3183.         // Set the extra property information on the entry object metadata.
  3184.         entry.__metadata.properties = entry.__metadata.properties || {};
  3185.         entry.__metadata.properties[propertyName] = {
  3186.             extensions: link.extensions
  3187.         };
  3188.     };
  3189.  
  3190.     var readAtomEntryStructuralObject = function (propertiesElement, parent, parentMetadata) {
  3191.         /// <summary>Reads an atom entry's property as a structural object and sets its value in the parent and the metadata in the parentMetadata objects.</summary>
  3192.         /// <param name="propertiesElement">XML element for the 'properties' node.</param>
  3193.         /// <param name="parent">
  3194.         /// Object that will contain the property value. It can be either an antom entry or
  3195.         /// an atom complex property object.
  3196.         /// </param>
  3197.         /// <param name="parentMetadata">Object that will contain the property metadata. It can be either an atom entry metadata or a complex property metadata object</param>
  3198.  
  3199.         xmlChildElements(propertiesElement, function (property) {
  3200.             if (property.nsURI === odataXmlNs) {
  3201.                 parentMetadata.properties = parentMetadata.properties || {};
  3202.                 readAtomEntryProperty(property, parent, parentMetadata.properties);
  3203.             }
  3204.         });
  3205.     };
  3206.  
  3207.     var readAtomEntryProperty = function (property, parent, metadata) {
  3208.         /// <summary>Reads a property on an ATOM OData entry.</summary>
  3209.         /// <param name="property">XML element for the property.</param>
  3210.         /// <param name="parent">
  3211.         /// Object that will contain the property value. It can be either an antom entry or
  3212.         /// an atom complex property object.
  3213.         /// </param>
  3214.         /// <param name="metadata">Metadata for the object that will contain the property value.</param>
  3215.  
  3216.         var propertyNullValue = null;
  3217.         var propertyTypeValue = "Edm.String";
  3218.         var propertyExtensions = [];
  3219.  
  3220.         xmlAttributes(property, function (attribute) {
  3221.             if (attribute.nsURI === odataMetaXmlNs) {
  3222.                 switch (attribute.localName) {
  3223.                     case "null":
  3224.                         propertyNullValue = attribute.domNode.nodeValue;
  3225.                         return;
  3226.                     case "type":
  3227.                         propertyTypeValue = attribute.domNode.nodeValue;
  3228.                         return;
  3229.                 };
  3230.             }
  3231.  
  3232.             if (isExtensionNs(attribute.nsURI)) {
  3233.                 var extension = readAtomExtensionAttribute(attribute);
  3234.                 propertyExtensions.push(extension);
  3235.             }
  3236.         });
  3237.  
  3238.         var propertyValue = null;
  3239.         var propertyMetadata = {
  3240.             type: propertyTypeValue,
  3241.             extensions: propertyExtensions
  3242.         };
  3243.  
  3244.         if (propertyNullValue !== "true") {
  3245.             propertyValue = xmlInnerText(property.domNode);
  3246.             if (isPrimitiveType(propertyTypeValue)) {
  3247.                 propertyValue = convertFromAtomPropertyText(propertyValue, propertyTypeValue);
  3248.             } else {
  3249.                 // Probe for a complex type and read it.
  3250.                 if (xmlFirstElement(property)) {
  3251.                     propertyValue = { __metadata: { type: propertyTypeValue} };
  3252.                     readAtomEntryStructuralObject(property, propertyValue, propertyMetadata);
  3253.                 }
  3254.             }
  3255.         }
  3256.  
  3257.         parent[property.localName] = propertyValue;
  3258.         metadata[property.localName] = propertyMetadata;
  3259.     };
  3260.  
  3261.     var readAtomServiceDocument = function (atomServiceDoc) {
  3262.         /// <summary>Reads an atom service document</summary>
  3263.         /// <param name="atomServiceDoc">An element node that represents the root service element of an AtomPub service document</param>
  3264.         /// <returns type="Object">An object that contains the properties of the service document</returns>
  3265.  
  3266.         // Consider handling Accept and Category elements.
  3267.  
  3268.         var serviceDoc = {
  3269.             workspaces: [],
  3270.             extensions: []
  3271.         };
  3272.  
  3273.         // Find all the workspace elements.
  3274.         xmlChildElements(atomServiceDoc, function (child) {
  3275.             if (child.nsURI === appXmlNs && child.localName === "workspace") {
  3276.                 var workspace = readAtomServiceDocumentWorkspace(child);
  3277.                 serviceDoc.workspaces.push(workspace);
  3278.             } else {
  3279.                 var serviceExtension = readAtomExtensionElement(atomServiceDoc);
  3280.                 serviceDoc.extensions.push(serviceExtension);
  3281.             }
  3282.         });
  3283.  
  3284.         // AtomPub (RFC 5023 Section 8.3.1) says a service document MUST contain one or
  3285.         // more workspaces. Throw if we don't find any.
  3286.         if (serviceDoc.workspaces.length === 0) {
  3287.             throw { message: "Invalid AtomPub service document: No workspace element found.", element: atomServiceDoc };
  3288.         }
  3289.  
  3290.         return serviceDoc;
  3291.     };
  3292.  
  3293.     var readAtomServiceDocumentWorkspace = function (atomWorkspace) {
  3294.         /// <summary>Reads a single workspace element from an atom service document</summary>
  3295.         /// <param name="atomWorkspace">An element node that represents a workspace element of an AtomPub service document</param>
  3296.         /// <returns type="Object">An object that contains the properties of the workspace</returns>
  3297.  
  3298.         var workspace = {
  3299.             collections: [],
  3300.             extensions: []
  3301.         };
  3302.  
  3303.         xmlChildElements(atomWorkspace, function (child) {
  3304.             if (child.nsURI === atomXmlNs) {
  3305.                 if (child.localName === "title") {
  3306.                     if (atomWorkspace.title) {
  3307.                         throw { message: "Invalid AtomPub service document: workspace has more than one child title element", element: child };
  3308.                     }
  3309.  
  3310.                     workspace.title = xmlInnerText(child.domNode);
  3311.                 }
  3312.             } else if (child.nsURI === appXmlNs) {
  3313.                 if (child.localName === "collection") {
  3314.                     var collection = readAtomServiceDocumentCollection(child, workspace);
  3315.                     workspace.collections.push(collection);
  3316.                 }
  3317.             } else {
  3318.                 var extension = readAtomExtensionElement(atomWorkspace);
  3319.                 workspace.extensions.push(extension);
  3320.             }
  3321.         });
  3322.  
  3323.         workspace.title = workspace.title || "";
  3324.  
  3325.         return workspace;
  3326.     };
  3327.  
  3328.     var readAtomServiceDocumentCollection = function (atomCollection) {
  3329.         /// <summary>Reads a service document collection element into an object.</summary>
  3330.         /// <param name="atomCollection">An element node that represents a collection element of an AtomPub service document.</param>
  3331.         /// <returns type="Object">An object that contains the properties of the collection.</returns>
  3332.  
  3333.         var collection = {
  3334.             href: xmlAttribute(atomCollection, "href"),
  3335.             extensions: []
  3336.         };
  3337.  
  3338.         if (!collection.href) {
  3339.             throw { message: "Invalid AtomPub service document: collection has no href attribute", element: atomCollection };
  3340.         }
  3341.  
  3342.         collection.href = normalizeURI(collection.href, atomCollection.baseURI);
  3343.  
  3344.         xmlChildElements(atomCollection, function (child) {
  3345.             if (child.nsURI === atomXmlNs) {
  3346.                 if (child.localName === "title") {
  3347.                     if (collection.title) {
  3348.                         throw { message: "Invalid AtomPub service document: collection has more than one child title element", element: child };
  3349.                     }
  3350.  
  3351.                     collection.title = xmlInnerText(child.domNode);
  3352.                 }
  3353.             } else if (child.nsURI !== appXmlNs) {
  3354.                 var extension = readAtomExtensionElement(atomCollection);
  3355.                 collection.extensions.push(extension);
  3356.             }
  3357.         });
  3358.  
  3359.         // AtomPub (RFC 5023 Section 8.3.3) says the collection element MUST contain
  3360.         // a title element. It's likely to be problematic if the service doc doesn't
  3361.         // have one so here we throw.
  3362.         if (!collection.title) {
  3363.             throw { message: "Invalid AtomPub service document: collection has no title element", element: atomCollection };
  3364.         }
  3365.  
  3366.         return collection;
  3367.     };
  3368.  
  3369.     var writeAtomDocument = function (data, context) {
  3370.         /// <summary>Writes the specified data into an OData ATOM document.</summary>
  3371.         /// <param name="data">Data to write.</param>
  3372.         /// <param name="context">Context used for serialization.</param>
  3373.         /// <returns>The root of the DOM tree built.</returns>
  3374.  
  3375.         var docRoot;
  3376.         var type = payloadTypeOf(data);
  3377.         switch (type) {
  3378.             case PAYLOADTYPE_FEEDORLINKS:
  3379.                 docRoot = writeAtomFeed(null, data, context);
  3380.                 break;
  3381.             case PAYLOADTYPE_ENTRY:
  3382.                 // FALLTHROUGH
  3383.             case PAYLOADTYPE_COMPLEXTYPE:
  3384.                 docRoot = writeAtomEntry(null, data, context);
  3385.                 break;
  3386.         }
  3387.  
  3388.         return docRoot;
  3389.     };
  3390.  
  3391.     var writeAtomRoot = function (parent, name) {
  3392.         /// <summary>Writes the root of an ATOM document, possibly under an existing element.</summary>
  3393.         /// <param name="parent" mayBeNull="true">Element under which to create a new element.</param>
  3394.         /// <param name="name">Name for the new element, to be created in the ATOM namespace.</param>
  3395.         /// <returns>The created element.</returns>
  3396.  
  3397.         if (parent) {
  3398.             return xmlNewElement(parent, name, atomXmlNs);
  3399.         }
  3400.  
  3401.         var result = xmlNewDocument(name, atomXmlNs);
  3402.  
  3403.         // Add commonly used namespaces.
  3404.         // ATOM is implied by the just-created element.
  3405.         // xmlAddNamespaceAttribute(result.domNode, "xmlns", atomXmlNs);
  3406.         xmlAddNamespaceAttribute(result.domNode, "xmlns:d", odataXmlNs);
  3407.         xmlAddNamespaceAttribute(result.domNode, "xmlns:m", odataMetaXmlNs);
  3408.  
  3409.         return result;
  3410.     };
  3411.  
  3412.     var writeAtomFeed = function (parent, data, context) {
  3413.         /// <summary>Writes the specified feed data into an OData ATOM feed.</summary>
  3414.         /// <param name="parent" mayBeNull="true">Parent to append feed tree to.</param>
  3415.         /// <param name="data">Feed data to write.</param>
  3416.         /// <param name="context">Context used for serialization.</param>
  3417.         /// <returns>The feed element of the DOM tree built.</returns>
  3418.  
  3419.         var feed = writeAtomRoot(parent, "feed");
  3420.         var entries = (isArray(data)) ? data : data.results;
  3421.         if (entries) {
  3422.             var i, len;
  3423.             for (i = 0, len = entries.length; i < len; i++) {
  3424.                 writeAtomEntry(feed, entries[i], context);
  3425.             }
  3426.         }
  3427.  
  3428.         return feed;
  3429.     };
  3430.  
  3431.     var writeAtomEntry = function (parent, data, context) {
  3432.         /// <summary>Appends an ATOM entry XML payload to the parent node.</summary>
  3433.         /// <param name="parent">Parent element.</param>
  3434.         /// <param name="data">Data object to write in intermediate format.</param>
  3435.         /// <param name="context">Context used for serialization.</param>
  3436.         /// <returns>The new entry.</returns>
  3437.  
  3438.         var entry = writeAtomRoot(parent, "entry");
  3439.  
  3440.         // Set up a default empty author name as required by ATOM.
  3441.         var author = xmlNewElement(entry, "author", atomXmlNs);
  3442.         xmlNewElement(author, "name", atomXmlNs);
  3443.  
  3444.         // Set up a default empty title as required by ATOM.
  3445.         xmlNewElement(entry, "title", atomXmlNs);
  3446.  
  3447.         var content = xmlNewElement(entry, "content", atomXmlNs);
  3448.         xmlNewAttribute(content, "type", null, "application/xml");
  3449.  
  3450.         var properties = xmlNewElement(content, propertiesTag, odataMetaXmlNs);
  3451.  
  3452.         var propertiesMetadata = (data.__metadata) ? data.__metadata.properties : null;
  3453.  
  3454.         writeAtomEntryMetadata(entry, data.__metadata);
  3455.         writeAtomEntryProperties(entry, properties, data, propertiesMetadata, context);
  3456.         applyMetadataToEntry(entry, data, context);
  3457.  
  3458.         return entry;
  3459.     };
  3460.  
  3461.     var applyMetadataToEntry = function (entry, data, context) {
  3462.         /// <summary>Applies feed customizations to the specified entry element.</summary>
  3463.         /// <param name="entry">Entry to apply feed customizations to.</param>
  3464.         /// <param name="data">Data object associated with the entry.</param>
  3465.         /// <param name="context">Context used for serialization.</param>
  3466.  
  3467.         if (!data.__metadata) {
  3468.             return;
  3469.         }
  3470.  
  3471.         var metadata = context.metadata;
  3472.         var entityType = lookupEntityType(data.__metadata.type, metadata);
  3473.         while (entityType) {
  3474.             // Apply all feed customizations from the entity and each of its properties.
  3475.             var propertyType;
  3476.             var source = entityType.FC_SourcePath;
  3477.             if (source) {
  3478.                 propertyType = lookupPropertyType(metadata, entityType, source);
  3479.                 applyEntryCustomizationToEntry(entityType, source, entry, data, propertyType, "", context);
  3480.             }
  3481.  
  3482.             var properties = entityType.property;
  3483.             if (properties) {
  3484.                 var i, len;
  3485.                 for (i = 0, len = properties.length; i < len; i++) {
  3486.                     var property = properties[i];
  3487.                     var suffixCounter = 0;
  3488.                     var suffix = "";
  3489.                     while (property["FC_TargetPath" + suffix]) {
  3490.                         source = property.name;
  3491.                         if (property["FC_SourcePath" + suffix]) {
  3492.                             source += "/" + property["FC_SourcePath" + suffix];
  3493.                         }
  3494.  
  3495.                         applyEntryCustomizationToEntry(property, source, entry, data, property.type, suffix, context);
  3496.                         suffixCounter++;
  3497.                         suffix = "_" + suffixCounter;
  3498.                     }
  3499.                 }
  3500.             }
  3501.  
  3502.             // Apply feed customizations from base types.
  3503.             entityType = lookupEntityType(entityType.baseType, metadata);
  3504.         }
  3505.     };
  3506.  
  3507.     var writeAtomEntryMetadata = function (entry, metadata) {
  3508.         /// <summary>Writes the content of metadata into the specified DOM entry element.</summary>
  3509.         /// <param name="entry">DOM entry element.</param>
  3510.         /// <param name="metadata" mayBeNull="true">Object __metadata to write.</param>
  3511.  
  3512.         if (metadata) {
  3513.             // Write the etag if present.
  3514.             if (metadata.etag) {
  3515.                 xmlNewAttribute(entry, "etag", odataMetaXmlNs, metadata.etag);
  3516.             }
  3517.  
  3518.             // Write the ID if present.
  3519.             if (metadata.uri) {
  3520.                 xmlNewElement(entry, "id", atomXmlNs, metadata.uri);
  3521.             }
  3522.  
  3523.             // Write the type name if present.
  3524.             if (metadata.type) {
  3525.                 var category = xmlNewElement(entry, "category", atomXmlNs);
  3526.                 xmlNewAttribute(category, "term", null, metadata.type);
  3527.                 xmlNewAttribute(category, "scheme", null, odataScheme);
  3528.             }
  3529.         }
  3530.     };
  3531.  
  3532.     var writeAtomEntryLink = function (entry, href, rel) {
  3533.         /// <summary>Writes an ATOM link into an entry.</summary>
  3534.         /// <param name="entry">DOM entry element to add link to.</param>
  3535.         /// <param name="href" type="String">Value for href attribute in link element.</param>
  3536.         /// <param name="rel" type="String">Value for rel attribute in link element</param>
  3537.         /// <returns>The new link element.</returns>
  3538.  
  3539.         var link = xmlNewElement(entry, "link", atomXmlNs);
  3540.         xmlNewAttribute(link, "rel", null, rel);
  3541.         xmlNewAttribute(link, "href", null, href);
  3542.         return link;
  3543.     };
  3544.  
  3545.     var writeAtomEntryProperties = function (entry, parentElement, data, propertiesMetadata, context) {
  3546.         /// <summary>Writes the properties of an entry or complex type.</summary>
  3547.         /// <param name="entry" mayBeNull="true">Entry object being written out; null if this is a complex type.</param>
  3548.         /// <param name="parentElement">Parent DOM element under which the property should be added.</param>
  3549.         /// <param name="data">Data object to write in intermediate format.</param>
  3550.         /// <param name="propertiesMetadata" mayBeNull="true">Instance metadata about properties of the 'data' object.</param>
  3551.         /// <param name="context">Context used for serialization.</param>
  3552.  
  3553.         var name, value, kind, propertyMetadata;
  3554.         var contextMetadata = context.metadata;
  3555.         var dataMetadataType;
  3556.         for (name in data) {
  3557.             if (name !== "__metadata") {
  3558.                 value = data[name];
  3559.                 kind = propertyKindOf(value);
  3560.                 propertyMetadata = (propertiesMetadata) ? propertiesMetadata[name] : null;
  3561.                 if (!propertyMetadata) {
  3562.                     // Look up at-most-once.
  3563.                     if (dataMetadataType === undefined) {
  3564.                         if (data.__metadata) {
  3565.                             dataMetadataType = lookupEntityType(data.__metadata.type, contextMetadata);
  3566.                         }
  3567.                     }
  3568.  
  3569.                     if (dataMetadataType) {
  3570.                         propertyMetadata = lookupProperty(dataMetadataType.property, name);
  3571.                         if (!propertyMetadata) {
  3572.                             propertyMetadata = lookupProperty(dataMetadataType.navigationProperty, name);
  3573.                         }
  3574.                     }
  3575.                 }
  3576.  
  3577.                 if (kind === PROPERTYKIND_NONE) {
  3578.                     // This could be a null primitive property, complex type or link;
  3579.                     // look it up in metadata if available, otherwise assume primitive.
  3580.                     kind = PROPERTYKIND_PRIMITIVE;
  3581.                     if (propertyMetadata && !isPrimitiveType(propertyMetadata.type)) {
  3582.                         if (propertyMetadata.relationship) {
  3583.                             kind = PROPERTYKIND_INLINE;
  3584.                         } else if (lookupComplexType(propertyMetadata.type, context.metadata)) {
  3585.                             kind = PROPERTYKIND_COMPLEX;
  3586.                         }
  3587.                     }
  3588.                 }
  3589.  
  3590.                 if (kind === PROPERTYKIND_PRIMITIVE || kind === PROPERTYKIND_COMPLEX) {
  3591.                     writeAtomEntryProperty(parentElement, name, kind, value, propertyMetadata, context);
  3592.                 } else {
  3593.                     writeAtomEntryDeferredProperty(entry, kind, name, value, context);
  3594.                 }
  3595.             }
  3596.         }
  3597.     };
  3598.  
  3599.     var writeAtomEntryProperty = function (parentElement, name, kind, value, propertiesMetadata, context) {
  3600.         /// <summary>Writes a single property for an entry or complex type.</summary>
  3601.         /// <param name="parentElement">Parent DOM element under which the property should be added.</param>
  3602.         /// <param name="name" type="String">Property name.</param>
  3603.         /// <param name="kind" type="String">Property kind description (from propertyKind values).</param>
  3604.         /// <param name="value" mayBeNull="true">Property value.</param>
  3605.         /// <param name="propertiesMetadata" mayBeNull="true">Instance metadata about properties of the 'data' object.</param>
  3606.         /// <param name="context">Serialization context.</param>
  3607.  
  3608.         var propertyTagName = xmlQualifyXmlTagName(name, "d");
  3609.         var propertyType = propertiesMetadata && propertiesMetadata.type;
  3610.         var property;
  3611.         if (kind === PROPERTYKIND_COMPLEX) {
  3612.             property = xmlNewElement(parentElement, propertyTagName, odataXmlNs);
  3613.             var propertyMetadata;
  3614.             if (propertiesMetadata) {
  3615.                 propertyMetadata = propertiesMetadata.properties;
  3616.             }
  3617.  
  3618.             // Null complex types require a V2 payload.
  3619.             if (value === null) {
  3620.                 context.dataServiceVersion = "2.0";
  3621.             }
  3622.  
  3623.             writeAtomEntryProperties(null, property, value, propertyMetadata, context);
  3624.         } else {
  3625.             // Default the conversion to string if no property type has been defined.
  3626.             property = xmlNewElement(parentElement, propertyTagName, odataXmlNs, convertToAtomPropertyText(value, propertyType || "Edm.String"));
  3627.         }
  3628.  
  3629.         if (value === null) {
  3630.             xmlNewAttribute(property, propertyNullAttribute, odataMetaXmlNs, "true");
  3631.         }
  3632.  
  3633.         if (propertyType) {
  3634.             xmlNewAttribute(property, propertyTypeAttribute, odataMetaXmlNs, propertyType);
  3635.         }
  3636.     };
  3637.  
  3638.     var writeAtomEntryDeferredProperty = function (entry, kind, name, value, context) {
  3639.         /// <summary>Writes a single property for an entry or complex type.</summary>
  3640.         /// <param name="entry">Entry object being written out.</param>
  3641.         /// <param name="name" type="String">Property name.</param>
  3642.         /// <param name="kind" type="String">Property kind description (from propertyKind values).</param>
  3643.         /// <param name="value" mayBeNull="true">Property value.</param>
  3644.         /// <param name="context">Serialization context.</param>
  3645.         /// <remarks>entry cannot be null because that would indicate a complex type, which don't support links.</remarks>
  3646.  
  3647.         var href;
  3648.         var inlineWriter;
  3649.         var inlineType;
  3650.         var inlineContent = kind === PROPERTYKIND_INLINE;
  3651.         if (inlineContent) {
  3652.             href = (value && value.__metadata) ? value.__metadata.uri : "";
  3653.             inlineType = payloadTypeOf(value);
  3654.             switch (inlineType) {
  3655.                 case PAYLOADTYPE_ENTRY:
  3656.                     inlineWriter = writeAtomEntry;
  3657.                     break;
  3658.                 case PAYLOADTYPE_FEEDORLINKS:
  3659.                     inlineType = "feed";
  3660.                     inlineWriter = writeAtomFeed;
  3661.                     break;
  3662.                 case PAYLOADTYPE_NONE:
  3663.                     // Only for a null entry, serialized as a link with an empty m:inline value.
  3664.                     inlineType = PAYLOADTYPE_ENTRY;
  3665.                     inlineWriter = null;
  3666.                     break;
  3667.                 default:
  3668.                     throw { message: "Invalid payload for inline navigation property: " + inlineType };
  3669.             }
  3670.         } else {
  3671.             href = value.__deferred.uri;
  3672.         }
  3673.  
  3674.         var rel = normalizeURI(name, odataRelatedPrefix);
  3675.         var link = writeAtomEntryLink(entry, href, rel);
  3676.         if (inlineContent) {
  3677.             var inlineRoot = xmlNewElement(link, inlineTag, odataMetaXmlNs);
  3678.             xmlNewAttribute(link, "type", null, "application/atom+xml;type=" + inlineType);
  3679.             if (inlineWriter) {
  3680.                 inlineWriter(inlineRoot, value, context);
  3681.             }
  3682.         }
  3683.     };
  3684.  
  3685.     var atomParser = function (handler, text, context) {
  3686.         /// <summary>Parses an ATOM document (feed, entry or service document).</summary>
  3687.         /// <param name="handler">This handler.</param>
  3688.         /// <param name="text" type="String">Document text.</param>
  3689.         /// <param name="context" type="Object">Object with parsing context.</param>
  3690.         /// <returns>An object representation of the document; undefined if not applicable.</returns>
  3691.  
  3692.         if (text) {
  3693.             var atomRoot = xmlParse(text);
  3694.             if (atomRoot) {
  3695.                 return readAtomDocument(atomRoot, context.metadata);
  3696.             }
  3697.         }
  3698.     };
  3699.  
  3700.     var atomSerializer = function (handler, data, context) {
  3701.         /// <summary>Serializes an ATOM object into a document (feed or entry).</summary>
  3702.         /// <param name="handler">This handler.</param>
  3703.         /// <param name="data" type="Object">Representation of feed or entry.</param>
  3704.         /// <param name="context" type="Object">Object with parsing context.</param>
  3705.         /// <returns>An text representation of the data object; undefined if not applicable.</returns>
  3706.  
  3707.         var cType = context.contentType = context.contentType || contentType(atomMediaType);
  3708.         var result = undefined;
  3709.         if (cType && cType.mediaType === atomMediaType) {
  3710.             var atomDoc = writeAtomDocument(data, context);
  3711.             result = xmlSerialize(atomDoc);
  3712.         }
  3713.  
  3714.         return result;
  3715.     };
  3716.  
  3717.     odata.atomHandler = handler(atomParser, atomSerializer, atomAcceptTypes.join(","), "2.0");
  3718.  
  3719.  
  3720.  
  3721.     // It's assumed that all elements may have Documentation children and Annotation elements.
  3722.     // See http://msdn.microsoft.com/en-us/library/bb399292.aspx for a CSDL reference.
  3723.     var schema = {
  3724.         elements: {
  3725.             Association: {
  3726.                 attributes: ["Name"],
  3727.                 elements: ["End*", "ReferentialConstraint"]
  3728.             },
  3729.             AssociationSet: {
  3730.                 attributes: ["Name", "Association"],
  3731.                 elements: ["End*"]
  3732.             },
  3733.             CollectionType: {
  3734.                 attributes: ["ElementType", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation"]
  3735.             },
  3736.             ComplexType: {
  3737.                 attributes: ["Name", "BaseType", "Abstract"],
  3738.                 elements: ["Property*"]
  3739.             },
  3740.             DefiningExpression: {
  3741.                 text: true
  3742.             },
  3743.             Dependent: {
  3744.                 attributes: ["Role"],
  3745.                 elements: ["PropertyRef*"]
  3746.             },
  3747.             Documentation: {
  3748.                 text: true
  3749.             },
  3750.             End: {
  3751.                 attributes: ["Type", "Role", "Multiplicity", "EntitySet"],
  3752.                 elements: ["OnDelete"]
  3753.             },
  3754.             EntityContainer: {
  3755.                 attributes: ["Name", "Extends"],
  3756.                 elements: ["EntitySet*", "AssociationSet*", "FunctionImport*"]
  3757.             },
  3758.             EntitySet: {
  3759.                 attributes: ["Name", "EntityType"]
  3760.             },
  3761.             EntityType: {
  3762.                 attributes: ["Name", "BaseType", "Abstract", "OpenType"],
  3763.                 elements: ["Key", "Property*", "NavigationProperty*"]
  3764.             },
  3765.             Function: {
  3766.                 attributes: ["Name", "ReturnType"],
  3767.                 elements: ["Parameter*", "DefiningExpression", "ReturnType"]
  3768.             },
  3769.             FunctionImport: {
  3770.                 attributes: ["Name", "ReturnType", "EntitySet"],
  3771.                 elements: ["Parameter*"]
  3772.             },
  3773.             Key: {
  3774.                 elements: ["PropertyRef*"]
  3775.             },
  3776.             NavigationProperty: {
  3777.                 attributes: ["Name", "Relationship", "ToRole", "FromRole"]
  3778.             },
  3779.             OnDelete: {
  3780.                 attributes: ["Action"]
  3781.             },
  3782.             Parameter: {
  3783.                 attributes: ["Name", "Type", "Mode", "MaxLength", "Precision", "Scale"]
  3784.             },
  3785.             Principal: {
  3786.                 attributes: ["Role"],
  3787.                 elements: ["PropertyRef*"]
  3788.             },
  3789.             Property: {
  3790.                 attributes: ["Name", "Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "ConcurrencyMode"]
  3791.             },
  3792.             PropertyRef: {
  3793.                 attributes: ["Name"]
  3794.             },
  3795.             ReferenceType: {
  3796.                 attributes: ["Type"]
  3797.             },
  3798.             ReferentialConstraint: {
  3799.                 elements: ["Principal", "Dependent"]
  3800.             },
  3801.             ReturnType: {
  3802.                 attributes: ["ReturnType"],
  3803.                 elements: ["CollectionType", "ReferenceType", "RowType"]
  3804.             },
  3805.             RowType: {
  3806.                 elements: ["Property*"]
  3807.             },
  3808.             Schema: {
  3809.                 attributes: ["Namespace", "Alias"],
  3810.                 elements: ["Using*", "EntityContainer*", "EntityType*", "Association*", "ComplexType*", "Function*"]
  3811.             },
  3812.             TypeRef: {
  3813.                 attributes: ["Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation"]
  3814.             },
  3815.             Using: {
  3816.                 attributes: ["Namespace", "Alias"]
  3817.             }
  3818.         }
  3819.     };
  3820.  
  3821.     // See http://msdn.microsoft.com/en-us/library/ee373839.aspx for a feed customization reference.
  3822.     var customizationAttributes = ["m:FC_ContentKind", "m:FC_KeepInContent", "m:FC_NsPrefix", "m:FC_NsUri", "m:FC_SourcePath", "m:FC_TargetPath"];
  3823.     schema.elements.Property.attributes = schema.elements.Property.attributes.concat(customizationAttributes);
  3824.     schema.elements.EntityType.attributes = schema.elements.EntityType.attributes.concat(customizationAttributes);
  3825.  
  3826.     // See http://msdn.microsoft.com/en-us/library/dd541284(PROT.10).aspx for an EDMX reference.
  3827.     schema.elements.Edmx = { attributes: ["Version"], elements: ["DataServices"], ns: edmxNs };
  3828.     schema.elements.DataServices = { elements: ["Schema*"], ns: edmxNs };
  3829.  
  3830.     // See http://msdn.microsoft.com/en-us/library/dd541233(v=PROT.10) for Conceptual Schema Definition Language Document for Data Services.
  3831.     schema.elements.EntityContainer.attributes.push("m:IsDefaultEntityContainer");
  3832.     schema.elements.Property.attributes.push("m:MimeType");
  3833.     schema.elements.FunctionImport.attributes.push("m:HttpMethod");
  3834.     schema.elements.EntityType.attributes.push("m:HasStream");
  3835.     schema.elements.DataServices.attributes = ["m:DataServiceVersion"];
  3836.  
  3837.     var scriptCase = function (text) {
  3838.         /// <summary>Converts a Pascal-case identifier into a camel-case identifier.</summary>
  3839.         /// <param name="text" type="String">Text to convert.</param>
  3840.         /// <returns type="String">Converted text.</returns>
  3841.         /// <remarks>If the text starts with multiple uppercase characters, it is left as-is.</remarks>
  3842.  
  3843.         if (!text) {
  3844.             return text;
  3845.         }
  3846.  
  3847.         if (text.length > 1) {
  3848.             var firstTwo = text.substr(0, 2);
  3849.             if (firstTwo === firstTwo.toUpperCase()) {
  3850.                 return text;
  3851.             }
  3852.  
  3853.             return text.charAt(0).toLowerCase() + text.substr(1);
  3854.         }
  3855.  
  3856.         return text.charAt(0).toLowerCase();
  3857.     };
  3858.  
  3859.     var getChildSchema = function (parentSchema, candidateName) {
  3860.         /// <summary>Gets the schema node for the specified element.</summary>
  3861.         /// <param name="parentSchema" type="Object">Schema of the parent XML node of 'element'.</param>
  3862.         /// <param name="candidateName">XML element name to consider.</param>
  3863.         /// <returns type="Object">The schema that describes the specified element; null if not found.</returns>
  3864.  
  3865.         if (candidateName === "Documentation") {
  3866.             return { isArray: true, propertyName: "documentation" };
  3867.         }
  3868.  
  3869.         var elements = parentSchema.elements;
  3870.         if (!elements) {
  3871.             return null;
  3872.         }
  3873.  
  3874.         var i, len;
  3875.         for (i = 0, len = elements.length; i < len; i++) {
  3876.             var elementName = elements[i];
  3877.             var multipleElements = false;
  3878.             if (elementName.charAt(elementName.length - 1) === "*") {
  3879.                 multipleElements = true;
  3880.                 elementName = elementName.substr(0, elementName.length - 1);
  3881.             }
  3882.  
  3883.             if (candidateName === elementName) {
  3884.                 var propertyName = scriptCase(elementName);
  3885.                 return { isArray: multipleElements, propertyName: propertyName };
  3886.             }
  3887.         }
  3888.  
  3889.         return null;
  3890.     };
  3891.  
  3892.     // This regular expression is used to detect a feed customization element
  3893.     // after we've normalized it into the 'm' prefix. It starts with m:FC_,
  3894.     // followed by other characters, and ends with _ and a number.
  3895.     // The captures are 0 - whole string, 1 - name as it appears in internal table.
  3896.     var isFeedCustomizationNameRE = /^(m:FC_.*)_[0-9]+$/;
  3897.  
  3898.     var isEdmNamespace = function (nsURI) {
  3899.         /// <summary>Checks whether the specifies namespace URI is one of the known CSDL namespace URIs.</summary>
  3900.         /// <param name="nsURI" type="String">Namespace URI to check.</param>
  3901.         /// <returns type="Boolean">true if nsURI is a known CSDL namespace; false otherwise.</returns>
  3902.  
  3903.         return nsURI === edmNs || nsURI === edmNs2 || nsURI === edmNs3;
  3904.     };
  3905.  
  3906.     var parseConceptualModelElement = function (element) {
  3907.         /// <summary>Parses a CSDL document.</summary>
  3908.         /// <param name="element">DOM element to parse.</param>
  3909.         /// <returns type="Object">An object describing the parsed element.</returns>
  3910.  
  3911.         if (!element.domNode) {
  3912.             element = xml_wrapNode(element, "");
  3913.         }
  3914.  
  3915.         var localName = element.localName;
  3916.         var elementSchema = schema.elements[localName];
  3917.         if (!elementSchema) {
  3918.             return null;
  3919.         }
  3920.  
  3921.         if (elementSchema.ns) {
  3922.             if (element.nsURI !== elementSchema.ns) {
  3923.                 return null;
  3924.             }
  3925.         } else if (!isEdmNamespace(element.nsURI)) {
  3926.             return null;
  3927.         }
  3928.  
  3929.         var item = {};
  3930.         var extensions = [];
  3931.         var attributes = elementSchema.attributes || [];
  3932.         xmlAttributes(element, function (wrappedAttribute) {
  3933.             var node = wrappedAttribute.domNode;
  3934.             var localName = wrappedAttribute.localName;
  3935.             var namespaceURI = wrappedAttribute.nsURI;
  3936.             var value = node.value;
  3937.  
  3938.             // Don't do anything with xmlns attributes.
  3939.             if (namespaceURI === xmlnsNS) {
  3940.                 return;
  3941.             }
  3942.  
  3943.             // Currently, only m: for metadata is supported as a prefix in the internal schema table,
  3944.             // un-prefixed element names imply one a CSDL element.
  3945.             var schemaName = null;
  3946.             var handled = false;
  3947.             if (isEdmNamespace(namespaceURI) || namespaceURI === null) {
  3948.                 schemaName = "";
  3949.             } else if (namespaceURI === odataMetaXmlNs) {
  3950.                 schemaName = "m:";
  3951.             }
  3952.  
  3953.             if (schemaName !== null) {
  3954.                 schemaName += localName;
  3955.  
  3956.                 // Feed customizations for complex types have additional
  3957.                 // attributes with a suffixed counter starting at '1', so
  3958.                 // take that into account when doing the lookup.
  3959.                 var match = isFeedCustomizationNameRE.exec(schemaName);
  3960.                 if (match) {
  3961.                     schemaName = match[1];
  3962.                 }
  3963.  
  3964.                 if (contains(attributes, schemaName)) {
  3965.                     handled = true;
  3966.                     item[scriptCase(localName)] = value;
  3967.                 }
  3968.             }
  3969.  
  3970.             if (!handled) {
  3971.                 extensions.push(createAttributeExtension(wrappedAttribute));
  3972.             }
  3973.         });
  3974.  
  3975.         xmlChildElements(element, function (child) {
  3976.             var childSchema = getChildSchema(elementSchema, child.localName);
  3977.             if (childSchema) {
  3978.                 if (childSchema.isArray) {
  3979.                     var arr = item[childSchema.propertyName];
  3980.                     if (!arr) {
  3981.                         arr = [];
  3982.                         item[childSchema.propertyName] = arr;
  3983.                     }
  3984.                     arr.push(parseConceptualModelElement(child));
  3985.                 } else {
  3986.                     item[childSchema.propertyName] = parseConceptualModelElement(child);
  3987.                 }
  3988.             } else {
  3989.                 extensions.push(createElementExtension(child));
  3990.             }
  3991.         });
  3992.  
  3993.         if (elementSchema.text) {
  3994.             item.text = xmlInnerText(element);
  3995.         }
  3996.  
  3997.         if (extensions.length) {
  3998.             item.extensions = extensions;
  3999.         }
  4000.  
  4001.         return item;
  4002.     };
  4003.  
  4004.     var metadataParser = function (handler, text) {
  4005.         /// <summary>Parses a metadata document.</summary>
  4006.         /// <param name="handler">This handler.</param>
  4007.         /// <param name="text" type="String">Metadata text.</param>
  4008.         /// <returns>An object representation of the conceptual model.</returns>
  4009.  
  4010.         var doc = xmlParse(text);
  4011.         return parseConceptualModelElement(doc) || undefined;
  4012.     };
  4013.  
  4014.     odata.metadataHandler = handler(metadataParser, null, xmlMediaType, "1.0");
  4015.  
  4016.  
  4017.  
  4018.     var jsonMediaType = "application/json";
  4019.  
  4020.     var normalizeServiceDocument = function (data, baseURI) {
  4021.         /// <summary>Normalizes a JSON service document to look like an ATOM service document.</summary>
  4022.         /// <param name="data" type="Object">Object representation of service documents as deserialized.</param>
  4023.         /// <param name="baseURI" type="String">Base URI to resolve relative URIs.</param>
  4024.         /// <returns type="Object">An object representation of the service document.</returns>
  4025.         var workspace = { collections: [] };
  4026.  
  4027.         var i, len;
  4028.         for (i = 0, len = data.EntitySets.length; i < len; i++) {
  4029.             var title = data.EntitySets[i];
  4030.             var collection = {
  4031.                 title: title,
  4032.                 href: normalizeURI(title, baseURI)
  4033.             };
  4034.  
  4035.             workspace.collections.push(collection);
  4036.         }
  4037.  
  4038.         return { workspaces: [workspace] };
  4039.     };
  4040.  
  4041.     // The regular expression corresponds to something like this:
  4042.     // /Date(123+60)/
  4043.     //
  4044.     // This first number is date ticks, the + may be a - and is optional,
  4045.     // with the second number indicating a timezone offset in minutes.
  4046.     //
  4047.     // On the wire, the leading and trailing forward slashes are
  4048.     // escaped without being required to so the chance of collisions is reduced;
  4049.     // however, by the time we see the objects, the characters already
  4050.     // look like regular forward slashes.
  4051.     var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
  4052.  
  4053.     var minutesToOffset = function (minutes) {
  4054.         /// <summary>Formats the given minutes into (+/-)hh:mm format.</summary>
  4055.         /// <param name="minutes" type="Number">Number of minutes to format.</param>
  4056.         /// <returns type="String">The minutes in (+/-)hh:mm format.</returns>
  4057.  
  4058.         var sign;
  4059.         if (minutes < 0) {
  4060.             sign = "-";
  4061.             minutes = -minutes;
  4062.         } else {
  4063.             sign = "+";
  4064.         }
  4065.  
  4066.         var hours = Math.floor(minutes / 60);
  4067.         minutes = minutes - (60 * hours);
  4068.  
  4069.         return sign + formatNumberWidth(hours, 2) + ":" + formatNumberWidth(minutes, 2);
  4070.     };
  4071.  
  4072.     var parseJsonDateString = function (value) {
  4073.         /// <summary>Parses the JSON Date representation into a Date object.</summary>
  4074.         /// <param name="value" type="String">String value.</param>
  4075.         /// <returns type="Date">A Date object if the value matches one; falsy otherwise.</returns>
  4076.  
  4077.         var arr = value && jsonDateRE.exec(value);
  4078.         if (arr) {
  4079.             // 0 - complete results; 1 - ticks; 2 - sign; 3 - minutes
  4080.             var result = new Date(parseInt10(arr[1]));
  4081.             if (arr[2]) {
  4082.                 var mins = parseInt10(arr[3]);
  4083.                 if (arr[2] === "-") {
  4084.                     mins = -mins;
  4085.                 }
  4086.  
  4087.                 // The offset is reversed to get back the UTC date, which is
  4088.                 // what the API will eventually have.
  4089.                 var current = result.getUTCMinutes();
  4090.                 result.setUTCMinutes(current - mins);
  4091.                 result.__edmType = "Edm.DateTimeOffset";
  4092.                 result.__offset = minutesToOffset(mins);
  4093.             }
  4094.             if (!isNaN(result.valueOf())) {
  4095.                 return result;
  4096.             }
  4097.         }
  4098.  
  4099.         // Allow undefined to be returned.
  4100.     };
  4101.  
  4102.      // Some JSON implementations don't allow for emmiting the character sequence \/
  4103.      // which is needed to format DateTime and DateTimeOffset into the
  4104.      // JSON string representation defined by the OData protocol.
  4105.    
  4106.      var formatJsonDateString = function(value) {
  4107.         /// <summary>Formats a Date object into its OData JSON representation.</summary>
  4108.         /// <param name="value" type="Date">Date value.</param>
  4109.         /// <returns type="String">JSON Date string.</returns>
  4110.         var hasOffset = (value.__edmType === "Edm.DateTimeOffset" || (!value.__edmType && value.__offset));
  4111.         var offset = getCanonicalTimezone(value.__offset);
  4112.         if (hasOffset && offset !== "Z") {
  4113.             // Work with a copy of the value;
  4114.             value = new Date(value.valueOf());
  4115.             var timezone = parseTimezone(offset);
  4116.             var hours = value.getUTCHours() + (timezone.d * timezone.h);
  4117.             var minutes = value.getMinutes() + (timezone.d * timezone.m);
  4118.             value.setUTCHours(hours, minutes);
  4119.    
  4120.             var offsetString = (timezone.d === 1 ? "+" : "-")
  4121.                     + (timezone.h * 60 + timezone.m);
  4122.         }
  4123.    
  4124.         return "/Date(" + value.valueOf() + (offsetString || "") + ")/";
  4125.      };
  4126.  
  4127.     var traverseInternal = function (item, callback) {
  4128.         /// <summary>Traverses a tree of objects invoking callback for every value.</summary>
  4129.         /// <param name="item" type="Object">Object or array to traverse.</param>
  4130.         /// <param name="callback" type="Function">
  4131.         /// Callback function with key and value, similar to JSON.parse reviver.
  4132.         /// </param>
  4133.         /// <returns type="Object">The object with traversed properties.</returns>
  4134.         /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks>
  4135.  
  4136.         if (item && typeof item === "object") {
  4137.             for (var name in item) {
  4138.                 var value = item[name];
  4139.                 var result = traverseInternal(value, callback);
  4140.                 result = callback(name, result);
  4141.                 if (result !== value) {
  4142.                     if (value === undefined) {
  4143.                         delete item[name];
  4144.                     } else {
  4145.                         item[name] = result;
  4146.                     }
  4147.                 }
  4148.             }
  4149.         }
  4150.  
  4151.         return item;
  4152.     };
  4153.  
  4154.     var traverse = function (item, callback) {
  4155.         /// <summary>Traverses a tree of objects invoking callback for every value.</summary>
  4156.         /// <param name="item" type="Object">Object or array to traverse.</param>
  4157.         /// <param name="callback" type="Function">
  4158.         /// Callback function with key and value, similar to JSON.parse reviver.
  4159.         /// </param>
  4160.         /// <returns type="Object">The traversed object.</returns>
  4161.         /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks>
  4162.  
  4163.         return callback("", traverseInternal(item, callback));
  4164.     };
  4165.  
  4166.     var jsonParser = function (handler, text, context) {
  4167.         /// <summary>Parses a JSON OData payload.</summary>
  4168.         /// <param name="handler">This handler.</param>
  4169.         /// <param name="text">Payload text (this parser also handles pre-parsed objects).</param>
  4170.         /// <param name="context" type="Object">Object with parsing context.</param>
  4171.         /// <returns>An object representation of the OData payload.</returns>
  4172.  
  4173.         var metadata = context.metadata;
  4174.         var recognizeDates = defined(context.context ? context.context.recognizeDates : undefined, handler.recognizeDates);
  4175.  
  4176.         var json = (typeof text === "string") ? window.JSON.parse(text) : text;
  4177.         json = traverse(json, function (_, value) {
  4178.             if (value && typeof value === "object") {
  4179.                 var dataTypeName = value.__metadata && value.__metadata.type;
  4180.                 var dataType = lookupEntityType(dataTypeName, metadata) || lookupComplexType(dataTypeName, metadata);
  4181.  
  4182.                 if (dataType) {
  4183.                     var properties = dataType.property;
  4184.                     if (properties) {
  4185.                         var i, len;
  4186.                         for (i = 0, len = properties.length; i < len; i++) {
  4187.                             var property = properties[i];
  4188.                             var propertyName = property.name;
  4189.                             var propertyValue = value[propertyName];
  4190.  
  4191.                             if (property.type === "Edm.DateTime" || property.type === "Edm.DateTimeOffset") {
  4192.                                 if (propertyValue) {
  4193.                                     propertyValue = parseJsonDateString(propertyValue);
  4194.                                     if (!propertyValue) {
  4195.                                         throw { message: "Invalid date/time value" };
  4196.                                     }
  4197.                                     value[propertyName] = propertyValue;
  4198.                                 }
  4199.                             } else if (property.type === "Edm.Time") {
  4200.                                 value[propertyName] = parseDuration(propertyValue);
  4201.                             }
  4202.                         }
  4203.                     }
  4204.                 } else if (recognizeDates) {
  4205.                     for (var name in value) {
  4206.                         propertyValue = value[name];
  4207.                         if (typeof propertyValue === "string") {
  4208.                             value[name] = parseJsonDateString(propertyValue) || propertyValue;
  4209.                         }
  4210.                     }
  4211.                 }
  4212.             }
  4213.             return value;
  4214.         }).d;
  4215.  
  4216.         json = jsonUpdateDataFromVersion(json, context.dataServiceVersion);
  4217.         json = jsonNormalizeData(json, context.response.requestUri);
  4218.  
  4219.         return json;
  4220.     };
  4221.  
  4222.     var jsonSerializer = function (handler, data, context) {
  4223.         /// <summary>Serializes the data by returning its string representation.</summary>
  4224.         /// <param name="handler">This handler.</param>
  4225.         /// <param name="data">Data to serialize.</param>
  4226.         /// <param name="context" type="Object">Object with serialization context.</param>
  4227.         /// <returns type="String">The string representation of data.</returns>
  4228.  
  4229.         var result = undefined;
  4230.         var cType = context.contentType = context.contentType || contentType(jsonMediaType);
  4231.         if (cType && cType.mediaType === jsonMediaType) {
  4232.             var json = data;
  4233.  
  4234.             // Save the current date.toJSON function
  4235.             var dateToJSON = Date.prototype.toJSON;
  4236.             try {
  4237.                 // Set our own date.toJSON function
  4238.                 Date.prototype.toJSON = function () {
  4239.                     return formatJsonDateString(this);
  4240.                 };
  4241.                 result = window.JSON.stringify(json, jsonReplacer);
  4242.             } finally {
  4243.                 // Restore the original toJSON function
  4244.                 Date.prototype.toJSON = dateToJSON;
  4245.             }
  4246.         }
  4247.         return result;
  4248.     };
  4249.  
  4250.     var jsonReplacer = function (_, value) {
  4251.         /// <summary>JSON replacer function for converting a value to its JSON representation.</summary>
  4252.         /// <param value type="Object">Value to convert.</param>
  4253.         /// <returns type="String">JSON representation of the input value.</returns>
  4254.         /// <remarks>
  4255.         ///   This method is used during JSON serialization and invoked only by the JSON.stringify function.
  4256.         ///   It should never be called directly.
  4257.         /// </remarks>
  4258.  
  4259.         if (value && value.__edmType === "Edm.Time") {
  4260.             return formatDuration(value);
  4261.         } else {
  4262.             return value;
  4263.         }
  4264.     };
  4265.  
  4266.     var jsonNormalizeData = function (data, baseURI) {
  4267.         /// <summary>
  4268.         /// Normalizes the specified data into an intermediate representation.
  4269.         /// like the latest supported version.
  4270.         /// </summary>
  4271.         /// <param name="data" optional="false">Data to update.</param>
  4272.         /// <param name="baseURI" optional="false">URI to use as the base for normalizing references.</param>
  4273.  
  4274.         if (payloadTypeOf(data) === PAYLOADTYPE_SVCDOC) {
  4275.             return normalizeServiceDocument(data, baseURI);
  4276.         } else {
  4277.             return data;
  4278.         }
  4279.     };
  4280.  
  4281.     var jsonUpdateDataFromVersion = function (data, dataVersion) {
  4282.         /// <summary>
  4283.         /// Updates the specified data in the specified version to look
  4284.         /// like the latest supported version.
  4285.         /// </summary>
  4286.         /// <param name="data" optional="false">Data to update.</param>
  4287.         /// <param name="dataVersion" optional="true" type="String">Version the data is in (possibly unknown).</param>
  4288.  
  4289.         // Strip the trailing comma if there.
  4290.         if (dataVersion && dataVersion.lastIndexOf(";") === dataVersion.length - 1) {
  4291.             dataVersion = dataVersion.substr(0, dataVersion.length - 1);
  4292.         }
  4293.  
  4294.         if (!dataVersion) {
  4295.             // Try to detect whether this is an array, in which case it
  4296.             // should probably be a feed structure - indicates V1 behavior.
  4297.             if (isArray(data)) {
  4298.                 dataVersion = "1.0";
  4299.             }
  4300.         }
  4301.  
  4302.         // If the data is in the latest version, there is nothing to update.
  4303.         if (dataVersion === "2.0") {
  4304.             return data;
  4305.         }
  4306.  
  4307.         if (dataVersion === "1.0") {
  4308.             if (isArray(data)) {
  4309.                 data = { results: data };
  4310.             }
  4311.         }
  4312.  
  4313.         return data;
  4314.     };
  4315.  
  4316.     odata.jsonHandler = handler(jsonParser, jsonSerializer, jsonMediaType, "2.0");
  4317.     odata.jsonHandler.recognizeDates = false;
  4318.  
  4319.  
  4320.     var batchMediaType = "multipart/mixed";
  4321.     var responseStatusRegex = /^HTTP\/1\.\d (\d{3}) (.*)$/i;
  4322.     var responseHeaderRegex = /^([^()<>@,;:\\"\/[\]?={} \t]+)\s?:\s?(.*)/;
  4323.  
  4324.     var hex16 = function () {
  4325.         /// <summary>
  4326.         /// Calculates a random 16 bit number and returns it in hexadecimal format.
  4327.         /// </summary>
  4328.         /// <returns type="String">A 16-bit number in hex format.</returns>
  4329.  
  4330.         return Math.floor((1 + Math.random()) * 0x10000).toString(16).substr(1);
  4331.     };
  4332.  
  4333.     var createBoundary = function (prefix) {
  4334.         /// <summary>
  4335.         /// Creates a string that can be used as a multipart request boundary.
  4336.         /// </summary>
  4337.         /// <param name="prefix" type="String" optional="true">String to use as the start of the boundary string</param>
  4338.         /// <returns type="String">Boundary string of the format: <prefix><hex16>-<hex16>-<hex16></returns>
  4339.  
  4340.         return prefix + hex16() + "-" + hex16() + "-" + hex16();
  4341.     };
  4342.  
  4343.     var partHandler = function (context) {
  4344.         /// <summary>
  4345.         /// Gets the handler for data serialization of individual requests / responses in a batch.
  4346.         /// </summary>
  4347.         /// <param name="context">Context used for data serialization.</param>
  4348.         /// <returns>Handler object.</returns>
  4349.  
  4350.         return context.handler.partHandler;
  4351.     };
  4352.  
  4353.     var currentBoundary = function (context) {
  4354.         /// <summary>
  4355.         /// Gets the current boundary used for parsing the body of a multipart response.
  4356.         /// </summary>
  4357.         /// <param name="context">Context used for parsing a multipart response.</param>
  4358.         /// <returns type="String">Boundary string.</returns>
  4359.  
  4360.         var boundaries = context.boundaries;
  4361.         return boundaries[boundaries.length - 1];
  4362.     };
  4363.  
  4364.     var batchParser = function (handler, text, context) {
  4365.         /// <summary>Parses a batch response.</summary>
  4366.         /// <param name="handler">This handler.</param>
  4367.         /// <param name="text" type="String">Batch text.</param>
  4368.         /// <param name="context" type="Object">Object with parsing context.</param>
  4369.         /// <returns>An object representation of the batch.</returns>
  4370.  
  4371.         var boundary = context.contentType.properties["boundary"];
  4372.         return { __batchResponses: readBatch(text, { boundaries: [boundary], handlerContext: context }) };
  4373.     };
  4374.  
  4375.     var batchSerializer = function (handler, data, context) {
  4376.         /// <summary>Serializes a batch object representation into text.</summary>
  4377.         /// <param name="handler">This handler.</param>
  4378.         /// <param name="data" type="Object">Representation of a batch.</param>
  4379.         /// <param name="context" type="Object">Object with parsing context.</param>
  4380.         /// <returns>An text representation of the batch object; undefined if not applicable.</returns>
  4381.  
  4382.         var cType = context.contentType = context.contentType || contentType(batchMediaType);
  4383.         if (cType.mediaType === batchMediaType) {
  4384.             return writeBatch(data, context);
  4385.         }
  4386.     };
  4387.  
  4388.     var readBatch = function (text, context) {
  4389.         /// <summary>
  4390.         /// Parses a multipart/mixed response body from from the position defined by the context.
  4391.         /// </summary>
  4392.         /// <param name="text" type="String" optional="false">Body of the multipart/mixed response.</param>
  4393.         /// <param name="context">Context used for parsing.</param>
  4394.         /// <returns>Array of objects representing the individual responses.</returns>
  4395.  
  4396.         var delimiter = "--" + currentBoundary(context);
  4397.  
  4398.         // Move beyond the delimiter and read the complete batch
  4399.         readTo(text, context, delimiter);
  4400.  
  4401.         // Ignore the incoming line
  4402.         readLine(text, context);
  4403.  
  4404.         // Read the batch parts
  4405.         var responses = [];
  4406.         var partEnd;
  4407.  
  4408.         while (partEnd !== "--" && context.position < text.length) {
  4409.             var partHeaders = readHeaders(text, context);
  4410.             var partContentType = contentType(partHeaders["Content-Type"]);
  4411.  
  4412.             if (partContentType && partContentType.mediaType === batchMediaType) {
  4413.                 context.boundaries.push(partContentType.properties["boundary"]);
  4414.                 try {
  4415.                     var changeResponses = readBatch(text, context);
  4416.                 } catch (e) {
  4417.                     e.response = readResponse(text, context, delimiter);
  4418.                     changeResponses = [e];
  4419.                 }
  4420.                 responses.push({__changeResponses: changeResponses});
  4421.                 context.boundaries.pop();
  4422.                 readTo(text, context, "--" + currentBoundary(context));
  4423.             } else {
  4424.                 if (!partContentType || partContentType.mediaType !== "application/http") {
  4425.                     throw { message: "invalid MIME part type " };
  4426.                 }
  4427.                 // Skip empty line
  4428.                 readLine(text, context);
  4429.                 // Read the response
  4430.                 var response = readResponse(text, context, delimiter);
  4431.                 try {
  4432.                     if (response.statusCode >= 200 && response.statusCode <= 299) {
  4433.                         partHandler(context.handlerContext).read(response, context.handlerContext);
  4434.                     } else {
  4435.                         // Keep track of failed responses and continue processing the batch.
  4436.                         response = { message: "HTTP request failed", response: response };
  4437.                     }
  4438.                 } catch (e) {
  4439.                     response = e;
  4440.                 }
  4441.  
  4442.                 responses.push(response);
  4443.             }
  4444.  
  4445.             partEnd = text.substr(context.position, 2);
  4446.  
  4447.             // Ignore the incoming line.
  4448.             readLine(text, context);
  4449.         }
  4450.         return responses;
  4451.     };
  4452.  
  4453.     var readHeaders = function (text, context) {
  4454.         /// <summary>
  4455.         /// Parses the http headers in the text from the position defined by the context.  
  4456.         /// </summary>
  4457.         /// <param name="text" type="String" optional="false">Text containing an http response's headers</param>
  4458.         /// <param name="context">Context used for parsing.</param>
  4459.         /// <returns>Object containing the headers as key value pairs.</returns>
  4460.         /// <remarks>
  4461.         /// This function doesn't support split headers and it will stop reading when it hits two consecutive line breaks.
  4462.         /// </remarks>
  4463.  
  4464.         var headers = {};
  4465.         var parts;
  4466.         var line;
  4467.         var pos;
  4468.        
  4469.         do {
  4470.             pos = context.position;
  4471.             line = readLine(text, context);
  4472.             parts = responseHeaderRegex.exec(line);
  4473.             if (parts !== null) {
  4474.                 headers[parts[1]] = parts[2];
  4475.             } else {
  4476.               // Whatever was found is not a header, so reset the context position.
  4477.               context.position = pos;
  4478.             }
  4479.         } while(line && parts);
  4480.  
  4481.         normalizeHeaders(headers);
  4482.  
  4483.         return headers;
  4484.     };
  4485.  
  4486.     var readResponse = function (text, context, delimiter) {
  4487.         /// <summary>
  4488.         /// Parses an HTTP response.
  4489.         /// </summary>
  4490.         /// <param name="text" type="String" optional="false">Text representing the http response.</param>
  4491.         /// <param name="context" optional="false">Context used for parsing.</param>
  4492.         /// <param name="delimiter" type="String" optional="false">String used as delimiter of the multipart response parts.</param>
  4493.         /// <returns>Object representing the http response.</returns>
  4494.  
  4495.         // Read the status line.
  4496.         var pos = context.position;
  4497.         var match = responseStatusRegex.exec(readLine(text, context));
  4498.  
  4499.         var statusCode;
  4500.         var statusText;
  4501.         var headers;
  4502.  
  4503.         if (match) {
  4504.             statusCode = match[1];
  4505.             statusText = match[2];
  4506.             headers = readHeaders(text, context);
  4507.             readLine(text, context);
  4508.         } else {
  4509.             context.position = pos;
  4510.         }
  4511.  
  4512.          return {
  4513.             statusCode: statusCode,
  4514.             statusText: statusText,
  4515.             headers: headers,
  4516.             body: readTo(text, context, delimiter)
  4517.         };
  4518.     };
  4519.  
  4520.     var readLine = function (text, context) {
  4521.         /// <summary>
  4522.         /// Returns a substring from the position defined by the context up to the next line break (CRLF).
  4523.         /// </summary>
  4524.         /// <param name="text" type="String" optional="false">Input string.</param>
  4525.         /// <param name="context" optional="false">Context used for reading the input string.</param>
  4526.         /// <returns type="String">Substring to the first ocurrence of a line break or null if none can be found. </returns>
  4527.  
  4528.         return readTo(text, context, "\r\n");
  4529.     };
  4530.  
  4531.     var readTo = function (text, context, str) {
  4532.         /// <summary>
  4533.         /// Returns a substring from the position given by the context up to value defined by the str parameter and increments the position in the context.
  4534.         /// </summary>
  4535.         /// <param name="text" type="String" optional="false">Input string.</param>
  4536.         /// <param name="context" type="Object" optional="false">Context used for reading the input string.</param>
  4537.         /// <param name="str" type="String" optional="true">Substring to read up to.</param>
  4538.         /// <returns type="String">Substring to the first ocurrence of str or the end of the input string if str is not specified. Null if the marker is not found.</returns>
  4539.  
  4540.         var start = context.position || 0;
  4541.         var end = text.length;
  4542.         if (str) {
  4543.             end = text.indexOf(str, start);
  4544.             if (end === -1) {
  4545.                 return null;
  4546.             }
  4547.             context.position = end + str.length;
  4548.         } else {
  4549.             context.position = end;
  4550.         }
  4551.  
  4552.         return text.substring(start, end);
  4553.     };
  4554.  
  4555.     var writeBatch = function (data, context) {
  4556.         /// <summary>
  4557.         /// Serializes a batch request object to a string.
  4558.         /// </summary>
  4559.         /// <param name="data" optional="false">Batch request object in payload representation format</param>
  4560.         /// <param name="context" optional="false">Context used for the serialization</param>
  4561.         /// <returns type="String">String representing the batch request</returns>
  4562.  
  4563.         var type = payloadTypeOf(data);
  4564.         if (type !== PAYLOADTYPE_BATCH) {
  4565.             throw { message: "Serialization of batches of type \"" + type + "\" is not supported" };
  4566.         }
  4567.  
  4568.         var batchBoundary = createBoundary("batch_");
  4569.         var batchParts = data.__batchRequests;
  4570.         var batch = "";
  4571.         var i, len;
  4572.         for (i = 0, len = batchParts.length; i < len; i++) {
  4573.             batch += writeBatchPartDelimiter(batchBoundary, false) +
  4574.                      writeBatchPart(batchParts[i], context);
  4575.         }
  4576.         batch += writeBatchPartDelimiter(batchBoundary, true);
  4577.  
  4578.         // Register the boundary with the request content type.
  4579.         var contentTypeProperties = context.contentType.properties;
  4580.         contentTypeProperties.boundary = batchBoundary;
  4581.  
  4582.         return batch;
  4583.     };
  4584.  
  4585.     var writeBatchPartDelimiter = function (boundary, close) {
  4586.         /// <summary>
  4587.         /// Creates the delimiter that indicates that start or end of an individual request.
  4588.         /// </summary>
  4589.         /// <param name="boundary" type="String" optional="false">Boundary string used to indicate the start of the request</param>
  4590.         /// <param name="close" type="Boolean">Flag indicating that a close delimiter string should be generated</param>
  4591.         /// <returns type="String">Delimiter string</returns>
  4592.  
  4593.         var result = "\r\n--" + boundary;
  4594.         if (close) {
  4595.             result += "--";
  4596.         }
  4597.  
  4598.         return result + "\r\n";
  4599.     };
  4600.  
  4601.     var writeBatchPart = function (part, context, nested) {
  4602.         /// <summary>
  4603.         /// Serializes a part of a batch request to a string. A part can be either a GET request or
  4604.         /// a change set grouping several CUD (create, update, delete) requests.
  4605.         /// </summary>
  4606.         /// <param name="part" optional="false">Request or change set object in payload representation format</param>
  4607.         /// <param name="context" optional="false">Object containing context information used for the serialization</param>
  4608.         /// <param name="nested" type="boolean" optional="true">Flag indicating that the part is nested inside a change set</param>
  4609.         /// <returns type="String">String representing the serialized part</returns>
  4610.         /// <remarks>
  4611.         /// A change set is an array of request objects and they cannot be nested inside other change sets.
  4612.         /// </remarks>
  4613.  
  4614.         var changeSet = part.__changeRequests;
  4615.         var result;
  4616.         if (isArray(changeSet)) {
  4617.             if (nested) {
  4618.                 throw { message: "Not Supported: change set nested in other change set" };
  4619.             }
  4620.  
  4621.             var changeSetBoundary = createBoundary("changeset_");
  4622.             result = "Content-Type: " + batchMediaType + "; boundary=" + changeSetBoundary + "\r\n";
  4623.             var i, len;
  4624.             for (i = 0, len = changeSet.length; i < len; i++) {
  4625.                 result += writeBatchPartDelimiter(changeSetBoundary, false) +
  4626.                      writeBatchPart(changeSet[i], context, true);
  4627.             }
  4628.  
  4629.             result += writeBatchPartDelimiter(changeSetBoundary, true);
  4630.         } else {
  4631.             result = "Content-Type: application/http\r\nContent-Transfer-Encoding: binary\r\n\r\n";
  4632.             prepareRequest(part, partHandler(context), { metadata: context.metadata });
  4633.             result += writeRequest(part);
  4634.         }
  4635.  
  4636.         return result;
  4637.     };
  4638.  
  4639.     var writeRequest = function (request) {
  4640.         /// <summary>
  4641.         /// Serializes a request object to a string.
  4642.         /// </summary>
  4643.         /// <param name="request" optional="false">Request object to serialize</param>
  4644.         /// <returns type="String">String representing the serialized request</returns>
  4645.  
  4646.         var result = (request.method ? request.method : "GET") + " " + request.requestUri + " HTTP/1.1\r\n";
  4647.         for (var name in request.headers) {
  4648.             if (request.headers[name]) {
  4649.                 result = result + name + ": " + request.headers[name] + "\r\n";
  4650.             }
  4651.         }
  4652.  
  4653.         result += "\r\n";
  4654.  
  4655.         if (request.body) {
  4656.             result += request.body;
  4657.         }
  4658.  
  4659.         return result;
  4660.     };
  4661.  
  4662.     odata.batchHandler = handler(batchParser, batchSerializer, batchMediaType, "1.0");
  4663.  
  4664.  
  4665.  
  4666.     var handlers = [odata.jsonHandler, odata.atomHandler, odata.xmlHandler, odata.textHandler];
  4667.  
  4668.     var dispatchHandler = function (handlerMethod, requestOrResponse, context) {
  4669.         /// <summary>Dispatches an operation to handlers.</summary>
  4670.         /// <param name="handlerMethod" type="String">Name of handler method to invoke.</param>
  4671.         /// <param name="requestOrResponse" type="Object">request/response argument for delegated call.</param>
  4672.         /// <param name="context" type="Object">context argument for delegated call.</param>
  4673.  
  4674.         var i, len;
  4675.         for (i = 0, len = handlers.length; i < len && !handlers[i][handlerMethod](requestOrResponse, context); i++) {
  4676.         }
  4677.  
  4678.         if (i === len) {
  4679.             throw { message: "no handler for data" };
  4680.         }
  4681.     };
  4682.  
  4683.     odata.defaultSuccess = function (data) {
  4684.         /// <summary>Default success handler for OData.</summary>
  4685.         /// <param name="data">Data to process.</param>
  4686.  
  4687.         window.alert(window.JSON.stringify(data));
  4688.     };
  4689.  
  4690.     odata.defaultError = throwErrorCallback;
  4691.  
  4692.     odata.defaultHandler = {
  4693.         read: function (response, context) {
  4694.             /// <summary>Reads the body of the specified response by delegating to JSON and ATOM handlers.</summary>
  4695.             /// <param name="response">Response object.</param>
  4696.             /// <param name="context">Operation context.</param>
  4697.  
  4698.             if (response && assigned(response.body) && response.headers["Content-Type"]) {
  4699.                 dispatchHandler("read", response, context);
  4700.             }
  4701.         },
  4702.  
  4703.         write: function (request, context) {
  4704.             /// <summary>Write the body of the specified request by delegating to JSON and ATOM handlers.</summary>
  4705.             /// <param name="request">Reques tobject.</param>
  4706.             /// <param name="context">Operation context.</param>
  4707.  
  4708.             dispatchHandler("write", request, context);
  4709.         },
  4710.  
  4711.         accept: "application/atomsvc+xml;q=0.8, application/json;q=0.5, */*;q=0.1"
  4712.     };
  4713.  
  4714.     odata.defaultMetadata = [];
  4715.  
  4716.     odata.read = function (urlOrRequest, success, error, handler, httpClient, metadata) {
  4717.         /// <summary>Reads data from the specified URL.</summary>
  4718.         /// <param name="urlOrRequest">URL to read data from.</param>
  4719.         /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param>
  4720.         /// <param name="error" type="Function" optional="true">Callback for handling errors.</param>
  4721.         /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param>
  4722.         /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param>
  4723.         /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param>
  4724.  
  4725.         var request;
  4726.         if (urlOrRequest instanceof String || typeof urlOrRequest === "string") {
  4727.             request = { requestUri: urlOrRequest };
  4728.         } else {
  4729.             request = urlOrRequest;
  4730.         }
  4731.  
  4732.         return odata.request(request, success, error, handler, httpClient, metadata);
  4733.     };
  4734.  
  4735.     odata.request = function (request, success, error, handler, httpClient, metadata) {
  4736.         /// <summary>Sends a request containing OData payload to a server.</summary>
  4737.         /// <param name="request" type="Object">Object that represents the request to be sent.</param>
  4738.         /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param>
  4739.         /// <param name="error" type="Function" optional="true">Callback for handling errors.</param>
  4740.         /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param>
  4741.         /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param>
  4742.         /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param>
  4743.  
  4744.         if (!success) {
  4745.             success = odata.defaultSuccess;
  4746.         }
  4747.  
  4748.         if (!error) {
  4749.             error = odata.defaultError;
  4750.         }
  4751.  
  4752.         if (!handler) {
  4753.             handler = odata.defaultHandler;
  4754.         }
  4755.  
  4756.         if (!httpClient) {
  4757.             httpClient = odata.defaultHttpClient;
  4758.         }
  4759.  
  4760.         if (!metadata) {
  4761.             metadata = odata.defaultMetadata;
  4762.         }
  4763.  
  4764.         // Augment the request with additional defaults.
  4765.         request.recognizeDates = defined(request.recognizeDates, odata.jsonHandler.recognizeDates);
  4766.         request.callbackParameterName = defined(request.callbackParameterName, odata.defaultHttpClient.callbackParameterName);
  4767.         request.formatQueryString = defined(request.formatQueryString, odata.defaultHttpClient.formatQueryString);
  4768.         request.enableJsonpCallback = defined(request.enableJsonpCallback, odata.defaultHttpClient.enableJsonpCallback);
  4769.  
  4770.         // Create the base context for read/write operations, also specifying complete settings.
  4771.         var context = {
  4772.             metadata: metadata,
  4773.             recognizeDates: request.recognizeDates,
  4774.             callbackParameterName: request.callbackParameterName,
  4775.             formatQueryString: request.formatQueryString,
  4776.             enableJsonpCallback: request.enableJsonpCallback
  4777.         };
  4778.  
  4779.         try {
  4780.             prepareRequest(request, handler, context);
  4781.             return invokeRequest(request, success, error, handler, httpClient, context);
  4782.         } catch (err) {
  4783.             error(err);
  4784.         }
  4785.     };
  4786.  
  4787.     // Configure the batch handler to use the default handler for the batch parts.
  4788.     odata.batchHandler.partHandler = odata.defaultHandler;
  4789.  
  4790.  
  4791.  
  4792.     var localStorage = window.localStorage;
  4793.  
  4794.     var domStoreDateToJSON = function () {
  4795.         /// <summary>Converts a Date object into an object representation friendly to JSON serialization.</summary>
  4796.         /// <returns type="Object">Object that represents the Date.</returns>
  4797.         /// <remarks>
  4798.         ///   This method is used to override the Date.toJSON method and is called only by
  4799.         ///   JSON.stringify.  It should never be called directly.
  4800.         /// </remarks>
  4801.  
  4802.         var newValue = { v: this.valueOf(), t: "[object Date]" };
  4803.         // Date objects might have extra properties on them so we save them.
  4804.         for (var name in this) {
  4805.             newValue[name] = this[name];
  4806.         }
  4807.         return newValue;
  4808.     };
  4809.  
  4810.     var domStoreJSONToDate = function (_, value) {
  4811.         /// <summary>JSON reviver function for converting an object representing a Date in a JSON stream to a Date object</summary>
  4812.         /// <param value="Object">Object to convert.</param>
  4813.         /// <returns type="Date">Date object.</returns>
  4814.         /// <remarks>
  4815.         ///   This method is used during JSON parsing and invoked only by the reviver function.
  4816.         ///   It should never be called directly.
  4817.         /// </remarks>
  4818.  
  4819.         if (value && value.t === "[object Date]") {
  4820.             var newValue = new Date(value.v);
  4821.             for (var name in value) {
  4822.                 if (name !== "t" && name !== "v") {
  4823.                     newValue[name] = value[name];
  4824.                 }
  4825.             }
  4826.             value = newValue;
  4827.         }
  4828.         return value;
  4829.     };
  4830.  
  4831.     var qualifyDomStoreKey = function (store, key) {
  4832.         /// <summary>Qualifies the key with the name of the store.</summary>
  4833.         /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param>
  4834.         /// <param name="key" type="String">Key string.</param>
  4835.         /// <returns type="String">Fully qualified key string.</returns>
  4836.  
  4837.         return store.name + "#!#" + key;
  4838.     };
  4839.  
  4840.     var unqualifyDomStoreKey = function (store, key) {
  4841.         /// <summary>Gets the key part of a fully qualified key string.</summary>
  4842.         /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param>
  4843.         /// <param name="key" type="String">Fully qualified key string.</param>
  4844.         /// <returns type="String">Key part string</returns>
  4845.  
  4846.         return key.replace(store.name + "#!#", "");
  4847.     };
  4848.  
  4849.     var DomStore = function (name) {
  4850.         /// <summary>Constructor for store objects that use DOM storage as the underlying mechanism.</summary>
  4851.         /// <param name="name" type="String">Store name.</param>
  4852.         this.name = name;
  4853.     };
  4854.  
  4855.     DomStore.create = function (name) {
  4856.         /// <summary>Creates a store object that uses DOM Storage as its underlying mechanism.</summary>
  4857.         /// <param name="name" type="String">Store name.</param>
  4858.         /// <returns type="Object">Store object.</returns>
  4859.  
  4860.         if (DomStore.isSupported()) {
  4861.             return new DomStore(name);
  4862.         }
  4863.  
  4864.         throw { message: "Web Storage not supported by the browser" };
  4865.     };
  4866.  
  4867.     DomStore.isSupported = function () {
  4868.         /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary>
  4869.         /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</summary>
  4870.         return !!localStorage;
  4871.     };
  4872.  
  4873.     DomStore.prototype.add = function (key, value, success, error) {
  4874.         /// <summary>Adds a new value identified by a key to the store.</summary>
  4875.         /// <param name="key" type="String">Key string.</param>
  4876.         /// <param name="value">Value that is going to be added to the store.</param>
  4877.         /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param>
  4878.         /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  4879.         /// <remarks>
  4880.         ///    This method errors out if the store already contains the specified key.
  4881.         /// </remarks>
  4882.  
  4883.         error = error || this.defaultError;
  4884.         var store = this;
  4885.         this.contains(key, function (contained) {
  4886.             if (!contained) {
  4887.                 store.addOrUpdate(key, value, success, error);
  4888.             } else {
  4889.                 delay(error, { message: "key already exists", key: key });
  4890.             }
  4891.         }, error);
  4892.     };
  4893.  
  4894.     DomStore.prototype.addOrUpdate = function (key, value, success, error) {
  4895.         /// <summary>Adds or updates a value identified by a key to the store.</summary>
  4896.         /// <param name="key" type="String">Key string.</param>
  4897.         /// <param name="value">Value that is going to be added or updated to the store.</param>
  4898.         /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param>
  4899.         /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  4900.         /// <remarks>
  4901.         ///   This method will overwrite the key's current value if it already exists in the store; otherwise it simply adds the new key and value.
  4902.         /// </remarks>
  4903.  
  4904.         error = error || this.defaultError;
  4905.  
  4906.         if (key instanceof Array) {
  4907.             error({ message: "Array of keys not supported" });
  4908.         } else {
  4909.             var fullKey = qualifyDomStoreKey(this, key);
  4910.             var oldDateToJSON = Date.prototype.toJSON;
  4911.             try {
  4912.                 var storedValue = value;
  4913.                 if (storedValue !== undefined) {
  4914.                     // Dehydrate using json
  4915.                     Date.prototype.toJSON = domStoreDateToJSON;
  4916.                     storedValue = window.JSON.stringify(value);
  4917.                 }
  4918.                 // Save the json string.
  4919.                 localStorage.setItem(fullKey, storedValue);
  4920.                 delay(success, key, value);
  4921.             }
  4922.             catch (e) {
  4923.                 if (e.code === 22 || e.number === 0x8007000E) {
  4924.                     delay(error, { name: "QUOTA_EXCEEDED_ERR", error: e });
  4925.                 } else {
  4926.                     delay(error, e);
  4927.                 }
  4928.             }
  4929.             finally {
  4930.                 Date.prototype.toJSON = oldDateToJSON;
  4931.             }
  4932.         }
  4933.     };
  4934.  
  4935.     DomStore.prototype.clear = function (success, error) {
  4936.         /// <summary>Removes all the data associated with this store object.</summary>
  4937.         /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param>
  4938.         /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  4939.         /// <remarks>
  4940.         ///    In case of an error, this method will not restore any keys that might have been deleted at that point.
  4941.         /// </remarks>
  4942.  
  4943.         error = error || this.defaultError;
  4944.         try {
  4945.             var i = 0, len = localStorage.length;
  4946.             while (len > 0 && i < len) {
  4947.                 var fullKey = localStorage.key(i);
  4948.                 var key = unqualifyDomStoreKey(this, fullKey);
  4949.                 if (fullKey !== key) {
  4950.                     localStorage.removeItem(fullKey);
  4951.                     len = localStorage.length;
  4952.                 } else {
  4953.                     i++;
  4954.                 }
  4955.             };
  4956.             delay(success);
  4957.         }
  4958.         catch (e) {
  4959.             delay(error, e);
  4960.         }
  4961.     };
  4962.  
  4963.     DomStore.prototype.close = function () {
  4964.         /// <summary>This function does nothing in DomStore as it does not have a connection model</summary>
  4965.     };
  4966.  
  4967.     DomStore.prototype.contains = function (key, success, error) {
  4968.         /// <summary>Checks whether a key exists in the store.</summary>
  4969.         /// <param name="key" type="String">Key string.</param>
  4970.         /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param>
  4971.         /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  4972.         error = error || this.defaultError;
  4973.         try {
  4974.             var fullKey = qualifyDomStoreKey(this, key);
  4975.             var value = localStorage.getItem(fullKey);
  4976.             delay(success, value !== null);
  4977.         } catch (e) {
  4978.             delay(error, e);
  4979.         }
  4980.     };
  4981.  
  4982.     DomStore.prototype.defaultError = throwErrorCallback;
  4983.  
  4984.     DomStore.prototype.getAllKeys = function (success, error) {
  4985.         /// <summary>Gets all the keys that exist in the store.</summary>
  4986.         /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param>
  4987.         /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  4988.  
  4989.         error = error || this.defaultError;
  4990.  
  4991.         var results = [];
  4992.         var i, len;
  4993.  
  4994.         try {
  4995.             for (i = 0, len = localStorage.length; i < len; i++) {
  4996.                 var fullKey = localStorage.key(i);
  4997.                 var key = unqualifyDomStoreKey(this, fullKey);
  4998.                 if (fullKey !== key) {
  4999.                     results.push(key);
  5000.                 }
  5001.             }
  5002.             delay(success, results);
  5003.         }
  5004.         catch (e) {
  5005.             delay(error, e);
  5006.         }
  5007.     };
  5008.  
  5009.     /// <summary>Identifies the underlying mechanism used by the store.</summary>
  5010.     DomStore.prototype.mechanism = "dom";
  5011.  
  5012.     DomStore.prototype.read = function (key, success, error) {
  5013.         /// <summary>Reads the value associated to a key in the store.</summary>
  5014.         /// <param name="key" type="String">Key string.</param>
  5015.         /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param>
  5016.         /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  5017.         error = error || this.defaultError;
  5018.  
  5019.         if (key instanceof Array) {
  5020.             error({ message: "Array of keys not supported" });
  5021.         } else {
  5022.             try {
  5023.                 var fullKey = qualifyDomStoreKey(this, key);
  5024.                 var value = localStorage.getItem(fullKey);
  5025.                 if (value !== null && value !== "undefined") {
  5026.                     // Hydrate using json
  5027.                     value = window.JSON.parse(value, domStoreJSONToDate);
  5028.                 }
  5029.                 else {
  5030.                     value = undefined;
  5031.                 }
  5032.                 delay(success, key, value);
  5033.             } catch (e) {
  5034.                 delay(error, e);
  5035.             }
  5036.         }
  5037.     };
  5038.  
  5039.     DomStore.prototype.remove = function (key, success, error) {
  5040.         /// <summary>Removes a key and its value from the store.</summary>
  5041.         /// <param name="key" type="String">Key string.</param>
  5042.         /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param>
  5043.         /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  5044.         error = error || this.defaultError;
  5045.  
  5046.         if (key instanceof Array) {
  5047.             error({ message: "Batches not supported" });
  5048.         } else {
  5049.             try {
  5050.                 var fullKey = qualifyDomStoreKey(this, key);
  5051.                 localStorage.removeItem(fullKey);
  5052.                 delay(success);
  5053.             } catch (e) {
  5054.                 delay(error, e);
  5055.             }
  5056.         }
  5057.     };
  5058.  
  5059.     DomStore.prototype.update = function (key, value, success, error) {
  5060.         /// <summary>Updates the value associated to a key in the store.</summary>
  5061.         /// <param name="key" type="String">Key string.</param>
  5062.         /// <param name="value">New value.</param>
  5063.         /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param>
  5064.         /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  5065.         /// <remarks>
  5066.         ///    This method errors out if the specified key is not found in the store.
  5067.         /// </remarks>
  5068.  
  5069.         error = error || this.defaultError;
  5070.         var store = this;
  5071.         this.contains(key, function (contained) {
  5072.             if (contained) {
  5073.                 store.addOrUpdate(key, value, success, error);
  5074.             } else {
  5075.                 delay(error, { message: "key not found", key: key });
  5076.             }
  5077.         }, error);
  5078.     };
  5079.  
  5080.  
  5081.  
  5082.     var mozIndexedDB = window.mozIndexedDB;
  5083.  
  5084.     var IDBTransaction = window.IDBTransaction;
  5085.     var IDBKeyRange = window.IDBKeyRange;
  5086.  
  5087.     var getError = function (error, defaultError) {
  5088.         /// <summary>Returns either a specific error handler or the default error handler</summary>
  5089.         /// <param name="error" type="Function">The specific error handler</param>
  5090.         /// <param name="defaultError" type="Function">The default error handler</param>
  5091.         /// <returns type="Function">The error callback</returns>
  5092.         return function (e) {
  5093.             if (e.code === 11 /* IndexedDb disk quota exceeded */) {
  5094.                 e = { name: "QUOTA_EXCEEDED_ERR", error: e };
  5095.             }
  5096.  
  5097.             if (error) {
  5098.                 error(e);
  5099.             } else if (defaultError) {
  5100.                 defaultError(e);
  5101.             }
  5102.         };
  5103.     };
  5104.  
  5105.     var openTransaction = function (store, mode, success, error) {
  5106.         /// <summary>Opens a new transaction to the store</summary>
  5107.         /// <param name="store" type="IndexedDBStore">The store object</param>
  5108.         /// <param name="mode" type="Short">The read/write mode of the transaction (constants from IDBTransaction)</param>
  5109.         /// <param name="success" type="Function">The success callback</param>
  5110.         /// <param name="error" type="Function">The error callback</param>
  5111.         var name = store.name;
  5112.         var db = store.db;
  5113.         var errorCallback = getError(error, store.defaultError);
  5114.  
  5115.         if (db) {
  5116.             success(db.transaction(name, mode));
  5117.         } else {
  5118.             var request = mozIndexedDB.open("_datajs_" + name);
  5119.             request.onsuccess = function (event) {
  5120.                 db = store.db = event.target.result;
  5121.                 if (!db.objectStoreNames.contains(name)) {
  5122.                     var versionRequest = db.setVersion("1.0");
  5123.                     versionRequest.onsuccess = function () {
  5124.                         db.createObjectStore(name, null, false);
  5125.                         success(db.transaction(name, mode));
  5126.                     };
  5127.                     versionRequest.onerror = errorCallback;
  5128.                     versionRequest.onblocked = errorCallback;
  5129.                 } else {
  5130.                     success(db.transaction(name, mode));
  5131.                 }
  5132.             };
  5133.             request.onerror = getError(error, this.defaultError);
  5134.         }
  5135.     };
  5136.  
  5137.     var IndexedDBStore = function (name) {
  5138.         /// <summary>Creates a new IndexedDBStore.</summary>
  5139.         /// <param name="name" type="String">The name of the store.</param>
  5140.         /// <returns type="Object">The new IndexedDBStore.</returns>
  5141.         this.name = name;
  5142.     };
  5143.  
  5144.     IndexedDBStore.create = function (name) {
  5145.         /// <summary>Creates a new IndexedDBStore.</summary>
  5146.         /// <param name="name" type="String">The name of the store.</param>
  5147.         /// <returns type="Object">The new IndexedDBStore.</returns>
  5148.         if (IndexedDBStore.isSupported()) {
  5149.             return new IndexedDBStore(name);
  5150.         }
  5151.  
  5152.         throw { message: "IndexedDB is not supported on this browser" };
  5153.     };
  5154.  
  5155.     IndexedDBStore.isSupported = function () {
  5156.         /// <summary>Returns whether IndexedDB is supported.</summary>
  5157.         /// <returns type="Boolean">True if IndexedDB is supported, false otherwise.</returns>
  5158.         return !!mozIndexedDB;
  5159.     };
  5160.  
  5161.     IndexedDBStore.prototype.add = function (key, value, success, error) {
  5162.         /// <summary>Adds a key/value pair to the store</summary>
  5163.         /// <param name="key" type="String">The key</param>
  5164.         /// <param name="value" type="Object">The value</param>
  5165.         /// <param name="success" type="Function">The success callback</param>
  5166.         /// <param name="error" type="Function">The error callback</param>
  5167.         var name = this.name;
  5168.         var defaultError = this.defaultError;
  5169.         var keys = [];
  5170.         var values = [];
  5171.  
  5172.         if (key instanceof Array) {
  5173.             keys = key;
  5174.             values = value;
  5175.         } else {
  5176.             keys = [key];
  5177.             values = [value];
  5178.         }
  5179.  
  5180.         openTransaction(this, IDBTransaction.READ_WRITE, function (transaction) {
  5181.             transaction.onabort = getError(error, defaultError);
  5182.             transaction.oncomplete = function () {
  5183.                 if (key instanceof Array) {
  5184.                     success(keys, values);
  5185.                 } else {
  5186.                     success(key, value);
  5187.                 }
  5188.             };
  5189.  
  5190.             for (var i = 0; i < keys.length && i < values.length; i++) {
  5191.                 transaction.objectStore(name).add(values[i], keys[i]);
  5192.             }
  5193.         }, error);
  5194.     };
  5195.  
  5196.     IndexedDBStore.prototype.addOrUpdate = function (key, value, success, error) {
  5197.         /// <summary>Adds or updates a key/value pair in the store</summary>
  5198.         /// <param name="key" type="String">The key</param>
  5199.         /// <param name="value" type="Object">The value</param>
  5200.         /// <param name="success" type="Function">The success callback</param>
  5201.         /// <param name="error" type="Function">The error callback</param>
  5202.         var name = this.name;
  5203.         var defaultError = this.defaultError;
  5204.         var keys = [];
  5205.         var values = [];
  5206.  
  5207.         if (key instanceof Array) {
  5208.             keys = key;
  5209.             values = value;
  5210.         } else {
  5211.             keys = [key];
  5212.             values = [value];
  5213.         }
  5214.  
  5215.         openTransaction(this, IDBTransaction.READ_WRITE, function (transaction) {
  5216.             transaction.onabort = getError(error, defaultError);
  5217.             transaction.oncomplete = function () {
  5218.                 if (key instanceof Array) {
  5219.                     success(keys, values);
  5220.                 } else {
  5221.                     success(key, value);
  5222.                 }
  5223.             };
  5224.  
  5225.             for (var i = 0; i < keys.length && i < values.length; i++) {
  5226.                 transaction.objectStore(name).put(values[i], keys[i]);
  5227.             }
  5228.         }, error);
  5229.     };
  5230.  
  5231.     IndexedDBStore.prototype.clear = function (success, error) {
  5232.         /// <summary>Clears the store</summary>
  5233.         /// <param name="success" type="Function">The success callback</param>
  5234.         /// <param name="error" type="Function">The error callback</param>
  5235.         var name = this.name;
  5236.         var defaultError = this.defaultError;
  5237.         openTransaction(this, IDBTransaction.READ_WRITE, function (transaction) {
  5238.             transaction.onerror = getError(error, defaultError);
  5239.             transaction.oncomplete = function () {
  5240.                 success();
  5241.             };
  5242.  
  5243.             transaction.objectStore(name).clear();
  5244.         }, error);
  5245.     };
  5246.  
  5247.     IndexedDBStore.prototype.close = function () {
  5248.         /// <summary>Closes the connection to the database</summary>
  5249.         if (this.db) {
  5250.             this.db.close();
  5251.             this.db = null;
  5252.         }
  5253.     };
  5254.  
  5255.     IndexedDBStore.prototype.contains = function (key, success, error) {
  5256.         /// <summary>Returns whether the store contains a key</summary>
  5257.         /// <param name="key" type="String">The key</param>
  5258.         /// <param name="success" type="Function">The success callback</param>
  5259.         /// <param name="error" type="Function">The error callback</param>
  5260.         var name = this.name;
  5261.         var defaultError = this.defaultError;
  5262.         openTransaction(this, IDBTransaction.READ_ONLY, function (transaction) {
  5263.             var request = transaction.objectStore(name).openCursor(IDBKeyRange.only(key));
  5264.             transaction.oncomplete = function () {
  5265.                 success(request.result !== undefined);
  5266.             };
  5267.             transaction.onerror = getError(error, defaultError);
  5268.         }, error);
  5269.     };
  5270.  
  5271.     IndexedDBStore.prototype.defaultError = throwErrorCallback;
  5272.  
  5273.     IndexedDBStore.prototype.getAllKeys = function (success, error) {
  5274.         /// <summary>Gets all the keys from the store</summary>
  5275.         /// <param name="success" type="Function">The success callback</param>
  5276.         /// <param name="error" type="Function">The error callback</param>
  5277.         var name = this.name;
  5278.         var defaultError = this.defaultError;
  5279.         openTransaction(this, IDBTransaction.READ_ONLY, function (transaction) {
  5280.             var results = [];
  5281.  
  5282.             transaction.oncomplete = function () {
  5283.                 success(results);
  5284.             };
  5285.  
  5286.             var request = transaction.objectStore(name).openCursor();
  5287.  
  5288.             request.onerror = getError(error, defaultError);
  5289.             request.onsuccess = function (event) {
  5290.                 var cursor = event.target.result;
  5291.                 if (cursor) {
  5292.                     results.push(cursor.key);
  5293.                     // Some tools have issues because continue is a javascript reserved word.
  5294.                     cursor["continue"].call(cursor);
  5295.                 }
  5296.             };
  5297.         }, error);
  5298.     };
  5299.  
  5300.     /// <summary>Identifies the underlying mechanism used by the store.</summary>
  5301.     IndexedDBStore.prototype.mechanism = "indexeddb";
  5302.  
  5303.     IndexedDBStore.prototype.read = function (key, success, error) {
  5304.         /// <summary>Reads the value for the specified key</summary>
  5305.         /// <param name="key" type="String">The key</param>
  5306.         /// <param name="success" type="Function">The success callback</param>
  5307.         /// <param name="error" type="Function">The error callback</param>
  5308.         /// <remarks>If the key does not exist, the success handler will be called with value = undefined</remarks>
  5309.         var name = this.name;
  5310.         var defaultError = this.defaultError;
  5311.         var keys = (key instanceof Array) ? key : [key];
  5312.  
  5313.         openTransaction(this, IDBTransaction.READ_WRITE, function (transaction) {
  5314.             var values = [];
  5315.  
  5316.             transaction.onerror = getError(error, defaultError);
  5317.             transaction.oncomplete = function () {
  5318.                 if (key instanceof Array) {
  5319.                     success(keys, values);
  5320.                 } else {
  5321.                     success(keys[0], values[0]);
  5322.                 }
  5323.             };
  5324.  
  5325.             for (var i = 0; i < keys.length; i++) {
  5326.                 // Some tools have issues because get is a javascript reserved word.
  5327.                 var objectStore = transaction.objectStore(name);
  5328.                 var request = objectStore["get"].call(objectStore, keys[i]);
  5329.                 request.onsuccess = function (event) {
  5330.                     values.push(event.target.result);
  5331.                 };
  5332.             }
  5333.         }, error);
  5334.     };
  5335.  
  5336.     IndexedDBStore.prototype.remove = function (key, success, error) {
  5337.         /// <summary>Removes the specified from the store</summary>
  5338.         /// <param name="key" type="String">The key</param>
  5339.         /// <param name="success" type="Function">The success callback</param>
  5340.         /// <param name="error" type="Function">The error callback</param>
  5341.         var name = this.name;
  5342.         var defaultError = this.defaultError;
  5343.         var keys = (key instanceof Array) ? key : [key];
  5344.  
  5345.         openTransaction(this, IDBTransaction.READ_WRITE, function (transaction) {
  5346.             transaction.onerror = getError(error, defaultError);
  5347.             transaction.oncomplete = function () {
  5348.                 success();
  5349.             };
  5350.  
  5351.             for (var i = 0; i < keys.length; i++) {
  5352.                 // Some tools have issues because continue is a javascript reserved word.
  5353.                 var objectStore = transaction.objectStore(name);
  5354.                 objectStore["delete"].call(objectStore, keys[i]);
  5355.             }
  5356.         }, error);
  5357.     };
  5358.  
  5359.     IndexedDBStore.prototype.update = function (key, value, success, error) {
  5360.         /// <summary>Updates a key/value pair in the store</summary>
  5361.         /// <param name="key" type="String">The key</param>
  5362.         /// <param name="value" type="Object">The value</param>
  5363.         /// <param name="success" type="Function">The success callback</param>
  5364.         /// <param name="error" type="Function">The error callback</param>
  5365.         var name = this.name;
  5366.         var defaultError = this.defaultError;
  5367.         var keys = [];
  5368.         var values = [];
  5369.  
  5370.         if (key instanceof Array) {
  5371.             keys = key;
  5372.             values = value;
  5373.         } else {
  5374.             keys = [key];
  5375.             values = [value];
  5376.         }
  5377.  
  5378.         openTransaction(this, IDBTransaction.READ_WRITE, function (transaction) {
  5379.             transaction.onabort = getError(error, defaultError);
  5380.             transaction.oncomplete = function () {
  5381.                 if (key instanceof Array) {
  5382.                     success(keys, values);
  5383.                 } else {
  5384.                     success(key, value);
  5385.                 }
  5386.             };
  5387.  
  5388.             for (var i = 0; i < keys.length && i < values.length; i++) {
  5389.                 var request = transaction.objectStore(name).openCursor(IDBKeyRange.only(keys[i]));
  5390.                 request.pair = { key: keys[i], value: values[i] };
  5391.                 request.onsuccess = function (event) {
  5392.                     var cursor = event.target.result;
  5393.                     if (cursor) {
  5394.                         cursor.update(event.target.pair.value);
  5395.                     } else {
  5396.                         transaction.abort();
  5397.                     }
  5398.                 };
  5399.             }
  5400.         }, error);
  5401.     };
  5402.  
  5403.  
  5404.  
  5405.     var MemoryStore = function (name) {
  5406.         /// <summary>Constructor for store objects that use a sorted array as the underlying mechanism.</summary>
  5407.         /// <param name="name" type="String">Store name.</param>
  5408.  
  5409.         var holes = [];
  5410.         var items = [];
  5411.         var keys = {};
  5412.  
  5413.         this.name = name;
  5414.  
  5415.         var getErrorCallback = function (error) {
  5416.             return error || this.defaultError;
  5417.         };
  5418.  
  5419.         var validateKeyInput = function (key, error) {
  5420.             /// <summary>Validates that the specified key is not undefined, not null, and not an array</summary>
  5421.             /// <param name="key">Key value.</param>
  5422.             /// <param name="error" type="Function">Error callback.</param>
  5423.             /// <returns type="Boolean">True if the key is valid. False if the key is invalid and the error callback has been queued for execution.</returns>
  5424.  
  5425.             var messageString;
  5426.  
  5427.             if (key instanceof Array) {
  5428.                 messageString = "Array of keys not supported";
  5429.             }
  5430.  
  5431.             if (key === undefined || key === null) {
  5432.                 messageString = "Invalid key";
  5433.             }
  5434.  
  5435.             if (messageString) {
  5436.                 delay(error, { message: messageString });
  5437.                 return false;
  5438.             }
  5439.             return true;
  5440.         };
  5441.  
  5442.         this.add = function (key, value, success, error) {
  5443.             /// <summary>Adds a new value identified by a key to the store.</summary>
  5444.             /// <param name="key" type="String">Key string.</param>
  5445.             /// <param name="value">Value that is going to be added to the store.</param>
  5446.             /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param>
  5447.             /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  5448.             /// <remarks>
  5449.             ///    This method errors out if the store already contains the specified key.
  5450.             /// </remarks>
  5451.  
  5452.             error = getErrorCallback(error);
  5453.  
  5454.             if (validateKeyInput(key, error)) {
  5455.                 if (!keys.hasOwnProperty(key)) {
  5456.                     this.addOrUpdate(key, value, success, error);
  5457.                 } else {
  5458.                     error({ message: "key already exists", key: key });
  5459.                 }
  5460.             }
  5461.         };
  5462.  
  5463.         this.addOrUpdate = function (key, value, success, error) {
  5464.             /// <summary>Adds or updates a value identified by a key to the store.</summary>
  5465.             /// <param name="key" type="String">Key string.</param>
  5466.             /// <param name="value">Value that is going to be added or updated to the store.</param>
  5467.             /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param>
  5468.             /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  5469.             /// <remarks>
  5470.             ///   This method will overwrite the key's current value if it already exists in the store; otherwise it simply adds the new key and value.
  5471.             /// </remarks>
  5472.  
  5473.             error = getErrorCallback(error);
  5474.  
  5475.             if (validateKeyInput(key, error)) {
  5476.                 var index = keys[key];
  5477.                 if (index === undefined) {
  5478.                     if (holes.length > 0) {
  5479.                         index = holes.splice(0, 1);
  5480.                     } else {
  5481.                         index = items.length;
  5482.                     }
  5483.                 }
  5484.                 items[index] = value;
  5485.                 keys[key] = index;
  5486.                 delay(success, key, value);
  5487.             }
  5488.         };
  5489.  
  5490.         this.clear = function (success) {
  5491.             /// <summary>Removes all the data associated with this store object.</summary>
  5492.             /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param>
  5493.  
  5494.             items = [];
  5495.             keys = {};
  5496.             holes = [];
  5497.  
  5498.             delay(success);
  5499.         };
  5500.  
  5501.         this.contains = function (key, success) {
  5502.             /// <summary>Checks whether a key exists in the store.</summary>
  5503.             /// <param name="key" type="String">Key string.</param>
  5504.             /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param>
  5505.  
  5506.             var contained = keys.hasOwnProperty(key);
  5507.             delay(success, contained);
  5508.         };
  5509.  
  5510.         this.getAllKeys = function (success) {
  5511.             /// <summary>Gets all the keys that exist in the store.</summary>
  5512.             /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param>
  5513.  
  5514.             var results = [];
  5515.             for (var name in keys) {
  5516.                 results.push(name);
  5517.             }
  5518.             delay(success, results);
  5519.         };
  5520.  
  5521.         this.read = function (key, success, error) {
  5522.             /// <summary>Reads the value associated to a key in the store.</summary>
  5523.             /// <param name="key" type="String">Key string.</param>
  5524.             /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param>
  5525.             /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  5526.             error = getErrorCallback(error);
  5527.  
  5528.             if (validateKeyInput(key, error)) {
  5529.                 var index = keys[key];
  5530.                 delay(success, key, items[index]);
  5531.             }
  5532.         };
  5533.  
  5534.         this.remove = function (key, success, error) {
  5535.             /// <summary>Removes a key and its value from the store.</summary>
  5536.             /// <param name="key" type="String">Key string.</param>
  5537.             /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param>
  5538.             /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  5539.             error = getErrorCallback(error);
  5540.  
  5541.             if (validateKeyInput(key, error)) {
  5542.                 var index = keys[key];
  5543.                 if (index !== undefined) {
  5544.                     if (index === items.length - 1) {
  5545.                         items.pop();
  5546.                     } else {
  5547.                         items[index] = undefined;
  5548.                         holes.push(index);
  5549.                     }
  5550.                     delete keys[key];
  5551.  
  5552.                     // The last item was removed, no need to keep track of any holes in the array.
  5553.                     if (items.length === 0) {
  5554.                         holes = [];
  5555.                     }
  5556.                 }
  5557.  
  5558.                 delay(success);
  5559.             }
  5560.         };
  5561.  
  5562.         this.update = function (key, value, success, error) {
  5563.             /// <summary>Updates the value associated to a key in the store.</summary>
  5564.             /// <param name="key" type="String">Key string.</param>
  5565.             /// <param name="value">New value.</param>
  5566.             /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param>
  5567.             /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
  5568.             /// <remarks>
  5569.             ///    This method errors out if the specified key is not found in the store.
  5570.             /// </remarks>
  5571.  
  5572.             error = getErrorCallback(error);
  5573.             if (validateKeyInput(key, error)) {
  5574.                 if (keys.hasOwnProperty(key)) {
  5575.                     this.addOrUpdate(key, value, success, error);
  5576.                 } else {
  5577.                     error({ message: "key not found", key: key });
  5578.                 }
  5579.             }
  5580.         };
  5581.     };
  5582.  
  5583.     MemoryStore.create = function (name) {
  5584.         /// <summary>Creates a store object that uses memory storage as its underlying mechanism.</summary>
  5585.         /// <param name="name" type="String">Store name.</param>
  5586.         /// <returns type="Object">Store object.</returns>
  5587.         return new MemoryStore(name);
  5588.     };
  5589.  
  5590.     MemoryStore.isSupported = function () {
  5591.         /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary>
  5592.         /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</returns>
  5593.         return true;
  5594.     };
  5595.  
  5596.     MemoryStore.prototype.close = function () {
  5597.         /// <summary>This function does nothing in MemoryStore as it does not have a connection model.</summary>
  5598.     };
  5599.  
  5600.     MemoryStore.prototype.defaultError = throwErrorCallback;
  5601.  
  5602.     /// <summary>Identifies the underlying mechanism used by the store.</summary>
  5603.     MemoryStore.prototype.mechanism = "memory";
  5604.  
  5605.  
  5606.  
  5607.     var mechanisms = {
  5608.         indexeddb: IndexedDBStore,
  5609.         dom: DomStore,
  5610.         memory: MemoryStore
  5611.     };
  5612.  
  5613.     datajs.defaultStoreMechanism = "best";
  5614.  
  5615.     datajs.createStore = function (name, mechanism) {
  5616.         /// <summary>Creates a new store object.</summary>
  5617.         /// <param name="name" type="String">Store name.</param>
  5618.         /// <param name="mechanism" type="String" optional="true">A specific mechanism to use (defaults to best, can be "best", "dom", "indexeddb", "webdb").</param>
  5619.         /// <returns type="Object">Store object.</returns>
  5620.  
  5621.         if (!mechanism) {
  5622.             mechanism = datajs.defaultStoreMechanism;
  5623.         }
  5624.  
  5625.         if (mechanism === "best") {
  5626.             mechanism = (DomStore.isSupported()) ? "dom" : "memory";
  5627.         }
  5628.  
  5629.         var factory = mechanisms[mechanism];
  5630.         if (factory) {
  5631.             return factory.create(name);
  5632.         }
  5633.  
  5634.         throw { message: "Failed to create store", name: name, mechanism: mechanism };
  5635.     };
  5636.  
  5637.  
  5638.  
  5639.  
  5640.     var forwardCall = function (thisValue, name, returnValue) {
  5641.         /// <summary>Creates a new function to forward a call.</summary>
  5642.         /// <param name="thisValue" type="Object">Value to use as the 'this' object.</param>
  5643.         /// <param name="name" type="String">Name of function to forward to.</param>
  5644.         /// <param name="returnValue" type="Object">Return value for the forward call (helps keep identity when chaining calls).</param>
  5645.         /// <returns type="Function">A new function that will forward a call.</returns>
  5646.  
  5647.         return function () {
  5648.             thisValue[name].apply(thisValue, arguments);
  5649.             return returnValue;
  5650.         };
  5651.     };
  5652.  
  5653.     var DjsDeferred = function () {
  5654.         /// <summary>Initializes a new DjsDeferred object.</summary>
  5655.         /// <remarks>
  5656.         /// Compability Note A - Ordering of callbacks through chained 'then' invocations
  5657.         ///
  5658.         /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
  5659.         /// implies that .then() returns a distinct object.
  5660.         ////
  5661.         /// For compatibility with http://api.jquery.com/category/deferred-object/
  5662.         /// we return this same object. This affects ordering, as
  5663.         /// the jQuery version will fire callbacks in registration
  5664.         /// order regardless of whether they occur on the result
  5665.         /// or the original object.
  5666.         ///
  5667.         /// Compability Note B - Fulfillment value
  5668.         ///
  5669.         /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
  5670.         /// implies that the result of a success callback is the
  5671.         /// fulfillment value of the object and is received by
  5672.         /// other success callbacks that are chained.
  5673.         ///
  5674.         /// For compatibility with http://api.jquery.com/category/deferred-object/
  5675.         /// we disregard this value instead.
  5676.         /// </remarks>
  5677.  
  5678.         this._arguments = undefined;
  5679.         this._done = undefined;
  5680.         this._fail = undefined;
  5681.         this.resolved = false;
  5682.         this.rejected = false;
  5683.     };
  5684.  
  5685.     DjsDeferred.prototype = {
  5686.         then: function (fulfilledHandler, errorHandler /*, progressHandler */) {
  5687.             /// <summary>Adds success and error callbacks for this deferred object.</summary>
  5688.             /// <param name="fulfilledHandler" type="Function" mayBeNull="true" optional="true">Success callback.</param>
  5689.             /// <param name="errorHandler" type="Function" mayBeNull="true" optional="true">Error callback.</param>
  5690.             /// <remarks>See Compatibility Note A.</remarks>
  5691.  
  5692.             if (fulfilledHandler) {
  5693.                 if (!this._done) {
  5694.                     this._done = [fulfilledHandler];
  5695.                 } else {
  5696.                     this._done.push(fulfilledHandler);
  5697.                 }
  5698.             }
  5699.  
  5700.             if (errorHandler) {
  5701.                 if (!this._fail) {
  5702.                     this._fail = [errorHandler];
  5703.                 } else {
  5704.                     this._fail.push(errorHandler);
  5705.                 }
  5706.             }
  5707.  
  5708.             //// See Compatibility Note A in the DjsDeferred constructor.
  5709.             //// if (!this._next) {
  5710.             ////    this._next = createDeferred();
  5711.             //// }
  5712.             //// return this._next.promise();
  5713.  
  5714.             if (this._resolved) {
  5715.                 this.resolve.apply(this, this._arguments);
  5716.             } else if (this._rejected) {
  5717.                 this.reject.apply(this, this._arguments);
  5718.             }
  5719.  
  5720.             return this;
  5721.         },
  5722.  
  5723.         resolve: function (/* args */) {
  5724.             /// <summary>Invokes success callbacks for this deferred object.</summary>
  5725.             /// <remarks>All arguments are forwarded to success callbacks.</remarks>
  5726.  
  5727.  
  5728.             if (this._done) {
  5729.                 var i, len;
  5730.                 for (i = 0, len = this._done.length; i < len; i++) {
  5731.                     //// See Compability Note B - Fulfillment value.
  5732.                     //// var nextValue =
  5733.                     this._done[i].apply(null, arguments);
  5734.                 }
  5735.  
  5736.                 //// See Compatibility Note A in the DjsDeferred constructor.
  5737.                 //// this._next.resolve(nextValue);
  5738.                 //// delete this._next;
  5739.  
  5740.                 this._done = undefined;
  5741.                 this._resolved = false;
  5742.                 this._arguments = undefined;
  5743.             } else {
  5744.                 this._resolved = true;
  5745.                 this._arguments = arguments;
  5746.             }
  5747.         },
  5748.  
  5749.         reject: function (/* args */) {
  5750.             /// <summary>Invokes error callbacks for this deferred object.</summary>
  5751.             /// <remarks>All arguments are forwarded to error callbacks.</remarks>
  5752.             if (this._fail) {
  5753.                 var i, len;
  5754.                 for (i = 0, len = this._fail.length; i < len; i++) {
  5755.                     this._fail[i].apply(null, arguments);
  5756.                 }
  5757.  
  5758.                 this._fail = undefined;
  5759.                 this._rejected = false;
  5760.                 this._arguments = undefined;
  5761.             } else {
  5762.                 this._rejected = true;
  5763.                 this._arguments = arguments;
  5764.             }
  5765.         },
  5766.  
  5767.         promise: function () {
  5768.             /// <summary>Returns a version of this object that has only the read-only methods available.</summary>
  5769.             /// <returns>An object with only the promise object.</returns>
  5770.  
  5771.             var result = {};
  5772.             result.then = forwardCall(this, "then", result);
  5773.             return result;
  5774.         }
  5775.     };
  5776.  
  5777.     var createDeferred = function () {
  5778.         /// <summary>Creates a deferred object.</summary>
  5779.         /// <returns type="DjsDeferred">
  5780.         /// A new deferred object. If jQuery is installed, then a jQuery
  5781.         /// Deferred object is returned, which provides a superset of features.
  5782.         /// </returns>
  5783.  
  5784.         if (window.jQuery && window.jQuery.Deferred) {
  5785.             return new window.jQuery.Deferred();
  5786.         } else {
  5787.             return new DjsDeferred();
  5788.         }
  5789.     };
  5790.  
  5791.  
  5792.  
  5793.     var appendQueryOption = function (uri, queryOption) {
  5794.         /// <summary>Appends the specified escaped query option to the specified URI.</summary>
  5795.         /// <param name="uri" type="String">URI to append option to.</param>
  5796.         /// <param name="queryOption" type="String">Escaped query option to append.</param>
  5797.         var separator = (uri.indexOf("?") >= 0) ? "&" : "?";
  5798.         return uri + separator + queryOption;
  5799.     };
  5800.  
  5801.     var appendSegment = function (uri, segment) {
  5802.         /// <summary>Appends the specified segment to the given URI.</summary>
  5803.         /// <param name="uri" type="String">URI to append a segment to.</param>
  5804.         /// <param name="segment" type="String">Segment to append.</param>
  5805.         /// <returns type="String">The original URI with a new segment appended.</returns>
  5806.  
  5807.         var index = uri.indexOf("?");
  5808.         var queryPortion = "";
  5809.         if (index >= 0) {
  5810.             queryPortion = uri.substr(index);
  5811.             uri = uri.substr(0, index);
  5812.         }
  5813.  
  5814.         if (uri[uri.length - 1] !== "/") {
  5815.             uri += "/";
  5816.         }
  5817.         return uri + segment + queryPortion;
  5818.     };
  5819.  
  5820.     var buildODataRequest = function (uri, options) {
  5821.         /// <summary>Builds a request object to GET the specified URI.</summary>
  5822.         /// <param name="uri" type="String">URI for request.</param>
  5823.         /// <param name="options" type="Object">Additional options.</param>
  5824.  
  5825.         return {
  5826.             method: "GET",
  5827.             requestUri: uri,
  5828.             user: options.user,
  5829.             password: options.password,
  5830.             enableJsonpCallback: options.enableJsonpCallback,
  5831.             callbackParameterName: options.callbackParameterName,
  5832.             formatQueryString: options.formatQueryString
  5833.         };
  5834.     };
  5835.  
  5836.     var findQueryOptionStart = function (uri, name) {
  5837.         /// <summary>Finds the index where the value of a query option starts.</summary>
  5838.         /// <param name="uri" type="String">URI to search in.</param>
  5839.         /// <param name="name" type="String">Name to look for.</param>
  5840.         /// <returns type="Number">The index where the query option starts.</returns>
  5841.  
  5842.         var result = -1;
  5843.         var queryIndex = uri.indexOf("?");
  5844.         if (queryIndex !== -1) {
  5845.             var start = uri.indexOf("?" + name + "=", queryIndex);
  5846.             if (start === -1) {
  5847.                 start = uri.indexOf("&" + name + "=", queryIndex);
  5848.             }
  5849.             if (start !== -1) {
  5850.                 result = start + name.length + 2;
  5851.             }
  5852.         }
  5853.         return result;
  5854.     };
  5855.  
  5856.     var queryForData = function (uri, options, success, error) {
  5857.         /// <summary>Gets data from an OData service.</summary>
  5858.         /// <param name="uri" type="String">URI to the OData service.</param>
  5859.         /// <param name="options" type="Object">Object with additional well-known request options.</param>
  5860.         /// <param name="success" type="Function">Success callback.</param>
  5861.         /// <param name="error" type="Function">Error callback.</param>
  5862.         /// <returns type="Object">Object with an abort method.</returns>
  5863.  
  5864.         var request = queryForDataInternal(uri, options, [], success, error);
  5865.         return request;
  5866.     };
  5867.  
  5868.     var queryForDataInternal = function (uri, options, data, success, error) {
  5869.         /// <summary>Gets data from an OData service taking into consideration server side paging.</summary>
  5870.         /// <param name="uri" type="String">URI to the OData service.</param>
  5871.         /// <param name="options" type="Object">Object with additional well-known request options.</param>
  5872.         /// <param name="data" type="Array">Array that stores the data provided by the OData service.</param>
  5873.         /// <param name="success" type="Function">Success callback.</param>
  5874.         /// <param name="error" type="Function">Error callback.</param>
  5875.         /// <returns type="Object">Object with an abort method.</returns>
  5876.  
  5877.         var request = buildODataRequest(uri, options);
  5878.         var currentRequest = odata.request(request, function (newData) {
  5879.             var next = newData.__next;
  5880.             var results = newData.results;
  5881.  
  5882.             data = data.concat(results);
  5883.  
  5884.             if (next) {
  5885.                 currentRequest = queryForDataInternal(next, options, data, success, error);
  5886.             } else {
  5887.                 success(data);
  5888.             }
  5889.         }, error, undefined, options.httpClient, options.metadata);
  5890.  
  5891.         return {
  5892.             abort: function () {
  5893.                 currentRequest.abort();
  5894.             }
  5895.         };
  5896.     };
  5897.  
  5898.     var ODataCacheSource = function (options) {
  5899.         /// <summary>Creates a data cache source object for requesting data from an OData service.</summary>
  5900.         /// <param name="options">Options for the cache data source.</param>
  5901.         /// <returns type="ODataCacheSource">A new data cache source instance.</returns>
  5902.  
  5903.         var that = this;
  5904.         var uri = options.source;
  5905.        
  5906.         that.identifier = normalizeURICase(encodeURI(decodeURI(uri)));
  5907.         that.options = options;
  5908.  
  5909.         that.count = function (success, error) {
  5910.             /// <summary>Gets the number of items in the collection.</summary>
  5911.             /// <param name="success" type="Function">Success callback with the item count.</param>
  5912.             /// <param name="error" type="Function">Error callback.</param>
  5913.             /// <returns type="Object">Request object with an abort method./<param>
  5914.  
  5915.             var options = that.options;
  5916.             return odata.request(
  5917.                 buildODataRequest(appendSegment(uri, "$count"), options),
  5918.                 function (data) {
  5919.                     var count = parseInt10(data.toString());
  5920.                     if (isNaN(count)) {
  5921.                         error({ message: "Count is NaN", count: count });
  5922.                     } else {
  5923.                         success(count);
  5924.                     }
  5925.                 }, error, undefined, options.httpClient, options.metadata);
  5926.         };
  5927.  
  5928.         that.read = function (index, count, success, error) {
  5929.             /// <summary>Gets a number of consecutive items from the collection.</summary>
  5930.             /// <param name="index" type="Number">Zero-based index of the items to retrieve.</param>
  5931.             /// <param name="count" type="Number">Number of items to retrieve.</param>
  5932.             /// <param name="success" type="Function">Success callback with the requested items.</param>
  5933.             /// <param name="error" type="Function">Error callback.</param>
  5934.             /// <returns type="Object">Request object with an abort method./<param>
  5935.  
  5936.             var queryOptions = "$skip=" + index + "&$top=" + count;
  5937.             return queryForData(appendQueryOption(uri, queryOptions), that.options, success, error);
  5938.         };
  5939.  
  5940.         return that;
  5941.     };
  5942.  
  5943.  
  5944.  
  5945.     var appendPage = function (operation, page) {
  5946.         /// <summary>Appends a page's data to the operation data.</summary>
  5947.         /// <param name="operation" type="Object">Operation with (i)ndex, (c)ount and (d)ata.</param>
  5948.         /// <param name="page" type="Object">Page with (i)ndex, (c)ount and (d)ata.</param>
  5949.  
  5950.         var intersection = intersectRanges(operation, page);
  5951.         if (intersection) {
  5952.             var start = intersection.i - page.i;
  5953.             var end = start + (operation.c - operation.d.length);
  5954.             operation.d = operation.d.concat(page.d.slice(start, end));
  5955.         }
  5956.     };
  5957.  
  5958.     var intersectRanges = function (x, y) {
  5959.         /// <summary>Returns the {(i)ndex, (c)ount} range for the intersection of x and y.</summary>
  5960.         /// <param name="x" type="Object">Range with (i)ndex and (c)ount members.</param>
  5961.         /// <param name="y" type="Object">Range with (i)ndex and (c)ount members.</param>
  5962.         /// <returns type="Object">The intersection (i)ndex and (c)ount; undefined if there is no intersection.</returns>
  5963.  
  5964.         var xLast = x.i + x.c;
  5965.         var yLast = y.i + y.c;
  5966.         var resultIndex = (x.i > y.i) ? x.i : y.i;
  5967.         var resultLast = (xLast < yLast) ? xLast : yLast;
  5968.         var result;
  5969.         if (resultLast >= resultIndex) {
  5970.             result = { i: resultIndex, c: resultLast - resultIndex };
  5971.         }
  5972.  
  5973.         return result;
  5974.     };
  5975.  
  5976.     var checkZeroGreater = function (val, name) {
  5977.         /// <summary>Checks whether val is a defined number with value zero or greater.</summary>
  5978.         /// <param name="val" type="Number">Value to check.</param>
  5979.         /// <param name="name" type="String">Parameter name to use in exception.</param>
  5980.  
  5981.         if (val === undefined || typeof val !== "number") {
  5982.             throw { message: "'" + name + "' must be a number." };
  5983.         }
  5984.  
  5985.         if (isNaN(val) || val < 0 || !isFinite(val)) {
  5986.             throw { message: "'" + name + "' must be greater than or equal to zero." };
  5987.         }
  5988.     };
  5989.  
  5990.     var checkUndefinedGreaterThanZero = function (val, name) {
  5991.         /// <summary>Checks whether val is undefined or a number with value greater than zero.</summary>
  5992.         /// <param name="val" type="Number">Value to check.</param>
  5993.         /// <param name="name" type="String">Parameter name to use in exception.</param>
  5994.  
  5995.         if (val !== undefined) {
  5996.             if (typeof val !== "number") {
  5997.                 throw { message: "'" + name + "' must be a number." };
  5998.             }
  5999.  
  6000.             if (isNaN(val) || val <= 0 || !isFinite(val)) {
  6001.                 throw { message: "'" + name + "' must be greater than zero." };
  6002.             }
  6003.         }
  6004.     };
  6005.  
  6006.     var checkUndefinedOrNumber = function (val, name) {
  6007.         /// <summary>Checks whether val is undefined or a number</summary>
  6008.         /// <param name="val" type="Number">Value to check.</param>
  6009.         /// <param name="name" type="String">Parameter name to use in exception.</param>
  6010.         if (val !== undefined && (typeof val !== "number" || isNaN(val) || !isFinite(val))) {
  6011.             throw { message: "'" + name + "' must be a number." };
  6012.         }
  6013.     };
  6014.  
  6015.     var removeFromArray = function (arr, item) {
  6016.         /// <summary>Performs a linear search on the specified array and removes the first instance of 'item'.</summary>
  6017.         /// <param name="arr" type="Array">Array to search.</param>
  6018.         /// <param name="item">Item being sought.</param>
  6019.         /// <returns type="Boolean">Whether the item was removed.</returns>
  6020.  
  6021.         var i, len;
  6022.         for (i = 0, len = arr.length; i < len; i++) {
  6023.             if (arr[i] === item) {
  6024.                 arr.splice(i, 1);
  6025.                 return true;
  6026.             }
  6027.         }
  6028.  
  6029.         return false;
  6030.     };
  6031.  
  6032.     var extend = function (target, values) {
  6033.         /// <summary>Extends the target with the specified values.</summary>
  6034.         /// <param name="target" type="Object">Object to add properties to.</param>
  6035.         /// <param name="values" type="Object">Object with properties to add into target.</param>
  6036.         /// <returns type="Object">The target object.</returns>
  6037.  
  6038.         for (var name in values) {
  6039.             target[name] = values[name];
  6040.         }
  6041.  
  6042.         return target;
  6043.     };
  6044.  
  6045.     var estimateSize = function (obj) {
  6046.         /// <summary>Estimates the size of an object in bytes.</summary>
  6047.         /// <param name="obj" type="Object">Object to determine the size of.</param>
  6048.         /// <returns type="Integer">Estimated size of the object in bytes.</returns>
  6049.         var size = 0;
  6050.         var type = typeof obj;
  6051.  
  6052.         if (type === "object" && obj) {
  6053.             for (var name in obj) {
  6054.                 size += name.length * 2 + estimateSize(obj[name]);
  6055.             }
  6056.         } else if (type === "string") {
  6057.             size = obj.length * 2;
  6058.         } else {
  6059.             size = 8;
  6060.         }
  6061.         return size;
  6062.     };
  6063.  
  6064.     var snapToPageBoundaries = function (lowIndex, highIndex, pageSize) {
  6065.         /// <summary>Snaps low and high indices into page sizes and returns a range.</summary>
  6066.         /// <param name="lowIndex" type="Number">Low index to snap to a lower value.</param>
  6067.         /// <param name="highIndex" type="Number">High index to snap to a higher value.</param>
  6068.         /// <param name="pageSize" type="Number">Page size to snap to.</param>
  6069.         /// <returns type="Object">A range with (i)ndex and (c)ount of elements.</returns>
  6070.  
  6071.         lowIndex = Math.floor(lowIndex / pageSize) * pageSize;
  6072.         highIndex = Math.ceil((highIndex + 1) / pageSize) * pageSize;
  6073.         return { i: lowIndex, c: highIndex - lowIndex };
  6074.     };
  6075.  
  6076.     // The DataCache is implemented using state machines.  The following constants are used to properly
  6077.     // identify and label the states that these machines transition to.
  6078.  
  6079.     // DataCache state constants
  6080.  
  6081.     var CACHE_STATE_DESTROY = "destroy";
  6082.     var CACHE_STATE_IDLE = "idle";
  6083.     var CACHE_STATE_INIT = "init";
  6084.     var CACHE_STATE_READ = "read";
  6085.     var CACHE_STATE_PREFETCH = "prefetch";
  6086.     var CACHE_STATE_WRITE = "write";
  6087.  
  6088.     // DataCacheOperation state machine states.  
  6089.     // Transitions on operations also depend on the cache current of the cache.
  6090.  
  6091.     var OPERATION_STATE_CANCEL = "cancel";
  6092.     var OPERATION_STATE_END = "end";
  6093.     var OPERATION_STATE_ERROR = "error";
  6094.     var OPERATION_STATE_START = "start";
  6095.     var OPERATION_STATE_WAIT = "wait";
  6096.  
  6097.     // Destroy state machine states
  6098.  
  6099.     var DESTROY_STATE_CLEAR = "clear";
  6100.  
  6101.     // Read / Prefetch state machine states
  6102.  
  6103.     var READ_STATE_DONE = "done";
  6104.     var READ_STATE_LOCAL = "local";
  6105.     var READ_STATE_SAVE = "save";
  6106.     var READ_STATE_SOURCE = "source";
  6107.  
  6108.     var DataCacheOperation = function (stateMachine, promise, isCancelable, index, count, data, pending) {
  6109.         /// <summary>Creates a new operation object.</summary>
  6110.         /// <param name="stateMachine" type="Function">State machine that describes the specific behavior of the operation.</param>
  6111.         /// <param name="promise" type ="DjsDeferred">Promise for requested values.</param>
  6112.         /// <param name="isCancelable" type ="Boolean">Whether this operation can be canceled or not.</param>
  6113.         /// <param name="index" type="Number">Index of first item requested.</param>
  6114.         /// <param name="count" type="Number">Count of items requested.</param>
  6115.         /// <param name="data" type="Array">Array with the items requested by the operation.</param>
  6116.         /// <param name="pending" type="Number">Total number of pending prefetch records.</param>
  6117.         /// <returns type="DataCacheOperation">A new data cache operation instance.</returns>
  6118.  
  6119.         /// <field name="p" type="DjsDeferred">Promise for requested values.</field>
  6120.         /// <field name="i" type="Number">Index of first item requested.</field>
  6121.         /// <field name="c" type="Number">Count of items requested.</field>
  6122.         /// <field name="d" type="Array">Array with the items requested by the operation.</field>
  6123.         /// <field name="s" type="Array">Current state of the operation.</field>
  6124.         /// <field name="canceled" type="Boolean">Whether the operation has been canceled.</field>
  6125.         /// <field name="pending" type="Number">Total number of pending prefetch records.</field>
  6126.         /// <field name="oncomplete" type="Function">Callback executed when the operation reaches the end state.</field>
  6127.  
  6128.         var stateData;
  6129.         var cacheState;
  6130.         var that = this;
  6131.  
  6132.         that.p = promise;
  6133.         that.i = index;
  6134.         that.c = count;
  6135.         that.d = data;
  6136.         that.s = OPERATION_STATE_START;
  6137.  
  6138.         that.canceled = false;
  6139.         that.pending = pending;
  6140.         that.oncomplete = null;
  6141.  
  6142.         that.cancel = function () {
  6143.             /// <summary>Transitions this operation to the cancel state and sets the canceled flag to true.</summary>
  6144.             /// <remarks>The function is a no-op if the operation is non-cancelable.</summary>
  6145.  
  6146.             if (!isCancelable) {
  6147.                 return;
  6148.             }
  6149.  
  6150.             var state = that.s;
  6151.             if (state !== OPERATION_STATE_ERROR && state !== OPERATION_STATE_END && state !== OPERATION_STATE_CANCEL) {
  6152.                 that.canceled = true;
  6153.                 transition(OPERATION_STATE_CANCEL, stateData);
  6154.             }
  6155.         };
  6156.  
  6157.         that.complete = function () {
  6158.             /// <summary>Transitions this operation to the end state.</summary>
  6159.  
  6160.             transition(OPERATION_STATE_END, stateData);
  6161.         };
  6162.  
  6163.         that.error = function (err) {
  6164.             /// <summary>Transitions this operation to the error state.</summary>
  6165.             if (!that.canceled) {
  6166.                 transition(OPERATION_STATE_ERROR, err);
  6167.             }
  6168.         };
  6169.  
  6170.         that.run = function (state) {
  6171.             /// <summary>Executes the operation's current state in the context of a new cache state.</summary>
  6172.             /// <param name="state" type="Object">New cache state.</param>
  6173.  
  6174.             cacheState = state;
  6175.             that.transition(that.s, stateData);
  6176.         };
  6177.  
  6178.         that.wait = function (data) {
  6179.             /// <summary>Transitions this operation to the wait state.</summary>
  6180.  
  6181.             transition(OPERATION_STATE_WAIT, data);
  6182.         };
  6183.  
  6184.         var operationStateMachine = function (opTargetState, cacheState, data) {
  6185.             /// <summary>State machine that describes all operations common behavior.</summary>
  6186.             /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
  6187.             /// <param name="cacheState" type="Object">Current cache state.</param>
  6188.             /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
  6189.  
  6190.             switch (opTargetState) {
  6191.                 case OPERATION_STATE_START:
  6192.                     // Initial state of the operation. The operation will remain in this state until the cache has been fully initialized.
  6193.                     if (cacheState !== CACHE_STATE_INIT) {
  6194.                         stateMachine(that, opTargetState, cacheState, data);
  6195.                     }
  6196.                     break;
  6197.  
  6198.                 case OPERATION_STATE_WAIT:
  6199.                     // Wait state indicating that the operation is active but waiting for an asynchronous operation to complete.
  6200.                     stateMachine(that, opTargetState, cacheState, data);
  6201.                     break;
  6202.  
  6203.                 case OPERATION_STATE_CANCEL:
  6204.                     // Cancel state.
  6205.                     stateMachine(that, opTargetState, cacheState, data);
  6206.                     that.fireCanceled();
  6207.                     transition(OPERATION_STATE_END);
  6208.                     break;
  6209.  
  6210.                 case OPERATION_STATE_ERROR:
  6211.                     // Error state. Data is expected to be an object detailing the error condition.  
  6212.                     stateMachine(that, opTargetState, cacheState, data);
  6213.                     that.canceled = true;
  6214.                     that.fireRejected(data);
  6215.                     transition(OPERATION_STATE_END);
  6216.                     break;
  6217.  
  6218.                 case OPERATION_STATE_END:
  6219.                     // Final state of the operation.
  6220.                     if (that.oncomplete) {
  6221.                         that.oncomplete(that);
  6222.                     }
  6223.                     if (!that.canceled) {
  6224.                         that.fireResolved();
  6225.                     }
  6226.                     stateMachine(that, opTargetState, cacheState, data);
  6227.                     break;
  6228.  
  6229.                 default:
  6230.                     // Any other state is passed down to the state machine describing the operation's specific behavior.
  6231.                         stateMachine(that, opTargetState, cacheState, data);
  6232.                     break;
  6233.             }
  6234.         };
  6235.  
  6236.         var transition = function (state, data) {
  6237.             /// <summary>Transitions this operation to a new state.</summary>
  6238.             /// <param name="state" type="Object">State to transition the operation to.</param>
  6239.             /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
  6240.  
  6241.             that.s = state;
  6242.             stateData = data;
  6243.             operationStateMachine(state, cacheState, data);
  6244.         };
  6245.  
  6246.         that.transition = transition;
  6247.  
  6248.         return that;
  6249.     };
  6250.  
  6251.     DataCacheOperation.prototype.fireResolved = function () {
  6252.         /// <summary>Fires a resolved notification as necessary.</summary>
  6253.  
  6254.         // Fire the resolve just once.
  6255.         var p = this.p;
  6256.         if (p) {
  6257.             this.p = null;
  6258.             p.resolve(this.d);
  6259.         }
  6260.     };
  6261.  
  6262.     DataCacheOperation.prototype.fireRejected = function (reason) {
  6263.         /// <summary>Fires a rejected notification as necessary.</summary>
  6264.  
  6265.         // Fire the rejection just once.
  6266.         var p = this.p;
  6267.         if (p) {
  6268.             this.p = null;
  6269.             p.reject(reason);
  6270.         }
  6271.     };
  6272.  
  6273.     DataCacheOperation.prototype.fireCanceled = function () {
  6274.         /// <summary>Fires a canceled notification as necessary.</summary>
  6275.  
  6276.         this.fireRejected({ canceled: true, message: "Operation canceled" });
  6277.     };
  6278.  
  6279.  
  6280.     var DataCache = function (options) {
  6281.         /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary>
  6282.         /// <param name="options">
  6283.         /// Options for the data cache, including name, source, pageSize,
  6284.         /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
  6285.         /// </param>
  6286.         /// <returns type="DataCache">A new data cache instance.</returns>
  6287.  
  6288.         var state = CACHE_STATE_INIT;
  6289.         var stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
  6290.  
  6291.         var clearOperations = [];
  6292.         var readOperations = [];
  6293.         var prefetchOperations = [];
  6294.  
  6295.         var actualCacheSize = 0;                                             // Actual cache size in bytes.
  6296.         var allDataLocal = false;                                            // Whether all data is local.
  6297.         var cacheSize = undefinedDefault(options.cacheSize, 1048576);        // Requested cache size in bytes, default 1 MB.
  6298.         var collectionCount = 0;                                             // Number of elements in the server collection.
  6299.         var highestSavedPage = 0;                                            // Highest index of all the saved pages.
  6300.         var highestSavedPageSize = 0;                                        // Item count of the saved page with the highest index.
  6301.         var overflowed = cacheSize === 0;                                    // If the cache has overflowed (actualCacheSize > cacheSize or cacheSize == 0);
  6302.         var pageSize = undefinedDefault(options.pageSize, 50);               // Number of elements to store per page.
  6303.         var prefetchSize = undefinedDefault(options.prefetchSize, pageSize); // Number of elements to prefetch from the source when the cache is idling.
  6304.         var version = "1.0";
  6305.         var cacheFailure;
  6306.  
  6307.         var pendingOperations = 0;
  6308.  
  6309.         var source = options.source;
  6310.         if (typeof source === "string") {
  6311.             // Create a new cache source.
  6312.             source = new ODataCacheSource(options);
  6313.         }
  6314.         source.options = options;
  6315.  
  6316.         // Create a cache local store.
  6317.         var store = datajs.createStore(options.name, options.mechanism);
  6318.  
  6319.         var that = this;
  6320.  
  6321.         that.onidle = options.idle;
  6322.         that.stats = stats;
  6323.  
  6324.         that.count = function () {
  6325.             /// <summary>Counts the number of items in the collection.</summary>
  6326.             /// <returns type="Object">A promise with the number of items.</returns>
  6327.  
  6328.             if (cacheFailure) {
  6329.                 throw cacheFailure;
  6330.             }
  6331.  
  6332.             var deferred = createDeferred();
  6333.             var canceled = false;
  6334.  
  6335.             if (allDataLocal) {
  6336.                 delay(function () {
  6337.                     deferred.resolve(collectionCount);
  6338.                 });
  6339.  
  6340.                 return deferred.promise();
  6341.             }
  6342.  
  6343.             // TODO: Consider returning the local data count instead once allDataLocal flag is set to true.
  6344.             var request = source.count(function (count) {
  6345.                 request = null;
  6346.                 stats.counts++;
  6347.                 deferred.resolve(count);
  6348.             }, function (err) {
  6349.                 request = null;
  6350.                 deferred.reject(extend(err, { canceled: canceled }));
  6351.             });
  6352.  
  6353.             return extend(deferred.promise(), {
  6354.                 cancel: function () {
  6355.                     /// <summary>Aborts the count operation.</summary>
  6356.                     if (request) {
  6357.                         canceled = true;
  6358.                         request.abort();
  6359.                         request = null;
  6360.                     }
  6361.                 }
  6362.             });
  6363.         };
  6364.  
  6365.         that.clear = function () {
  6366.             /// <summary>Cancels all running operations and clears all local data associated with this cache.</summary>
  6367.             /// <remarks>
  6368.             /// New read requests made while a clear operation is in progress will not be canceled.
  6369.             /// Instead they will be queued for execution once the operation is completed.
  6370.             /// </remarks>
  6371.             /// <returns type="Object">A promise that has no value and can't be canceled.</returns>
  6372.  
  6373.             if (cacheFailure) {
  6374.                 throw cacheFailure;
  6375.             }
  6376.  
  6377.             if (clearOperations.length === 0) {
  6378.                 var deferred = createDeferred();
  6379.                 var op = new DataCacheOperation(destroyStateMachine, deferred, false);
  6380.                 queueAndStart(op, clearOperations);
  6381.                 return deferred.promise();
  6382.             }
  6383.             return clearOperations[0].p;
  6384.         };
  6385.  
  6386.         that.filterForward = function (index, count, predicate) {
  6387.             /// <summary>Filters the cache data based a predicate.</summary>
  6388.             /// <param name="index" type="Number">The index of the item to start filtering forward from.</param>
  6389.             /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
  6390.             /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
  6391.             /// <remarks>
  6392.             /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
  6393.             /// </remarks>
  6394.             /// <returns type="DjsDeferred">A promise for an array of results.</returns>
  6395.             return filter(index, count, predicate, false);
  6396.         };
  6397.  
  6398.         that.filterBack = function (index, count, predicate) {
  6399.             /// <summary>Filters the cache data based a predicate.</summary>
  6400.             /// <param name="index" type="Number">The index of the item to start filtering backward from.</param>
  6401.             /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
  6402.             /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
  6403.             /// <remarks>
  6404.             /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
  6405.             /// </remarks>
  6406.             /// <returns type="DjsDeferred">A promise for an array of results.</returns>
  6407.             return filter(index, count, predicate, true);
  6408.         };
  6409.  
  6410.         that.readRange = function (index, count) {
  6411.             /// <summary>Reads a range of adjacent records.</summary>
  6412.             /// <param name="index" type="Number">Zero-based index of record range to read.</param>
  6413.             /// <param name="count" type="Number">Number of records in the range.</param>
  6414.             /// <remarks>
  6415.             /// New read requests made while a clear operation is in progress will not be canceled.
  6416.             /// Instead they will be queued for execution once the operation is completed.
  6417.             /// </remarks>
  6418.             /// <returns type="DjsDeferred">
  6419.             /// A promise for an array of records; less records may be returned if the
  6420.             /// end of the collection is found.
  6421.             /// </returns>
  6422.  
  6423.             checkZeroGreater(index, "index");
  6424.             checkZeroGreater(count, "count");
  6425.  
  6426.             if (cacheFailure) {
  6427.                 throw cacheFailure;
  6428.             }
  6429.  
  6430.             var deferred = createDeferred();
  6431.  
  6432.             // Merging read operations would be a nice optimization here.
  6433.             var op = new DataCacheOperation(readStateMachine, deferred, true, index, count, [], 0);
  6434.             queueAndStart(op, readOperations);
  6435.  
  6436.             return extend(deferred.promise(), {
  6437.                 cancel: function () {
  6438.                     /// <summary>Aborts the readRange operation.</summary>
  6439.                     op.cancel();
  6440.                 }
  6441.             });
  6442.         };
  6443.  
  6444.         that.ToObservable = that.toObservable = function () {
  6445.             /// <summary>Creates an Observable object that enumerates all the cache contents.</summary>
  6446.             /// <returns>A new Observable object that enumerates all the cache contents.</returns>
  6447.             if (!window.Rx || !window.Rx.Observable) {
  6448.                 throw { message: "Rx library not available - include rx.js" };
  6449.             }
  6450.  
  6451.             if (cacheFailure) {
  6452.                 throw cacheFailure;
  6453.             }
  6454.  
  6455.             return window.Rx.Observable.CreateWithDisposable(function (obs) {
  6456.                 var disposed = false;
  6457.                 var index = 0;
  6458.  
  6459.                 var errorCallback = function (error) {
  6460.                     if (!disposed) {
  6461.                         obs.OnError(error);
  6462.                     }
  6463.                 };
  6464.  
  6465.                 var successCallback = function (data) {
  6466.                     if (!disposed) {
  6467.                         var i, len;
  6468.                         for (i = 0, len = data.length; i < len; i++) {
  6469.                             // The wrapper automatically checks for Dispose
  6470.                             // on the observer, so we don't need to check it here.
  6471.                             obs.OnNext(data[i]);
  6472.                         }
  6473.  
  6474.                         if (data.length < pageSize) {
  6475.                             obs.OnCompleted();
  6476.                         } else {
  6477.                             index += pageSize;
  6478.                             that.readRange(index, pageSize).then(successCallback, errorCallback);
  6479.                         }
  6480.                     }
  6481.                 };
  6482.  
  6483.                 that.readRange(index, pageSize).then(successCallback, errorCallback);
  6484.  
  6485.                 return { Dispose: function () { disposed = true; } };
  6486.             });
  6487.         };
  6488.  
  6489.         var cacheFailureCallback = function (message) {
  6490.             /// <summary>Creates a function that handles a callback by setting the cache into failure mode.</summary>
  6491.             /// <param name="message" type="String">Message text.</param>
  6492.             /// <returns type="Function">Function to use as error callback.</returns>
  6493.             /// <remarks>
  6494.             /// This function will specifically handle problems with critical store resources
  6495.             /// during cache initialization.
  6496.             /// </remarks>
  6497.  
  6498.             return function (error) {
  6499.                 cacheFailure = { message: message, error: error };
  6500.  
  6501.                 // Destroy any pending clear or read operations.
  6502.                 // At this point there should be no prefetch operations.
  6503.                 // Count operations will go through but are benign because they
  6504.                 // won't interact with the store.
  6505.                 var i, len;
  6506.                 for (i = 0, len = readOperations.length; i < len; i++) {
  6507.                     readOperations[i].fireRejected(cacheFailure);
  6508.                 }
  6509.                 for (i = 0, len = clearOperations.length; i < len; i++) {
  6510.                     clearOperations[i].fireRejected(cacheFailure);
  6511.                 }
  6512.  
  6513.                 // Null out the operation arrays.
  6514.                 readOperations = clearOperations = null;
  6515.             };
  6516.         };
  6517.  
  6518.         var changeState = function (newState) {
  6519.             /// <summary>Updates the cache's state and signals all pending operations of the change.</summary>
  6520.             /// <param name="newState" type="Object">New cache state.</param>
  6521.             /// <remarks>This method is a no-op if the cache's current state and the new state are the same.</remarks>
  6522.  
  6523.             if (newState !== state) {
  6524.                 state = newState;
  6525.                 var operations = clearOperations.concat(readOperations, prefetchOperations);
  6526.                 var i, len;
  6527.                 for (i = 0, len = operations.length; i < len; i++) {
  6528.                     operations[i].run(state);
  6529.                 }
  6530.             }
  6531.         };
  6532.  
  6533.         var clearStore = function () {
  6534.             /// <summary>Removes all the data stored in the cache.</summary>
  6535.             /// <returns type="DjsDeferred">A promise with no value.</returns>
  6536.  
  6537.             var deferred = new DjsDeferred();
  6538.             store.clear(function () {
  6539.  
  6540.                 // Reset the cache settings.
  6541.                 actualCacheSize = 0;
  6542.                 allDataLocal = false;
  6543.                 collectionCount = 0;
  6544.                 highestSavedPage = 0;
  6545.                 highestSavedPageSize = 0;
  6546.                 overflowed = cacheSize === 0;
  6547.  
  6548.                 // version is not reset, in case there is other state in eg V1.1 that is still around.
  6549.  
  6550.                 // Reset the cache stats.
  6551.                 stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
  6552.                 that.stats = stats;
  6553.  
  6554.                 store.close();
  6555.                 deferred.resolve();
  6556.             }, function (err) {
  6557.                 deferred.reject(err);
  6558.             });
  6559.             return deferred;
  6560.         };
  6561.  
  6562.         var dequeueOperation = function (operation) {
  6563.             /// <summary>Removes an operation from the caches queues and changes the cache state to idle.</summary>
  6564.             /// <param name="operation" type="DataCacheOperation">Operation to dequeue.</param>
  6565.             /// <remarks>This method is used as a handler for the operation's oncomplete event.</remarks>
  6566.  
  6567.             var removed = removeFromArray(clearOperations, operation);
  6568.             if (!removed) {
  6569.                 removed = removeFromArray(readOperations, operation);
  6570.                 if (!removed) {
  6571.                     removeFromArray(prefetchOperations, operation);
  6572.                 }
  6573.             }
  6574.  
  6575.             pendingOperations--;
  6576.             changeState(CACHE_STATE_IDLE);
  6577.         };
  6578.  
  6579.         var fetchPage = function (start) {
  6580.             /// <summary>Requests data from the cache source.</summary>
  6581.             /// <param name="start" type="Number">Zero-based index of items to request.</param>
  6582.             /// <returns type="DjsDeferred">A promise for a page object with (i)ndex, (c)ount, (d)ata.</returns>
  6583.  
  6584.  
  6585.             var deferred = new DjsDeferred();
  6586.             var canceled = false;
  6587.  
  6588.             var request = source.read(start, pageSize, function (data) {
  6589.                 var page = { i: start, c: data.length, d: data };
  6590.                 deferred.resolve(page);
  6591.             }, function (err) {
  6592.                 deferred.reject(err);
  6593.             });
  6594.  
  6595.             return extend(deferred, {
  6596.                 cancel: function () {
  6597.                     if (request) {
  6598.                         request.abort();
  6599.                         canceled = true;
  6600.                         request = null;
  6601.                     }
  6602.                 }
  6603.             });
  6604.         };
  6605.  
  6606.         var filter = function (index, count, predicate, backwards) {
  6607.             /// <summary>Filters the cache data based a predicate.</summary>
  6608.             /// <param name="index" type="Number">The index of the item to start filtering from.</param>
  6609.             /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
  6610.             /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
  6611.             /// <param name="backwards" type="Boolean">True if the filtering should move backward from the specified index, falsey otherwise.</param>
  6612.             /// <remarks>
  6613.             /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
  6614.             /// </remarks>
  6615.             /// <returns type="DjsDeferred">A promise for an array of results.</returns>
  6616.             index = parseInt10(index);
  6617.             count = parseInt10(count);
  6618.  
  6619.             if (isNaN(index)) {
  6620.                 throw { message: "'index' must be a valid number.", index: index };
  6621.             }
  6622.             if (isNaN(count)) {
  6623.                 throw { message: "'count' must be a valid number.", count: count };
  6624.             }
  6625.  
  6626.             if (cacheFailure) {
  6627.                 throw cacheFailure;
  6628.             }
  6629.  
  6630.             index = Math.max(index, 0);
  6631.  
  6632.             var deferred = createDeferred();
  6633.             var arr = [];
  6634.             var canceled = false;
  6635.             var pendingReadRange = null;
  6636.  
  6637.             var readMore = function (readIndex, readCount) {
  6638.                 if (!canceled) {
  6639.                     if (count >= 0 && arr.length >= count) {
  6640.                         deferred.resolve(arr);
  6641.                     } else {
  6642.                         pendingReadRange = that.readRange(readIndex, readCount).then(function (data) {
  6643.                             for (var i = 0, length = data.length; i < length && (count < 0 || arr.length < count); i++) {
  6644.                                 var dataIndex = backwards ? length - i - 1 : i;
  6645.                                 var item = data[dataIndex];
  6646.                                 if (predicate(item)) {
  6647.                                     var element = {
  6648.                                         index: readIndex + dataIndex,
  6649.                                         item: item
  6650.                                     };
  6651.  
  6652.                                     backwards ? arr.unshift(element) : arr.push(element);
  6653.                                 }
  6654.                             }
  6655.  
  6656.                             // Have we reached the end of the collection?
  6657.                             if ((!backwards && data.length < readCount) || (backwards && readIndex <= 0)) {
  6658.                                 deferred.resolve(arr);
  6659.                             } else {
  6660.                                 var nextIndex = backwards ? Math.max(readIndex - pageSize, 0) : readIndex + readCount;
  6661.                                 readMore(nextIndex, pageSize);
  6662.                             }
  6663.                         }, function (err) {
  6664.                             deferred.reject(err);
  6665.                         });
  6666.                     }
  6667.                 }
  6668.             };
  6669.  
  6670.             // Initially, we read from the given starting index to the next/previous page boundary
  6671.             var initialPage = snapToPageBoundaries(index, index, pageSize);
  6672.             var initialIndex = backwards ? initialPage.i : index;
  6673.             var initialCount = backwards ? index - initialPage.i + 1 : initialPage.i + initialPage.c - index;
  6674.             readMore(initialIndex, initialCount);
  6675.  
  6676.             return extend(deferred.promise(), {
  6677.                 cancel: function () {
  6678.                     /// <summary>Aborts the filter operation</summary>
  6679.                     if (pendingReadRange) {
  6680.                         pendingReadRange.cancel();
  6681.                     }
  6682.                     canceled = true;
  6683.                 }
  6684.             });
  6685.         };
  6686.  
  6687.         var fireOnIdle = function () {
  6688.             /// <summary>Fires an onidle event if any functions are assigned.</summary>
  6689.  
  6690.             if (that.onidle && pendingOperations === 0) {
  6691.                 that.onidle();
  6692.             }
  6693.         };
  6694.  
  6695.         var prefetch = function (start) {
  6696.             /// <summary>Creates and starts a new prefetch operation.</summary>
  6697.             /// <param name="start" type="Number">Zero-based index of the items to prefetch.</param>
  6698.             /// <remarks>
  6699.             /// This method is a no-op if any of the following conditions is true:
  6700.             ///     1.- prefetchSize is 0
  6701.             ///     2.- All data has been read and stored locally in the cache.
  6702.             ///     3.- There is already an all data prefetch operation queued.
  6703.             ///     4.- The cache has run out of available space (overflowed).
  6704.             /// <remarks>
  6705.  
  6706.             if (allDataLocal || prefetchSize === 0 || overflowed) {
  6707.                 return;
  6708.             }
  6709.  
  6710.  
  6711.             if (prefetchOperations.length === 0 || (prefetchOperations[0] && prefetchOperations[0].c !== -1)) {
  6712.                 // Merging prefetch operations would be a nice optimization here.
  6713.                 var op = new DataCacheOperation(prefetchStateMachine, null, true, start, prefetchSize, null, prefetchSize);
  6714.                 queueAndStart(op, prefetchOperations);
  6715.             }
  6716.         };
  6717.  
  6718.         var queueAndStart = function (op, queue) {
  6719.             /// <summary>Queues an operation and runs it.</summary>
  6720.             /// <param name="op" type="DataCacheOperation">Operation to queue.</param>
  6721.             /// <param name="queue" type="Array">Array that will store the operation.</param>
  6722.  
  6723.             op.oncomplete = dequeueOperation;
  6724.             queue.push(op);
  6725.             pendingOperations++;
  6726.             op.run(state);
  6727.         };
  6728.  
  6729.         var readPage = function (key) {
  6730.             /// <summary>Requests a page from the cache local store.</summary>
  6731.             /// <param name="key" type="Number">Zero-based index of the reuqested page.</param>
  6732.             /// <returns type="DjsDeferred">A promise for a found flag and page object with (i)ndex, (c)ount, (d)ata, and (t)icks.</returns>
  6733.  
  6734.  
  6735.             var canceled = false;
  6736.             var deferred = extend(new DjsDeferred(), {
  6737.                 cancel: function () {
  6738.                     /// <summary>Aborts the readPage operation.</summary>
  6739.                     canceled = true;
  6740.                 }
  6741.             });
  6742.  
  6743.             var error = storeFailureCallback(deferred, "Read page from store failure");
  6744.  
  6745.             store.contains(key, function (contained) {
  6746.                 if (canceled) {
  6747.                     return;
  6748.                 }
  6749.                 if (contained) {
  6750.                     store.read(key, function (_, data) {
  6751.                         if (!canceled) {
  6752.                             deferred.resolve(data !== undefined, data);
  6753.                         }
  6754.                     }, error);
  6755.                     return;
  6756.                 }
  6757.                 deferred.resolve(false);
  6758.             }, error);
  6759.             return deferred;
  6760.         };
  6761.  
  6762.         var savePage = function (key, page) {
  6763.             /// <summary>Saves a page to the cache local store.</summary>
  6764.             /// <param name="key" type="Number">Zero-based index of the requested page.</param>
  6765.             /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata, and (t)icks.</param>
  6766.             /// <returns type="DjsDeferred">A promise with no value.</returns>
  6767.  
  6768.  
  6769.             var canceled = false;
  6770.  
  6771.             var deferred = extend(new DjsDeferred(), {
  6772.                 cancel: function () {
  6773.                     /// <summary>Aborts the readPage operation.</summary>
  6774.                     canceled = true;
  6775.                 }
  6776.             });
  6777.  
  6778.             var error = storeFailureCallback(deferred, "Save page to store failure");
  6779.  
  6780.             var resolve = function () {
  6781.                 deferred.resolve(true);
  6782.             };
  6783.  
  6784.             if (page.c > 0) {
  6785.                 var pageBytes = estimateSize(page);
  6786.                 overflowed = cacheSize >= 0 && cacheSize < actualCacheSize + pageBytes;
  6787.  
  6788.                 if (!overflowed) {
  6789.                     store.addOrUpdate(key, page, function () {
  6790.                         updateSettings(page, pageBytes);
  6791.                         saveSettings(resolve, error);
  6792.                     }, error);
  6793.                 } else {
  6794.                     resolve();
  6795.                 }
  6796.             } else {
  6797.                 updateSettings(page, 0);
  6798.                 saveSettings(resolve, error);
  6799.             }
  6800.             return deferred;
  6801.         };
  6802.  
  6803.         var saveSettings = function (success, error) {
  6804.             /// <summary>Saves the cache's current settings to the local store.</summary>
  6805.             /// <param name="success" type="Function">Success callback.</param>
  6806.             /// <param name="error" type="Function">Errror callback.</param>
  6807.  
  6808.             var settings = {
  6809.                 actualCacheSize: actualCacheSize,
  6810.                 allDataLocal: allDataLocal,
  6811.                 cacheSize: cacheSize,
  6812.                 collectionCount: collectionCount,
  6813.                 highestSavedPage: highestSavedPage,
  6814.                 highestSavedPageSize: highestSavedPageSize,
  6815.                 pageSize: pageSize,
  6816.                 sourceId: source.identifier,
  6817.                 version: version
  6818.             };
  6819.  
  6820.             store.addOrUpdate("__settings", settings, success, error);
  6821.         };
  6822.  
  6823.         var storeFailureCallback = function (deferred/*, message*/) {
  6824.             /// <summary>Creates a function that handles a store error.</summary>
  6825.             /// <param name="deferred" type="DjsDeferred">Deferred object to resolve.</param>
  6826.             /// <param name="message" type="String">Message text.</param>
  6827.             /// <returns type="Function">Function to use as error callback.</returns>
  6828.             /// <remarks>
  6829.             /// This function will specifically handle problems when interacting with the store.
  6830.             /// </remarks>
  6831.  
  6832.             return function (/*error*/) {
  6833.                 // var console = window.console;
  6834.                 // if (console && console.log) {
  6835.                 //    console.log(message);
  6836.                 //    console.dir(error);
  6837.                 // }
  6838.                 deferred.resolve(false);
  6839.             };
  6840.         };
  6841.  
  6842.         var updateSettings = function (page, pageBytes) {
  6843.             /// <summary>Updates the cache's settings based on a page object.</summary>
  6844.             /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata.</param>
  6845.             /// <param name="pageBytes" type="Number">Size of the page in bytes.</param>
  6846.  
  6847.             var pageCount = page.c;
  6848.             var pageIndex = page.i;
  6849.  
  6850.             // Detect the collection size.
  6851.             if (pageCount === 0) {
  6852.                 if (highestSavedPage === pageIndex - pageSize) {
  6853.                     collectionCount = highestSavedPage + highestSavedPageSize;
  6854.                 }
  6855.             } else {
  6856.                 highestSavedPage = Math.max(highestSavedPage, pageIndex);
  6857.                 if (highestSavedPage === pageIndex) {
  6858.                     highestSavedPageSize = pageCount;
  6859.                 }
  6860.                 actualCacheSize += pageBytes;
  6861.                 if (pageCount < pageSize && !collectionCount) {
  6862.                     collectionCount = pageIndex + pageCount;
  6863.                 }
  6864.             }
  6865.  
  6866.             // Detect the end of the collection.
  6867.             if (!allDataLocal && collectionCount === highestSavedPage + highestSavedPageSize) {
  6868.                 allDataLocal = true;
  6869.             }
  6870.         };
  6871.  
  6872.         var cancelStateMachine = function (operation, opTargetState, cacheState, data) {
  6873.             /// <summary>State machine describing the behavior for cancelling a read or prefetch operation.</summary>
  6874.             /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
  6875.             /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
  6876.             /// <param name="cacheState" type="Object">Current cache state.</param>
  6877.             /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
  6878.             /// <remarks>
  6879.             /// This state machine contains behavior common to read and prefetch operations.
  6880.             /// </remarks>
  6881.  
  6882.             var canceled = operation.canceled && opTargetState !== OPERATION_STATE_END;
  6883.             if (canceled) {
  6884.                 if (opTargetState === OPERATION_STATE_CANCEL) {
  6885.                     // Cancel state.
  6886.                     // Data is expected to be any pending request made to the cache.
  6887.                     if (data && data.cancel) {
  6888.                         data.cancel();
  6889.                     }
  6890.                 }
  6891.             }
  6892.             return canceled;
  6893.         };
  6894.  
  6895.         var destroyStateMachine = function (operation, opTargetState, cacheState) {
  6896.             /// <summary>State machine describing the behavior of a clear operation.</summary>
  6897.             /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
  6898.             /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
  6899.             /// <param name="cacheState" type="Object">Current cache state.</param>
  6900.             /// <remarks>
  6901.             /// Clear operations have the highest priority and can't be interrupted by other operations; however,
  6902.             /// they will preempt any other operation currently executing.
  6903.             /// </remarks>
  6904.  
  6905.             var transition = operation.transition;
  6906.  
  6907.             // Signal the cache that a clear operation is running.
  6908.             if (cacheState !== CACHE_STATE_DESTROY) {
  6909.                 changeState(CACHE_STATE_DESTROY);
  6910.                 return true;
  6911.             }
  6912.  
  6913.             switch (opTargetState) {
  6914.                 case OPERATION_STATE_START:
  6915.                     // Initial state of the operation.
  6916.                     transition(DESTROY_STATE_CLEAR);
  6917.                     break;
  6918.  
  6919.                 case OPERATION_STATE_END:
  6920.                     // State that signals the operation is done.
  6921.                     fireOnIdle();
  6922.                     break;
  6923.  
  6924.                 case DESTROY_STATE_CLEAR:
  6925.                     // State that clears all the local data of the cache.
  6926.                     clearStore().then(function () {
  6927.                         // Terminate the operation once the local store has been cleared.
  6928.                         operation.complete();
  6929.                     });
  6930.                     // Wait until the clear request completes.
  6931.                     operation.wait();
  6932.                     break;
  6933.  
  6934.                 default:
  6935.                     return false;
  6936.             }
  6937.             return true;
  6938.         };
  6939.  
  6940.         var prefetchStateMachine = function (operation, opTargetState, cacheState, data) {
  6941.             /// <summary>State machine describing the behavior of a prefetch operation.</summary>
  6942.             /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
  6943.             /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
  6944.             /// <param name="cacheState" type="Object">Current cache state.</param>
  6945.             /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
  6946.             /// <remarks>
  6947.             /// Prefetch operations have the lowest priority and will be interrupted by operations of
  6948.             /// other kinds. A preempted prefetch operation will resume its execution only when the state
  6949.             /// of the cache returns to idle.
  6950.             ///
  6951.             /// If a clear operation starts executing then all the prefetch operations are canceled,
  6952.             /// even if they haven't started executing yet.
  6953.             /// </remarks>
  6954.  
  6955.             // Handle cancelation
  6956.             if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
  6957.  
  6958.                 var transition = operation.transition;
  6959.  
  6960.                 // Handle preemption
  6961.                 if (cacheState !== CACHE_STATE_PREFETCH) {
  6962.                     if (cacheState === CACHE_STATE_DESTROY) {
  6963.                         if (opTargetState !== OPERATION_STATE_CANCEL) {
  6964.                             operation.cancel();
  6965.                         }
  6966.                     } else if (cacheState === CACHE_STATE_IDLE) {
  6967.                         // Signal the cache that a prefetch operation is running.
  6968.                         changeState(CACHE_STATE_PREFETCH);
  6969.                     }
  6970.                     return true;
  6971.                 }
  6972.  
  6973.                 switch (opTargetState) {
  6974.                     case OPERATION_STATE_START:
  6975.                         // Initial state of the operation.
  6976.                         if (prefetchOperations[0] === operation) {
  6977.                             transition(READ_STATE_LOCAL, operation.i);
  6978.                         }
  6979.                         break;
  6980.  
  6981.                     case READ_STATE_DONE:
  6982.                         // State that determines if the operation can be resolved or has to
  6983.                         // continue processing.
  6984.                         // Data is expected to be the read page.
  6985.                         var pending = operation.pending;
  6986.  
  6987.                         if (pending > 0) {
  6988.                             pending -= Math.min(pending, data.c);
  6989.                         }
  6990.  
  6991.                         // Are we done, or has all the data been stored?
  6992.                         if (allDataLocal || pending === 0 || data.c < pageSize || overflowed) {
  6993.                             operation.complete();
  6994.                         } else {
  6995.                             // Continue processing the operation.
  6996.                             operation.pending = pending;
  6997.                             transition(READ_STATE_LOCAL, data.i + pageSize);
  6998.                         }
  6999.                         break;
  7000.  
  7001.                     default:
  7002.                         return readSaveStateMachine(operation, opTargetState, cacheState, data, true);
  7003.                 }
  7004.             }
  7005.             return true;
  7006.         };
  7007.  
  7008.         var readStateMachine = function (operation, opTargetState, cacheState, data) {
  7009.             /// <summary>State machine describing the behavior of a read operation.</summary>
  7010.             /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
  7011.             /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
  7012.             /// <param name="cacheState" type="Object">Current cache state.</param>
  7013.             /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
  7014.             /// <remarks>
  7015.             /// Read operations have a higher priority than prefetch operations, but lower than
  7016.             /// clear operations. They will preempt any prefetch operation currently running
  7017.             /// but will be interrupted by a clear operation.
  7018.             ///
  7019.             /// If a clear operation starts executing then all the currently running
  7020.             /// read operations are canceled. Read operations that haven't started yet will
  7021.             /// wait in the start state until the destory operation finishes.
  7022.             /// </remarks>
  7023.  
  7024.             // Handle cancelation
  7025.             if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
  7026.  
  7027.                 var transition = operation.transition;
  7028.  
  7029.                 // Handle preemption
  7030.                 if (cacheState !== CACHE_STATE_READ && opTargetState !== OPERATION_STATE_START) {
  7031.                     if (cacheState === CACHE_STATE_DESTROY) {
  7032.                         if (opTargetState !== OPERATION_STATE_START) {
  7033.                             operation.cancel();
  7034.                         }
  7035.                     } else if (cacheState !== CACHE_STATE_WRITE) {
  7036.                         // Signal the cache that a read operation is running.
  7037.                         changeState(CACHE_STATE_READ);
  7038.                     }
  7039.  
  7040.                     return true;
  7041.                 }
  7042.  
  7043.                 switch (opTargetState) {
  7044.                     case OPERATION_STATE_START:
  7045.                         // Initial state of the operation.
  7046.                         // Wait until the cache is idle or prefetching.
  7047.                         if (cacheState === CACHE_STATE_IDLE || cacheState === CACHE_STATE_PREFETCH) {
  7048.                             // Signal the cache that a read operation is running.
  7049.                             changeState(CACHE_STATE_READ);
  7050.                             if (operation.c > 0) {
  7051.                                 // Snap the requested range to a page boundary.
  7052.                                 var range = snapToPageBoundaries(operation.i, operation.c, pageSize);
  7053.                                 transition(READ_STATE_LOCAL, range.i);
  7054.                             } else {
  7055.                                 transition(READ_STATE_DONE, operation);
  7056.                             }
  7057.                         }
  7058.                         break;
  7059.  
  7060.                     case READ_STATE_DONE:
  7061.                         // State that determines if the operation can be resolved or has to
  7062.                         // continue processing.
  7063.                         // Data is expected to be the read page.
  7064.                         appendPage(operation, data);
  7065.                         var len = operation.d.length;
  7066.                         // Are we done?
  7067.                         if (operation.c === len || data.c < pageSize) {
  7068.                             // Update the stats, request for a prefetch operation.
  7069.                             stats.cacheReads++;
  7070.                             prefetch(data.i + data.c);
  7071.                             // Terminate the operation.
  7072.                             operation.complete();
  7073.                         } else {
  7074.                             // Continue processing the operation.
  7075.                             transition(READ_STATE_LOCAL, data.i + pageSize);
  7076.                         }
  7077.                         break;
  7078.  
  7079.                     default:
  7080.                         return readSaveStateMachine(operation, opTargetState, cacheState, data, false);
  7081.                 }
  7082.             }
  7083.  
  7084.             return true;
  7085.         };
  7086.  
  7087.         var readSaveStateMachine = function (operation, opTargetState, cacheState, data, isPrefetch) {
  7088.             /// <summary>State machine describing the behavior for reading and saving data into the cache.</summary>
  7089.             /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
  7090.             /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
  7091.             /// <param name="cacheState" type="Object">Current cache state.</param>
  7092.             /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
  7093.             /// <param name="isPrefetch" type="Boolean">Flag indicating whether a read (false) or prefetch (true) operation is running.
  7094.             /// <remarks>
  7095.             /// This state machine contains behavior common to read and prefetch operations.
  7096.             /// </remarks>
  7097.  
  7098.             var error = operation.error;
  7099.             var transition = operation.transition;
  7100.             var wait = operation.wait;
  7101.             var request;
  7102.  
  7103.             switch (opTargetState) {
  7104.                 case OPERATION_STATE_END:
  7105.                     // State that signals the operation is done.
  7106.                     fireOnIdle();
  7107.                     break;
  7108.  
  7109.                 case READ_STATE_LOCAL:
  7110.                     // State that requests for a page from the local store.
  7111.                     // Data is expected to be the index of the page to request.
  7112.                     request = readPage(data).then(function (found, page) {
  7113.                         // Signal the cache that a read operation is running.
  7114.                         if (!operation.canceled) {
  7115.                             if (found) {
  7116.                                 // The page is in the local store, check if the operation can be resolved.
  7117.                                 transition(READ_STATE_DONE, page);
  7118.                             } else {
  7119.                                 // The page is not in the local store, request it from the source.
  7120.                                 transition(READ_STATE_SOURCE, data);
  7121.                             }
  7122.                         }
  7123.                     });
  7124.                     break;
  7125.  
  7126.                 case READ_STATE_SOURCE:
  7127.                     // State that requests for a page from the cache source.
  7128.                     // Data is expected to be the index of the page to request.
  7129.                     request = fetchPage(data).then(function (page) {
  7130.                         // Signal the cache that a read operation is running.
  7131.                         if (!operation.canceled) {
  7132.                             // Update the stats and save the page to the local store.
  7133.                             if (isPrefetch) {
  7134.                                 stats.prefetches++;
  7135.                             } else {
  7136.                                 stats.netReads++;
  7137.                             }
  7138.                             transition(READ_STATE_SAVE, page);
  7139.                         }
  7140.                     }, error);
  7141.                     break;
  7142.  
  7143.                 case READ_STATE_SAVE:
  7144.                     // State that saves a  page to the local store.
  7145.                     // Data is expected to be the page to save.
  7146.                     // Write access to the store is exclusive.
  7147.                     if (cacheState !== CACHE_STATE_WRITE) {
  7148.                         changeState(CACHE_STATE_WRITE);
  7149.                         request = savePage(data.i, data).then(function (saved) {
  7150.                             if (!operation.canceled) {
  7151.                                 if (!saved && isPrefetch) {
  7152.                                     operation.pending = 0;
  7153.                                 }
  7154.                                 // Check if the operation can be resolved.
  7155.                                 transition(READ_STATE_DONE, data);
  7156.                             }
  7157.                             changeState(CACHE_STATE_IDLE);
  7158.                         });
  7159.                     }
  7160.                     break;
  7161.  
  7162.                 default:
  7163.                     // Unknown state that can't be handled by this state machine.
  7164.                     return false;
  7165.             }
  7166.  
  7167.             if (request) {
  7168.                 // The operation might have been canceled between stack frames do to the async calls.  
  7169.                 if (operation.canceled) {
  7170.                     request.cancel();
  7171.                 } else if (operation.s === opTargetState) {
  7172.                     // Wait for the request to complete.
  7173.                     wait(request);
  7174.                 }
  7175.             }
  7176.  
  7177.             return true;
  7178.         };
  7179.  
  7180.         // Initialize the cache.
  7181.         store.read("__settings", function (_, settings) {
  7182.             if (assigned(settings)) {
  7183.                 var settingsVersion = settings.version;
  7184.                 if (!settingsVersion || settingsVersion.indexOf("1.") !== 0) {
  7185.                     cacheFailureCallback("Unsupported cache store version " + settingsVersion)();
  7186.                     return;
  7187.                 }
  7188.  
  7189.                 if (pageSize !== settings.pageSize || source.identifier !== settings.sourceId) {
  7190.                     // The shape or the source of the data was changed so invalidate the store.
  7191.                     clearStore().then(function () {
  7192.                         // Signal the cache is fully initialized.
  7193.                         changeState(CACHE_STATE_IDLE);
  7194.                     }, cacheFailureCallback("Unable to clear store during initialization"));
  7195.                 } else {
  7196.                     // Restore the saved settings.
  7197.                     actualCacheSize = settings.actualCacheSize;
  7198.                     allDataLocal = settings.allDataLocal;
  7199.                     cacheSize = settings.cacheSize;
  7200.                     collectionCount = settings.collectionCount;
  7201.                     highestSavedPage = settings.highestSavedPage;
  7202.                     highestSavedPageSize = settings.highestSavedPageSize;
  7203.                     version = settingsVersion;
  7204.  
  7205.                     // Signal the cache is fully initialized.
  7206.                     changeState(CACHE_STATE_IDLE);
  7207.                 }
  7208.             } else {
  7209.                 // This is a brand new cache.
  7210.                 saveSettings(function () {
  7211.                     // Signal the cache is fully initialized.
  7212.                     changeState(CACHE_STATE_IDLE);
  7213.                 }, cacheFailureCallback("Unable to write settings during initialization."));
  7214.             }
  7215.         }, cacheFailureCallback("Unable to read settings from store."));
  7216.  
  7217.         return that;
  7218.     };
  7219.  
  7220.     datajs.createDataCache = function (options) {
  7221.         /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary>
  7222.         /// <param name="options">
  7223.         /// Options for the data cache, including name, source, pageSize,
  7224.         /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
  7225.         /// </param>
  7226.         /// <returns type="DataCache">A new data cache instance.</returns>
  7227.         checkUndefinedGreaterThanZero(options.pageSize, "pageSize");
  7228.         checkUndefinedOrNumber(options.cacheSize, "cacheSize");
  7229.         checkUndefinedOrNumber(options.prefetchSize, "prefetchSize");
  7230.  
  7231.         if (!assigned(options.name)) {
  7232.             throw { message: "Undefined or null name", options: options };
  7233.         }
  7234.  
  7235.         if (!assigned(options.source)) {
  7236.             throw { message: "Undefined source", options: options };
  7237.         }
  7238.  
  7239.         return new DataCache(options);
  7240.     };
  7241.  
  7242.  
  7243.  
  7244. })(this);
Add Comment
Please, Sign In to add comment