· 9 years ago · Apr 25, 2017, 07:22 AM
1// Copyright (c) Microsoft Open Technologies, Inc. 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
18 var datajs = window.datajs || {};
19 var odata = window.OData || {};
20
21 // AMD support
22 if (typeof define === 'function' && define.amd) {
23 define('datajs', datajs);
24 define('OData', odata);
25 } else {
26 window.datajs = datajs;
27 window.OData = odata;
28 }
29
30 datajs.version = {
31 major: 1,
32 minor: 1,
33 build: 1
34 };
35
36
37 var activeXObject = function (progId) {
38 /// <summary>Creates a new ActiveXObject from the given progId.</summary>
39 /// <param name="progId" type="String" mayBeNull="false" optional="false">
40 /// ProgId string of the desired ActiveXObject.
41 /// </param>
42 /// <remarks>
43 /// This function throws whatever exception might occur during the creation
44 /// of the ActiveXObject.
45 /// </remarks>
46 /// <returns type="Object">
47 /// The ActiveXObject instance. Null if ActiveX is not supported by the
48 /// browser.
49 /// </returns>
50 if (window.ActiveXObject) {
51 return new window.ActiveXObject(progId);
52 }
53 return null;
54 };
55
56 var assigned = function (value) {
57 /// <summary>Checks whether the specified value is different from null and undefined.</summary>
58 /// <param name="value" mayBeNull="true" optional="true">Value to check.</param>
59 /// <returns type="Boolean">true if the value is assigned; false otherwise.</returns>
60 return value !== null && value !== undefined;
61 };
62
63 var contains = function (arr, item) {
64 /// <summary>Checks whether the specified item is in the array.</summary>
65 /// <param name="arr" type="Array" optional="false" mayBeNull="false">Array to check in.</param>
66 /// <param name="item">Item to look for.</param>
67 /// <returns type="Boolean">true if the item is contained, false otherwise.</returns>
68
69 var i, len;
70 for (i = 0, len = arr.length; i < len; i++) {
71 if (arr[i] === item) {
72 return true;
73 }
74 }
75
76 return false;
77 };
78
79 var defined = function (a, b) {
80 /// <summary>Given two values, picks the first one that is not undefined.</summary>
81 /// <param name="a">First value.</param>
82 /// <param name="b">Second value.</param>
83 /// <returns>a if it's a defined value; else b.</returns>
84 return (a !== undefined) ? a : b;
85 };
86
87 var delay = function (callback) {
88 /// <summary>Delays the invocation of the specified function until execution unwinds.</summary>
89 /// <param name="callback" type="Function">Callback function.</param>
90 if (arguments.length === 1) {
91 window.setTimeout(callback, 0);
92 return;
93 }
94
95 var args = Array.prototype.slice.call(arguments, 1);
96 window.setTimeout(function () {
97 callback.apply(this, args);
98 }, 0);
99 };
100
101
102 var extend = function (target, values) {
103 /// <summary>Extends the target with the specified values.</summary>
104 /// <param name="target" type="Object">Object to add properties to.</param>
105 /// <param name="values" type="Object">Object with properties to add into target.</param>
106 /// <returns type="Object">The target object.</returns>
107
108 for (var name in values) {
109 target[name] = values[name];
110 }
111
112 return target;
113 };
114
115 var find = function (arr, callback) {
116 /// <summary>Returns the first item in the array that makes the callback function true.</summary>
117 /// <param name="arr" type="Array" optional="false" mayBeNull="true">Array to check in.</param>
118 /// <param name="callback" type="Function">Callback function to invoke once per item in the array.</param>
119 /// <returns>The first item that makes the callback return true; null otherwise or if the array is null.</returns>
120
121 if (arr) {
122 var i, len;
123 for (i = 0, len = arr.length; i < len; i++) {
124 if (callback(arr[i])) {
125 return arr[i];
126 }
127 }
128 }
129 return null;
130 };
131
132 var isArray = function (value) {
133 /// <summary>Checks whether the specified value is an array object.</summary>
134 /// <param name="value">Value to check.</param>
135 /// <returns type="Boolean">true if the value is an array object; false otherwise.</returns>
136
137 return Object.prototype.toString.call(value) === "[object Array]";
138 };
139
140 var isDate = function (value) {
141 /// <summary>Checks whether the specified value is a Date object.</summary>
142 /// <param name="value">Value to check.</param>
143 /// <returns type="Boolean">true if the value is a Date object; false otherwise.</returns>
144
145 return Object.prototype.toString.call(value) === "[object Date]";
146 };
147
148 var isObject = function (value) {
149 /// <summary>Tests whether a value is an object.</summary>
150 /// <param name="value">Value to test.</param>
151 /// <remarks>
152 /// Per javascript rules, null and array values are objects and will cause this function to return true.
153 /// </remarks>
154 /// <returns type="Boolean">True is the value is an object; false otherwise.</returns>
155
156 return typeof value === "object";
157 };
158
159 var parseInt10 = function (value) {
160 /// <summary>Parses a value in base 10.</summary>
161 /// <param name="value" type="String">String value to parse.</param>
162 /// <returns type="Number">The parsed value, NaN if not a valid value.</returns>
163
164 return parseInt(value, 10);
165 };
166
167 var renameProperty = function (obj, oldName, newName) {
168 /// <summary>Renames a property in an object.</summary>
169 /// <param name="obj" type="Object">Object in which the property will be renamed.</param>
170 /// <param name="oldName" type="String">Name of the property that will be renamed.</param>
171 /// <param name="newName" type="String">New name of the property.</param>
172 /// <remarks>
173 /// This function will not do anything if the object doesn't own a property with the specified old name.
174 /// </remarks>
175
176 if (obj.hasOwnProperty(oldName)) {
177 obj[newName] = obj[oldName];
178 delete obj[oldName];
179 }
180 };
181
182 var throwErrorCallback = function (error) {
183 /// <summary>Default error handler.</summary>
184 /// <param name="error" type="Object">Error to handle.</param>
185 throw error;
186 };
187
188 var trimString = function (str) {
189 /// <summary>Removes leading and trailing whitespaces from a string.</summary>
190 /// <param name="str" type="String" optional="false" mayBeNull="false">String to trim</param>
191 /// <returns type="String">The string with no leading or trailing whitespace.</returns>
192
193 if (str.trim) {
194 return str.trim();
195 }
196
197 return str.replace(/^\s+|\s+$/g, '');
198 };
199
200 var undefinedDefault = function (value, defaultValue) {
201 /// <summary>Returns a default value in place of undefined.</summary>
202 /// <param name="value" mayBeNull="true" optional="true">Value to check.</param>
203 /// <param name="defaultValue">Value to return if value is undefined.</param>
204 /// <returns>value if it's defined; defaultValue otherwise.</returns>
205 /// <remarks>
206 /// This should only be used for cases where falsy values are valid;
207 /// otherwise the pattern should be 'x = (value) ? value : defaultValue;'.
208 /// </remarks>
209 return (value !== undefined) ? value : defaultValue;
210 };
211
212 // Regular expression that splits a uri into its components:
213 // 0 - is the matched string.
214 // 1 - is the scheme.
215 // 2 - is the authority.
216 // 3 - is the path.
217 // 4 - is the query.
218 // 5 - is the fragment.
219 var uriRegEx = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/;
220 var uriPartNames = ["scheme", "authority", "path", "query", "fragment"];
221
222 var getURIInfo = function (uri) {
223 /// <summary>Gets information about the components of the specified URI.</summary>
224 /// <param name="uri" type="String">URI to get information from.</param>
225 /// <returns type="Object">
226 /// An object with an isAbsolute flag and part names (scheme, authority, etc.) if available.
227 /// </returns>
228
229 var result = { isAbsolute: false };
230
231 if (uri) {
232 var matches = uriRegEx.exec(uri);
233 if (matches) {
234 var i, len;
235 for (i = 0, len = uriPartNames.length; i < len; i++) {
236 if (matches[i + 1]) {
237 result[uriPartNames[i]] = matches[i + 1];
238 }
239 }
240 }
241 if (result.scheme) {
242 result.isAbsolute = true;
243 }
244 }
245
246 return result;
247 };
248
249 var getURIFromInfo = function (uriInfo) {
250 /// <summary>Builds a URI string from its components.</summary>
251 /// <param name="uriInfo" type="Object"> An object with uri parts (scheme, authority, etc.).</param>
252 /// <returns type="String">URI string.</returns>
253
254 return "".concat(
255 uriInfo.scheme || "",
256 uriInfo.authority || "",
257 uriInfo.path || "",
258 uriInfo.query || "",
259 uriInfo.fragment || "");
260 };
261
262 // Regular expression that splits a uri authority into its subcomponents:
263 // 0 - is the matched string.
264 // 1 - is the userinfo subcomponent.
265 // 2 - is the host subcomponent.
266 // 3 - is the port component.
267 var uriAuthorityRegEx = /^\/{0,2}(?:([^@]*)@)?([^:]+)(?::{1}(\d+))?/;
268
269 // Regular expression that matches percentage enconded octects (i.e %20 or %3A);
270 var pctEncodingRegEx = /%[0-9A-F]{2}/ig;
271
272 var normalizeURICase = function (uri) {
273 /// <summary>Normalizes the casing of a URI.</summary>
274 /// <param name="uri" type="String">URI to normalize, absolute or relative.</param>
275 /// <returns type="String">The URI normalized to lower case.</returns>
276
277 var uriInfo = getURIInfo(uri);
278 var scheme = uriInfo.scheme;
279 var authority = uriInfo.authority;
280
281 if (scheme) {
282 uriInfo.scheme = scheme.toLowerCase();
283 if (authority) {
284 var matches = uriAuthorityRegEx.exec(authority);
285 if (matches) {
286 uriInfo.authority = "//" +
287 (matches[1] ? matches[1] + "@" : "") +
288 (matches[2].toLowerCase()) +
289 (matches[3] ? ":" + matches[3] : "");
290 }
291 }
292 }
293
294 uri = getURIFromInfo(uriInfo);
295
296 return uri.replace(pctEncodingRegEx, function (str) {
297 return str.toLowerCase();
298 });
299 };
300
301 var normalizeURI = function (uri, base) {
302 /// <summary>Normalizes a possibly relative URI with a base URI.</summary>
303 /// <param name="uri" type="String">URI to normalize, absolute or relative.</param>
304 /// <param name="base" type="String" mayBeNull="true">Base URI to compose with.</param>
305 /// <returns type="String">The composed URI if relative; the original one if absolute.</returns>
306
307 if (!base) {
308 return uri;
309 }
310
311 var uriInfo = getURIInfo(uri);
312 if (uriInfo.isAbsolute) {
313 return uri;
314 }
315
316 var baseInfo = getURIInfo(base);
317 var normInfo = {};
318 var path;
319
320 if (uriInfo.authority) {
321 normInfo.authority = uriInfo.authority;
322 path = uriInfo.path;
323 normInfo.query = uriInfo.query;
324 } else {
325 if (!uriInfo.path) {
326 path = baseInfo.path;
327 normInfo.query = uriInfo.query || baseInfo.query;
328 } else {
329 if (uriInfo.path.charAt(0) === '/') {
330 path = uriInfo.path;
331 } else {
332 path = mergeUriPathWithBase(uriInfo.path, baseInfo.path);
333 }
334 normInfo.query = uriInfo.query;
335 }
336 normInfo.authority = baseInfo.authority;
337 }
338
339 normInfo.path = removeDotsFromPath(path);
340
341 normInfo.scheme = baseInfo.scheme;
342 normInfo.fragment = uriInfo.fragment;
343
344 return getURIFromInfo(normInfo);
345 };
346
347 var mergeUriPathWithBase = function (uriPath, basePath) {
348 /// <summary>Merges the path of a relative URI and a base URI.</summary>
349 /// <param name="uriPath" type="String>Relative URI path.</param>
350 /// <param name="basePath" type="String">Base URI path.</param>
351 /// <returns type="String">A string with the merged path.</returns>
352
353 var path = "/";
354 var end;
355
356 if (basePath) {
357 end = basePath.lastIndexOf("/");
358 path = basePath.substring(0, end);
359
360 if (path.charAt(path.length - 1) !== "/") {
361 path = path + "/";
362 }
363 }
364
365 return path + uriPath;
366 };
367
368 var removeDotsFromPath = function (path) {
369 /// <summary>Removes the special folders . and .. from a URI's path.</summary>
370 /// <param name="path" type="string">URI path component.</param>
371 /// <returns type="String">Path without any . and .. folders.</returns>
372
373 var result = "";
374 var segment = "";
375 var end;
376
377 while (path) {
378 if (path.indexOf("..") === 0 || path.indexOf(".") === 0) {
379 path = path.replace(/^\.\.?\/?/g, "");
380 } else if (path.indexOf("/..") === 0) {
381 path = path.replace(/^\/\..\/?/g, "/");
382 end = result.lastIndexOf("/");
383 if (end === -1) {
384 result = "";
385 } else {
386 result = result.substring(0, end);
387 }
388 } else if (path.indexOf("/.") === 0) {
389 path = path.replace(/^\/\.\/?/g, "/");
390 } else {
391 segment = path;
392 end = path.indexOf("/", 1);
393 if (end !== -1) {
394 segment = path.substring(0, end);
395 }
396 result = result + segment;
397 path = path.replace(segment, "");
398 }
399 }
400 return result;
401 };
402
403 var convertByteArrayToHexString = function (str) {
404 var arr = [];
405 if (window.atob === undefined) {
406 arr = decodeBase64(str);
407 } else {
408 var binaryStr = window.atob(str);
409 for (var i = 0; i < binaryStr.length; i++) {
410 arr.push(binaryStr.charCodeAt(i));
411 }
412 }
413 var hexValue = "";
414 var hexValues = "0123456789ABCDEF";
415 for (var j = 0; j < arr.length; j++) {
416 var t = arr[j];
417 hexValue += hexValues[t >> 4];
418 hexValue += hexValues[t & 0x0F];
419 }
420 return hexValue;
421 };
422
423 var decodeBase64 = function (str) {
424 var binaryString = "";
425 for (var i = 0; i < str.length; i++) {
426 var base65IndexValue = getBase64IndexValue(str[i]);
427 var binaryValue = "";
428 if (base65IndexValue !== null) {
429 binaryValue = base65IndexValue.toString(2);
430 binaryString += addBase64Padding(binaryValue);
431 }
432 }
433 var byteArray = [];
434 var numberOfBytes = parseInt(binaryString.length / 8, 10);
435 for (i = 0; i < numberOfBytes; i++) {
436 var intValue = parseInt(binaryString.substring(i * 8, (i + 1) * 8), 2);
437 byteArray.push(intValue);
438 }
439 return byteArray;
440 };
441
442 var getBase64IndexValue = function (character) {
443 var asciiCode = character.charCodeAt(0);
444 var asciiOfA = 65;
445 var differenceBetweenZanda = 6;
446 if (asciiCode >= 65 && asciiCode <= 90) { // between "A" and "Z" inclusive
447 return asciiCode - asciiOfA;
448 } else if (asciiCode >= 97 && asciiCode <= 122) { // between 'a' and 'z' inclusive
449 return asciiCode - asciiOfA - differenceBetweenZanda;
450 } else if (asciiCode >= 48 && asciiCode <= 57) { // between '0' and '9' inclusive
451 return asciiCode + 4;
452 } else if (character == "+") {
453 return 62;
454 } else if (character == "/") {
455 return 63;
456 } else {
457 return null;
458 }
459 };
460
461 var addBase64Padding = function (binaryString) {
462 while (binaryString.length < 6) {
463 binaryString = "0" + binaryString;
464 }
465 return binaryString;
466 };
467
468
469 // URI prefixes to generate smaller code.
470 var http = "http://";
471 var w3org = http + "www.w3.org/"; // http://www.w3.org/
472
473 var xhtmlNS = w3org + "1999/xhtml"; // http://www.w3.org/1999/xhtml
474 var xmlnsNS = w3org + "2000/xmlns/"; // http://www.w3.org/2000/xmlns/
475 var xmlNS = w3org + "XML/1998/namespace"; // http://www.w3.org/XML/1998/namespace
476
477 var mozillaParserErroNS = http + "www.mozilla.org/newlayout/xml/parsererror.xml";
478
479 var hasLeadingOrTrailingWhitespace = function (text) {
480 /// <summary>Checks whether the specified string has leading or trailing spaces.</summary>
481 /// <param name="text" type="String">String to check.</param>
482 /// <returns type="Boolean">true if text has any leading or trailing whitespace; false otherwise.</returns>
483
484 var re = /(^\s)|(\s$)/;
485 return re.test(text);
486 };
487
488 var isWhitespace = function (text) {
489 /// <summary>Determines whether the specified text is empty or whitespace.</summary>
490 /// <param name="text" type="String">Value to inspect.</param>
491 /// <returns type="Boolean">true if the text value is empty or all whitespace; false otherwise.</returns>
492
493 var ws = /^\s*$/;
494 return text === null || ws.test(text);
495 };
496
497 var isWhitespacePreserveContext = function (domElement) {
498 /// <summary>Determines whether the specified element has xml:space='preserve' applied.</summary>
499 /// <param name="domElement">Element to inspect.</param>
500 /// <returns type="Boolean">Whether xml:space='preserve' is in effect.</returns>
501
502 while (domElement !== null && domElement.nodeType === 1) {
503 var val = xmlAttributeValue(domElement, "space", xmlNS);
504 if (val === "preserve") {
505 return true;
506 } else if (val === "default") {
507 break;
508 } else {
509 domElement = domElement.parentNode;
510 }
511 }
512
513 return false;
514 };
515
516 var isXmlNSDeclaration = function (domAttribute) {
517 /// <summary>Determines whether the attribute is a XML namespace declaration.</summary>
518 /// <param name="domAttribute">Element to inspect.</param>
519 /// <returns type="Boolean">
520 /// True if the attribute is a namespace declaration (its name is 'xmlns' or starts with 'xmlns:'; false otherwise.
521 /// </returns>
522
523 var nodeName = domAttribute.nodeName;
524 return nodeName == "xmlns" || nodeName.indexOf("xmlns:") === 0;
525 };
526
527 var safeSetProperty = function (obj, name, value) {
528 /// <summary>Safely set as property in an object by invoking obj.setProperty.</summary>
529 /// <param name="obj">Object that exposes a setProperty method.</param>
530 /// <param name="name" type="String" mayBeNull="false">Property name.</param>
531 /// <param name="value">Property value.</param>
532
533 try {
534 obj.setProperty(name, value);
535 } catch (_) { }
536 };
537
538 var msXmlDom3 = function () {
539 /// <summary>Creates an configures new MSXML 3.0 ActiveX object.</summary>
540 /// <remakrs>
541 /// This function throws any exception that occurs during the creation
542 /// of the MSXML 3.0 ActiveX object.
543 /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns>
544
545 var msxml3 = activeXObject("Msxml2.DOMDocument.3.0");
546 if (msxml3) {
547 safeSetProperty(msxml3, "ProhibitDTD", true);
548 safeSetProperty(msxml3, "MaxElementDepth", 256);
549 safeSetProperty(msxml3, "AllowDocumentFunction", false);
550 safeSetProperty(msxml3, "AllowXsltScript", false);
551 }
552 return msxml3;
553 };
554
555 var msXmlDom = function () {
556 /// <summary>Creates an configures new MSXML 6.0 or MSXML 3.0 ActiveX object.</summary>
557 /// <remakrs>
558 /// This function will try to create a new MSXML 6.0 ActiveX object. If it fails then
559 /// it will fallback to create a new MSXML 3.0 ActiveX object. Any exception that
560 /// happens during the creation of the MSXML 6.0 will be handled by the function while
561 /// the ones that happend during the creation of the MSXML 3.0 will be thrown.
562 /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns>
563
564 try {
565 var msxml = activeXObject("Msxml2.DOMDocument.6.0");
566 if (msxml) {
567 msxml.async = true;
568 }
569 return msxml;
570 } catch (_) {
571 return msXmlDom3();
572 }
573 };
574
575 var msXmlParse = function (text) {
576 /// <summary>Parses an XML string using the MSXML DOM.</summary>
577 /// <remakrs>
578 /// This function throws any exception that occurs during the creation
579 /// of the MSXML ActiveX object. It also will throw an exception
580 /// in case of a parsing error.
581 /// <returns type="Object">New MSXML DOMDocument node representing the parsed XML string.</returns>
582
583 var dom = msXmlDom();
584 if (!dom) {
585 return null;
586 }
587
588 dom.loadXML(text);
589 var parseError = dom.parseError;
590 if (parseError.errorCode !== 0) {
591 xmlThrowParserError(parseError.reason, parseError.srcText, text);
592 }
593 return dom;
594 };
595
596 var xmlThrowParserError = function (exceptionOrReason, srcText, errorXmlText) {
597 /// <summary>Throws a new exception containing XML parsing error information.</summary>
598 /// <param name="exceptionOrReason">
599 /// String indicatin the reason of the parsing failure or
600 /// Object detailing the parsing error.
601 /// </param>
602 /// <param name="srcText" type="String">
603 /// String indicating the part of the XML string that caused the parsing error.
604 /// </param>
605 /// <param name="errorXmlText" type="String">XML string for wich the parsing failed.</param>
606
607 if (typeof exceptionOrReason === "string") {
608 exceptionOrReason = { message: exceptionOrReason };
609 }
610 throw extend(exceptionOrReason, { srcText: srcText || "", errorXmlText: errorXmlText || "" });
611 };
612
613 var xmlParse = function (text) {
614 /// <summary>Returns an XML DOM document from the specified text.</summary>
615 /// <param name="text" type="String">Document text.</param>
616 /// <returns>XML DOM document.</returns>
617 /// <remarks>This function will throw an exception in case of a parse error.</remarks>
618
619 var domParser = window.DOMParser && new window.DOMParser();
620 var dom;
621
622 if (!domParser) {
623 dom = msXmlParse(text);
624 if (!dom) {
625 xmlThrowParserError("XML DOM parser not supported");
626 }
627 return dom;
628 }
629
630 try {
631 dom = domParser.parseFromString(text, "text/xml");
632 } catch (e) {
633 xmlThrowParserError(e, "", text);
634 }
635
636 var element = dom.documentElement;
637 var nsURI = element.namespaceURI;
638 var localName = xmlLocalName(element);
639
640 // Firefox reports errors by returing the DOM for an xml document describing the problem.
641 if (localName === "parsererror" && nsURI === mozillaParserErroNS) {
642 var srcTextElement = xmlFirstChildElement(element, mozillaParserErroNS, "sourcetext");
643 var srcText = srcTextElement ? xmlNodeValue(srcTextElement) : "";
644 xmlThrowParserError(xmlInnerText(element) || "", srcText, text);
645 }
646
647 // Chrome (and maybe other webkit based browsers) report errors by injecting a header with an error message.
648 // The error may be localized, so instead we simply check for a header as the
649 // top element or descendant child of the document.
650 if (localName === "h3" && nsURI === xhtmlNS || xmlFirstDescendantElement(element, xhtmlNS, "h3")) {
651 var reason = "";
652 var siblings = [];
653 var cursor = element.firstChild;
654 while (cursor) {
655 if (cursor.nodeType === 1) {
656 reason += xmlInnerText(cursor) || "";
657 }
658 siblings.push(cursor.nextSibling);
659 cursor = cursor.firstChild || siblings.shift();
660 }
661 reason += xmlInnerText(element) || "";
662 xmlThrowParserError(reason, "", text);
663 }
664
665 return dom;
666 };
667
668 var xmlQualifiedName = function (prefix, name) {
669 /// <summary>Builds a XML qualified name string in the form of "prefix:name".</summary>
670 /// <param name="prefix" type="String" maybeNull="true">Prefix string.</param>
671 /// <param name="name" type="String">Name string to qualify with the prefix.</param>
672 /// <returns type="String">Qualified name.</returns>
673
674 return prefix ? prefix + ":" + name : name;
675 };
676
677 var xmlAppendText = function (domNode, textNode) {
678 /// <summary>Appends a text node into the specified DOM element node.</summary>
679 /// <param name="domNode">DOM node for the element.</param>
680 /// <param name="text" type="String" mayBeNull="false">Text to append as a child of element.</param>
681 if (hasLeadingOrTrailingWhitespace(textNode.data)) {
682 var attr = xmlAttributeNode(domNode, xmlNS, "space");
683 if (!attr) {
684 attr = xmlNewAttribute(domNode.ownerDocument, xmlNS, xmlQualifiedName("xml", "space"));
685 xmlAppendChild(domNode, attr);
686 }
687 attr.value = "preserve";
688 }
689 domNode.appendChild(textNode);
690 return domNode;
691 };
692
693 var xmlAttributes = function (element, onAttributeCallback) {
694 /// <summary>Iterates through the XML element's attributes and invokes the callback function for each one.</summary>
695 /// <param name="element">Wrapped element to iterate over.</param>
696 /// <param name="onAttributeCallback" type="Function">Callback function to invoke with wrapped attribute nodes.</param>
697
698 var attributes = element.attributes;
699 var i, len;
700 for (i = 0, len = attributes.length; i < len; i++) {
701 onAttributeCallback(attributes.item(i));
702 }
703 };
704
705 var xmlAttributeValue = function (domNode, localName, nsURI) {
706 /// <summary>Returns the value of a DOM element's attribute.</summary>
707 /// <param name="domNode">DOM node for the owning element.</param>
708 /// <param name="localName" type="String">Local name of the attribute.</param>
709 /// <param name="nsURI" type="String">Namespace URI of the attribute.</param>
710 /// <returns type="String" maybeNull="true">The attribute value, null if not found.</returns>
711
712 var attribute = xmlAttributeNode(domNode, localName, nsURI);
713 return attribute ? xmlNodeValue(attribute) : null;
714 };
715
716 var xmlAttributeNode = function (domNode, localName, nsURI) {
717 /// <summary>Gets an attribute node from a DOM element.</summary>
718 /// <param name="domNode">DOM node for the owning element.</param>
719 /// <param name="localName" type="String">Local name of the attribute.</param>
720 /// <param name="nsURI" type="String">Namespace URI of the attribute.</param>
721 /// <returns>The attribute node, null if not found.</returns>
722
723 var attributes = domNode.attributes;
724 if (attributes.getNamedItemNS) {
725 return attributes.getNamedItemNS(nsURI || null, localName);
726 }
727
728 return attributes.getQualifiedItem(localName, nsURI) || null;
729 };
730
731 var xmlBaseURI = function (domNode, baseURI) {
732 /// <summary>Gets the value of the xml:base attribute on the specified element.</summary>
733 /// <param name="domNode">Element to get xml:base attribute value from.</param>
734 /// <param name="baseURI" mayBeNull="true" optional="true">Base URI used to normalize the value of the xml:base attribute.</param>
735 /// <returns type="String">Value of the xml:base attribute if found; the baseURI or null otherwise.</returns>
736
737 var base = xmlAttributeNode(domNode, "base", xmlNS);
738 return (base ? normalizeURI(base.value, baseURI) : baseURI) || null;
739 };
740
741
742 var xmlChildElements = function (domNode, onElementCallback) {
743 /// <summary>Iterates through the XML element's child DOM elements and invokes the callback function for each one.</summary>
744 /// <param name="element">DOM Node containing the DOM elements to iterate over.</param>
745 /// <param name="onElementCallback" type="Function">Callback function to invoke for each child DOM element.</param>
746
747 xmlTraverse(domNode, /*recursive*/false, function (child) {
748 if (child.nodeType === 1) {
749 onElementCallback(child);
750 }
751 // continue traversing.
752 return true;
753 });
754 };
755
756 var xmlFindElementByPath = function (root, namespaceURI, path) {
757 /// <summary>Gets the descendant element under root that corresponds to the specified path and namespace URI.</summary>
758 /// <param name="root">DOM element node from which to get the descendant element.</param>
759 /// <param name="namespaceURI" type="String">The namespace URI of the element to match.</param>
760 /// <param name="path" type="String">Path to the desired descendant element.</param>
761 /// <returns>The element specified by path and namespace URI.</returns>
762 /// <remarks>
763 /// All the elements in the path are matched against namespaceURI.
764 /// The function will stop searching on the first element that doesn't match the namespace and the path.
765 /// </remarks>
766
767 var parts = path.split("/");
768 var i, len;
769 for (i = 0, len = parts.length; i < len; i++) {
770 root = root && xmlFirstChildElement(root, namespaceURI, parts[i]);
771 }
772 return root || null;
773 };
774
775 var xmlFindNodeByPath = function (root, namespaceURI, path) {
776 /// <summary>Gets the DOM element or DOM attribute node under root that corresponds to the specified path and namespace URI.</summary>
777 /// <param name="root">DOM element node from which to get the descendant node.</param>
778 /// <param name="namespaceURI" type="String">The namespace URI of the node to match.</param>
779 /// <param name="path" type="String">Path to the desired descendant node.</param>
780 /// <returns>The node specified by path and namespace URI.</returns>
781 /// <remarks>
782 /// This function will traverse the path and match each node associated to a path segement against the namespace URI.
783 /// The traversal stops when the whole path has been exahusted or a node that doesn't belogong the specified namespace is encountered.
784 ///
785 /// The last segment of the path may be decorated with a starting @ character to indicate that the desired node is a DOM attribute.
786 /// </remarks>
787
788 var lastSegmentStart = path.lastIndexOf("/");
789 var nodePath = path.substring(lastSegmentStart + 1);
790 var parentPath = path.substring(0, lastSegmentStart);
791
792 var node = parentPath ? xmlFindElementByPath(root, namespaceURI, parentPath) : root;
793 if (node) {
794 if (nodePath.charAt(0) === "@") {
795 return xmlAttributeNode(node, nodePath.substring(1), namespaceURI);
796 }
797 return xmlFirstChildElement(node, namespaceURI, nodePath);
798 }
799 return null;
800 };
801
802 var xmlFirstChildElement = function (domNode, namespaceURI, localName) {
803 /// <summary>Returns the first child DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary>
804 /// <param name="domNode">DOM node from which the child DOM element is going to be retrieved.</param>
805 /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
806 /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
807 /// <returns>The node's first child DOM element that matches the specified namespace URI and local name; null otherwise.</returns>
808
809 return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/false);
810 };
811
812 var xmlFirstDescendantElement = function (domNode, namespaceURI, localName) {
813 /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary>
814 /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param>
815 /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
816 /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
817 /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns>
818
819 if (domNode.getElementsByTagNameNS) {
820 var result = domNode.getElementsByTagNameNS(namespaceURI, localName);
821 return result.length > 0 ? result[0] : null;
822 }
823 return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/true);
824 };
825
826 var xmlFirstElementMaybeRecursive = function (domNode, namespaceURI, localName, recursive) {
827 /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary>
828 /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param>
829 /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
830 /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
831 /// <param name="recursive" type="Boolean">
832 /// True if the search should include all the descendants of the DOM node.
833 /// False if the search should be scoped only to the direct children of the DOM node.
834 /// </param>
835 /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns>
836
837 var firstElement = null;
838 xmlTraverse(domNode, recursive, function (child) {
839 if (child.nodeType === 1) {
840 var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(child) === namespaceURI;
841 var isExpectedNodeName = !localName || xmlLocalName(child) === localName;
842
843 if (isExpectedNamespace && isExpectedNodeName) {
844 firstElement = child;
845 }
846 }
847 return firstElement === null;
848 });
849 return firstElement;
850 };
851
852 var xmlInnerText = function (xmlElement) {
853 /// <summary>Gets the concatenated value of all immediate child text and CDATA nodes for the specified element.</summary>
854 /// <param name="domElement">Element to get values for.</param>
855 /// <returns type="String">Text for all direct children.</returns>
856
857 var result = null;
858 var root = (xmlElement.nodeType === 9 && xmlElement.documentElement) ? xmlElement.documentElement : xmlElement;
859 var whitespaceAlreadyRemoved = root.ownerDocument.preserveWhiteSpace === false;
860 var whitespacePreserveContext;
861
862 xmlTraverse(root, false, function (child) {
863 if (child.nodeType === 3 || child.nodeType === 4) {
864 // isElementContentWhitespace indicates that this is 'ignorable whitespace',
865 // but it's not defined by all browsers, and does not honor xml:space='preserve'
866 // in some implementations.
867 //
868 // If we can't tell either way, we walk up the tree to figure out whether
869 // xml:space is set to preserve; otherwise we discard pure-whitespace.
870 //
871 // For example <a> <b>1</b></a>. The space between <a> and <b> is usually 'ignorable'.
872 var text = xmlNodeValue(child);
873 var shouldInclude = whitespaceAlreadyRemoved || !isWhitespace(text);
874 if (!shouldInclude) {
875 // Walk up the tree to figure out whether we are in xml:space='preserve' context
876 // for the cursor (needs to happen only once).
877 if (whitespacePreserveContext === undefined) {
878 whitespacePreserveContext = isWhitespacePreserveContext(root);
879 }
880
881 shouldInclude = whitespacePreserveContext;
882 }
883
884 if (shouldInclude) {
885 if (!result) {
886 result = text;
887 } else {
888 result += text;
889 }
890 }
891 }
892 // Continue traversing?
893 return true;
894 });
895 return result;
896 };
897
898 var xmlLocalName = function (domNode) {
899 /// <summary>Returns the localName of a XML node.</summary>
900 /// <param name="domNode">DOM node to get the value from.</param>
901 /// <returns type="String">localName of domNode.</returns>
902
903 return domNode.localName || domNode.baseName;
904 };
905
906 var xmlNamespaceURI = function (domNode) {
907 /// <summary>Returns the namespace URI of a XML node.</summary>
908 /// <param name="node">DOM node to get the value from.</param>
909 /// <returns type="String">Namespace URI of domNode.</returns>
910
911 return domNode.namespaceURI || null;
912 };
913
914 var xmlNodeValue = function (domNode) {
915 /// <summary>Returns the value or the inner text of a XML node.</summary>
916 /// <param name="node">DOM node to get the value from.</param>
917 /// <returns>Value of the domNode or the inner text if domNode represents a DOM element node.</returns>
918
919 if (domNode.nodeType === 1) {
920 return xmlInnerText(domNode);
921 }
922 return domNode.nodeValue;
923 };
924
925 var xmlTraverse = function (domNode, recursive, onChildCallback) {
926 /// <summary>Walks through the descendants of the domNode and invokes a callback for each node.</summary>
927 /// <param name="domNode">DOM node whose descendants are going to be traversed.</param>
928 /// <param name="recursive" type="Boolean">
929 /// True if the traversal should include all the descenants of the DOM node.
930 /// False if the traversal should be scoped only to the direct children of the DOM node.
931 /// </param>
932 /// <returns type="String">Namespace URI of node.</returns>
933
934 var subtrees = [];
935 var child = domNode.firstChild;
936 var proceed = true;
937 while (child && proceed) {
938 proceed = onChildCallback(child);
939 if (proceed) {
940 if (recursive && child.firstChild) {
941 subtrees.push(child.firstChild);
942 }
943 child = child.nextSibling || subtrees.shift();
944 }
945 }
946 };
947
948 var xmlSiblingElement = function (domNode, namespaceURI, localName) {
949 /// <summary>Returns the next sibling DOM element of the specified DOM node.</summary>
950 /// <param name="domNode">DOM node from which the next sibling is going to be retrieved.</param>
951 /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
952 /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
953 /// <returns>The node's next sibling DOM element, null if there is none.</returns>
954
955 var sibling = domNode.nextSibling;
956 while (sibling) {
957 if (sibling.nodeType === 1) {
958 var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(sibling) === namespaceURI;
959 var isExpectedNodeName = !localName || xmlLocalName(sibling) === localName;
960
961 if (isExpectedNamespace && isExpectedNodeName) {
962 return sibling;
963 }
964 }
965 sibling = sibling.nextSibling;
966 }
967 return null;
968 };
969
970 var xmlDom = function () {
971 /// <summary>Creates a new empty DOM document node.</summary>
972 /// <returns>New DOM document node.</returns>
973 /// <remarks>
974 /// This function will first try to create a native DOM document using
975 /// the browsers createDocument function. If the browser doesn't
976 /// support this but supports ActiveXObject, then an attempt to create
977 /// an MSXML 6.0 DOM will be made. If this attempt fails too, then an attempt
978 /// for creating an MXSML 3.0 DOM will be made. If this last attemp fails or
979 /// the browser doesn't support ActiveXObject then an exception will be thrown.
980 /// </remarks>
981
982 var implementation = window.document.implementation;
983 return (implementation && implementation.createDocument) ?
984 implementation.createDocument(null, null, null) :
985 msXmlDom();
986 };
987
988 var xmlAppendChildren = function (parent, children) {
989 /// <summary>Appends a collection of child nodes or string values to a parent DOM node.</summary>
990 /// <param name="parent">DOM node to which the children will be appended.</param>
991 /// <param name="children" type="Array">Array containing DOM nodes or string values that will be appended to the parent.</param>
992 /// <returns>The parent with the appended children or string values.</returns>
993 /// <remarks>
994 /// If a value in the children collection is a string, then a new DOM text node is going to be created
995 /// for it and then appended to the parent.
996 /// </remarks>
997
998 if (!isArray(children)) {
999 return xmlAppendChild(parent, children);
1000 }
1001
1002 var i, len;
1003 for (i = 0, len = children.length; i < len; i++) {
1004 children[i] && xmlAppendChild(parent, children[i]);
1005 }
1006 return parent;
1007 };
1008
1009 var xmlAppendChild = function (parent, child) {
1010 /// <summary>Appends a child node or a string value to a parent DOM node.</summary>
1011 /// <param name="parent">DOM node to which the child will be appended.</param>
1012 /// <param name="child">Child DOM node or string value to append to the parent.</param>
1013 /// <returns>The parent with the appended child or string value.</returns>
1014 /// <remarks>
1015 /// If child is a string value, then a new DOM text node is going to be created
1016 /// for it and then appended to the parent.
1017 /// </remarks>
1018
1019 if (child) {
1020 if (typeof child === "string") {
1021 return xmlAppendText(parent, xmlNewText(parent.ownerDocument, child));
1022 }
1023 if (child.nodeType === 2) {
1024 parent.setAttributeNodeNS ? parent.setAttributeNodeNS(child) : parent.setAttributeNode(child);
1025 } else {
1026 parent.appendChild(child);
1027 }
1028 }
1029 return parent;
1030 };
1031
1032 var xmlNewAttribute = function (dom, namespaceURI, qualifiedName, value) {
1033 /// <summary>Creates a new DOM attribute node.</summary>
1034 /// <param name="dom">DOM document used to create the attribute.</param>
1035 /// <param name="prefix" type="String">Namespace prefix.</param>
1036 /// <param name="namespaceURI" type="String">Namespace URI.</param>
1037 /// <returns>DOM attribute node for the namespace declaration.</returns>
1038
1039 var attribute =
1040 dom.createAttributeNS && dom.createAttributeNS(namespaceURI, qualifiedName) ||
1041 dom.createNode(2, qualifiedName, namespaceURI || undefined);
1042
1043 attribute.value = value || "";
1044 return attribute;
1045 };
1046
1047 var xmlNewElement = function (dom, nampespaceURI, qualifiedName, children) {
1048 /// <summary>Creates a new DOM element node.</summary>
1049 /// <param name="dom">DOM document used to create the DOM element.</param>
1050 /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element.</param>
1051 /// <param name="qualifiedName" type="String">Qualified name in the form of "prefix:name" of the new DOM element.</param>
1052 /// <param name="children" type="Array" optional="true">
1053 /// Collection of child DOM nodes or string values that are going to be appended to the new DOM element.
1054 /// </param>
1055 /// <returns>New DOM element.</returns>
1056 /// <remarks>
1057 /// If a value in the children collection is a string, then a new DOM text node is going to be created
1058 /// for it and then appended to the new DOM element.
1059 /// </remarks>
1060
1061 var element =
1062 dom.createElementNS && dom.createElementNS(nampespaceURI, qualifiedName) ||
1063 dom.createNode(1, qualifiedName, nampespaceURI || undefined);
1064
1065 return xmlAppendChildren(element, children || []);
1066 };
1067
1068 var xmlNewNSDeclaration = function (dom, namespaceURI, prefix) {
1069 /// <summary>Creates a namespace declaration attribute.</summary>
1070 /// <param name="dom">DOM document used to create the attribute.</param>
1071 /// <param name="namespaceURI" type="String">Namespace URI.</param>
1072 /// <param name="prefix" type="String">Namespace prefix.</param>
1073 /// <returns>DOM attribute node for the namespace declaration.</returns>
1074
1075 return xmlNewAttribute(dom, xmlnsNS, xmlQualifiedName("xmlns", prefix), namespaceURI);
1076 };
1077
1078 var xmlNewFragment = function (dom, text) {
1079 /// <summary>Creates a new DOM document fragment node for the specified xml text.</summary>
1080 /// <param name="dom">DOM document from which the fragment node is going to be created.</param>
1081 /// <param name="text" type="String" mayBeNull="false">XML text to be represented by the XmlFragment.</param>
1082 /// <returns>New DOM document fragment object.</returns>
1083
1084 var value = "<c>" + text + "</c>";
1085 var tempDom = xmlParse(value);
1086 var tempRoot = tempDom.documentElement;
1087 var imported = ("importNode" in dom) ? dom.importNode(tempRoot, true) : tempRoot;
1088 var fragment = dom.createDocumentFragment();
1089
1090 var importedChild = imported.firstChild;
1091 while (importedChild) {
1092 fragment.appendChild(importedChild);
1093 importedChild = importedChild.nextSibling;
1094 }
1095 return fragment;
1096 };
1097
1098 var xmlNewText = function (dom, text) {
1099 /// <summary>Creates new DOM text node.</summary>
1100 /// <param name="dom">DOM document used to create the text node.</param>
1101 /// <param name="text" type="String">Text value for the DOM text node.</param>
1102 /// <returns>DOM text node.</returns>
1103
1104 return dom.createTextNode(text);
1105 };
1106
1107 var xmlNewNodeByPath = function (dom, root, namespaceURI, prefix, path) {
1108 /// <summary>Creates a new DOM element or DOM attribute node as specified by path and appends it to the DOM tree pointed by root.</summary>
1109 /// <param name="dom">DOM document used to create the new node.</param>
1110 /// <param name="root">DOM element node used as root of the subtree on which the new nodes are going to be created.</param>
1111 /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element or attribute.</param>
1112 /// <param name="namespacePrefix" type="String">Prefix used to qualify the name of the new DOM element or attribute.</param>
1113 /// <param name="Path" type="String">Path string describing the location of the new DOM element or attribute from the root element.</param>
1114 /// <returns>DOM element or attribute node for the last segment of the path.</returns>
1115 /// <remarks>
1116 /// This function will traverse the path and will create a new DOM element with the specified namespace URI and prefix
1117 /// for each segment that doesn't have a matching element under root.
1118 ///
1119 /// The last segment of the path may be decorated with a starting @ character. In this case a new DOM attribute node
1120 /// will be created.
1121 /// </remarks>
1122
1123 var name = "";
1124 var parts = path.split("/");
1125 var xmlFindNode = xmlFirstChildElement;
1126 var xmlNewNode = xmlNewElement;
1127 var xmlNode = root;
1128
1129 var i, len;
1130 for (i = 0, len = parts.length; i < len; i++) {
1131 name = parts[i];
1132 if (name.charAt(0) === "@") {
1133 name = name.substring(1);
1134 xmlFindNode = xmlAttributeNode;
1135 xmlNewNode = xmlNewAttribute;
1136 }
1137
1138 var childNode = xmlFindNode(xmlNode, namespaceURI, name);
1139 if (!childNode) {
1140 childNode = xmlNewNode(dom, namespaceURI, xmlQualifiedName(prefix, name));
1141 xmlAppendChild(xmlNode, childNode);
1142 }
1143 xmlNode = childNode;
1144 }
1145 return xmlNode;
1146 };
1147
1148 var xmlSerialize = function (domNode) {
1149 /// <summary>
1150 /// Returns the text representation of the document to which the specified node belongs.
1151 /// </summary>
1152 /// <param name="root">Wrapped element in the document to serialize.</param>
1153 /// <returns type="String">Serialized document.</returns>
1154
1155 var xmlSerializer = window.XMLSerializer;
1156 if (xmlSerializer) {
1157 var serializer = new xmlSerializer();
1158 return serializer.serializeToString(domNode);
1159 }
1160
1161 if (domNode.xml) {
1162 return domNode.xml;
1163 }
1164
1165 throw { message: "XML serialization unsupported" };
1166 };
1167
1168 var xmlSerializeDescendants = function (domNode) {
1169 /// <summary>Returns the XML representation of the all the descendants of the node.</summary>
1170 /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param>
1171 /// <returns type="String">The XML representation of all the descendants of the node.</returns>
1172
1173 var children = domNode.childNodes;
1174 var i, len = children.length;
1175 if (len === 0) {
1176 return "";
1177 }
1178
1179 // Some implementations of the XMLSerializer don't deal very well with fragments that
1180 // don't have a DOMElement as their first child. The work around is to wrap all the
1181 // nodes in a dummy root node named "c", serialize it and then just extract the text between
1182 // the <c> and the </c> substrings.
1183
1184 var dom = domNode.ownerDocument;
1185 var fragment = dom.createDocumentFragment();
1186 var fragmentRoot = dom.createElement("c");
1187
1188 fragment.appendChild(fragmentRoot);
1189 // Move the children to the fragment tree.
1190 for (i = 0; i < len; i++) {
1191 fragmentRoot.appendChild(children[i]);
1192 }
1193
1194 var xml = xmlSerialize(fragment);
1195 xml = xml.substr(3, xml.length - 7);
1196
1197 // Move the children back to the original dom tree.
1198 for (i = 0; i < len; i++) {
1199 domNode.appendChild(fragmentRoot.childNodes[i]);
1200 }
1201
1202 return xml;
1203 };
1204
1205 var xmlSerializeNode = function (domNode) {
1206 /// <summary>Returns the XML representation of the node and all its descendants.</summary>
1207 /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param>
1208 /// <returns type="String">The XML representation of the node and all its descendants.</returns>
1209
1210 var xml = domNode.xml;
1211 if (xml !== undefined) {
1212 return xml;
1213 }
1214
1215 if (window.XMLSerializer) {
1216 var serializer = new window.XMLSerializer();
1217 return serializer.serializeToString(domNode);
1218 }
1219
1220 throw { message: "XML serialization unsupported" };
1221 };
1222
1223
1224
1225
1226 var forwardCall = function (thisValue, name, returnValue) {
1227 /// <summary>Creates a new function to forward a call.</summary>
1228 /// <param name="thisValue" type="Object">Value to use as the 'this' object.</param>
1229 /// <param name="name" type="String">Name of function to forward to.</param>
1230 /// <param name="returnValue" type="Object">Return value for the forward call (helps keep identity when chaining calls).</param>
1231 /// <returns type="Function">A new function that will forward a call.</returns>
1232
1233 return function () {
1234 thisValue[name].apply(thisValue, arguments);
1235 return returnValue;
1236 };
1237 };
1238
1239 var DjsDeferred = function () {
1240 /// <summary>Initializes a new DjsDeferred object.</summary>
1241 /// <remarks>
1242 /// Compability Note A - Ordering of callbacks through chained 'then' invocations
1243 ///
1244 /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
1245 /// implies that .then() returns a distinct object.
1246 ////
1247 /// For compatibility with http://api.jquery.com/category/deferred-object/
1248 /// we return this same object. This affects ordering, as
1249 /// the jQuery version will fire callbacks in registration
1250 /// order regardless of whether they occur on the result
1251 /// or the original object.
1252 ///
1253 /// Compability Note B - Fulfillment value
1254 ///
1255 /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
1256 /// implies that the result of a success callback is the
1257 /// fulfillment value of the object and is received by
1258 /// other success callbacks that are chained.
1259 ///
1260 /// For compatibility with http://api.jquery.com/category/deferred-object/
1261 /// we disregard this value instead.
1262 /// </remarks>
1263
1264 this._arguments = undefined;
1265 this._done = undefined;
1266 this._fail = undefined;
1267 this._resolved = false;
1268 this._rejected = false;
1269 };
1270
1271 DjsDeferred.prototype = {
1272 then: function (fulfilledHandler, errorHandler /*, progressHandler */) {
1273 /// <summary>Adds success and error callbacks for this deferred object.</summary>
1274 /// <param name="fulfilledHandler" type="Function" mayBeNull="true" optional="true">Success callback.</param>
1275 /// <param name="errorHandler" type="Function" mayBeNull="true" optional="true">Error callback.</param>
1276 /// <remarks>See Compatibility Note A.</remarks>
1277
1278 if (fulfilledHandler) {
1279 if (!this._done) {
1280 this._done = [fulfilledHandler];
1281 } else {
1282 this._done.push(fulfilledHandler);
1283 }
1284 }
1285
1286 if (errorHandler) {
1287 if (!this._fail) {
1288 this._fail = [errorHandler];
1289 } else {
1290 this._fail.push(errorHandler);
1291 }
1292 }
1293
1294 //// See Compatibility Note A in the DjsDeferred constructor.
1295 //// if (!this._next) {
1296 //// this._next = createDeferred();
1297 //// }
1298 //// return this._next.promise();
1299
1300 if (this._resolved) {
1301 this.resolve.apply(this, this._arguments);
1302 } else if (this._rejected) {
1303 this.reject.apply(this, this._arguments);
1304 }
1305
1306 return this;
1307 },
1308
1309 resolve: function (/* args */) {
1310 /// <summary>Invokes success callbacks for this deferred object.</summary>
1311 /// <remarks>All arguments are forwarded to success callbacks.</remarks>
1312
1313
1314 if (this._done) {
1315 var i, len;
1316 for (i = 0, len = this._done.length; i < len; i++) {
1317 //// See Compability Note B - Fulfillment value.
1318 //// var nextValue =
1319 this._done[i].apply(null, arguments);
1320 }
1321
1322 //// See Compatibility Note A in the DjsDeferred constructor.
1323 //// this._next.resolve(nextValue);
1324 //// delete this._next;
1325
1326 this._done = undefined;
1327 this._resolved = false;
1328 this._arguments = undefined;
1329 } else {
1330 this._resolved = true;
1331 this._arguments = arguments;
1332 }
1333 },
1334
1335 reject: function (/* args */) {
1336 /// <summary>Invokes error callbacks for this deferred object.</summary>
1337 /// <remarks>All arguments are forwarded to error callbacks.</remarks>
1338 if (this._fail) {
1339 var i, len;
1340 for (i = 0, len = this._fail.length; i < len; i++) {
1341 this._fail[i].apply(null, arguments);
1342 }
1343
1344 this._fail = undefined;
1345 this._rejected = false;
1346 this._arguments = undefined;
1347 } else {
1348 this._rejected = true;
1349 this._arguments = arguments;
1350 }
1351 },
1352
1353 promise: function () {
1354 /// <summary>Returns a version of this object that has only the read-only methods available.</summary>
1355 /// <returns>An object with only the promise object.</returns>
1356
1357 var result = {};
1358 result.then = forwardCall(this, "then", result);
1359 return result;
1360 }
1361 };
1362
1363 var createDeferred = function () {
1364 /// <summary>Creates a deferred object.</summary>
1365 /// <returns type="DjsDeferred">
1366 /// A new deferred object. If jQuery is installed, then a jQuery
1367 /// Deferred object is returned, which provides a superset of features.
1368 /// </returns>
1369
1370 if (window.jQuery && window.jQuery.Deferred) {
1371 return new window.jQuery.Deferred();
1372 } else {
1373 return new DjsDeferred();
1374 }
1375 };
1376
1377
1378
1379
1380 var dataItemTypeName = function (value, metadata) {
1381 /// <summary>Gets the type name of a data item value that belongs to a feed, an entry, a complex type property, or a collection property.</summary>
1382 /// <param name="value">Value of the data item from which the type name is going to be retrieved.</param>
1383 /// <param name="metadata" type="object" optional="true">Object containing metadata about the data tiem.</param>
1384 /// <remarks>
1385 /// This function will first try to get the type name from the data item's value itself if it is an object with a __metadata property; otherwise
1386 /// it will try to recover it from the metadata. If both attempts fail, it will return null.
1387 /// </remarks>
1388 /// <returns type="String">Data item type name; null if the type name cannot be found within the value or the metadata</returns>
1389
1390 var valueTypeName = ((value && value.__metadata) || {}).type;
1391 return valueTypeName || (metadata ? metadata.type : null);
1392 };
1393
1394 var EDM = "Edm.";
1395 var EDM_BINARY = EDM + "Binary";
1396 var EDM_BOOLEAN = EDM + "Boolean";
1397 var EDM_BYTE = EDM + "Byte";
1398 var EDM_DATETIME = EDM + "DateTime";
1399 var EDM_DATETIMEOFFSET = EDM + "DateTimeOffset";
1400 var EDM_DECIMAL = EDM + "Decimal";
1401 var EDM_DOUBLE = EDM + "Double";
1402 var EDM_GUID = EDM + "Guid";
1403 var EDM_INT16 = EDM + "Int16";
1404 var EDM_INT32 = EDM + "Int32";
1405 var EDM_INT64 = EDM + "Int64";
1406 var EDM_SBYTE = EDM + "SByte";
1407 var EDM_SINGLE = EDM + "Single";
1408 var EDM_STRING = EDM + "String";
1409 var EDM_TIME = EDM + "Time";
1410
1411 var EDM_GEOGRAPHY = EDM + "Geography";
1412 var EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY + "Point";
1413 var EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY + "LineString";
1414 var EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY + "Polygon";
1415 var EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY + "Collection";
1416 var EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY + "MultiPolygon";
1417 var EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY + "MultiLineString";
1418 var EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY + "MultiPoint";
1419
1420 var EDM_GEOMETRY = EDM + "Geometry";
1421 var EDM_GEOMETRY_POINT = EDM_GEOMETRY + "Point";
1422 var EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY + "LineString";
1423 var EDM_GEOMETRY_POLYGON = EDM_GEOMETRY + "Polygon";
1424 var EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY + "Collection";
1425 var EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY + "MultiPolygon";
1426 var EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY + "MultiLineString";
1427 var EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY + "MultiPoint";
1428
1429 var GEOJSON_POINT = "Point";
1430 var GEOJSON_LINESTRING = "LineString";
1431 var GEOJSON_POLYGON = "Polygon";
1432 var GEOJSON_MULTIPOINT = "MultiPoint";
1433 var GEOJSON_MULTILINESTRING = "MultiLineString";
1434 var GEOJSON_MULTIPOLYGON = "MultiPolygon";
1435 var GEOJSON_GEOMETRYCOLLECTION = "GeometryCollection";
1436
1437 var primitiveEdmTypes = [
1438 EDM_STRING,
1439 EDM_INT32,
1440 EDM_INT64,
1441 EDM_BOOLEAN,
1442 EDM_DOUBLE,
1443 EDM_SINGLE,
1444 EDM_DATETIME,
1445 EDM_DATETIMEOFFSET,
1446 EDM_TIME,
1447 EDM_DECIMAL,
1448 EDM_GUID,
1449 EDM_BYTE,
1450 EDM_INT16,
1451 EDM_SBYTE,
1452 EDM_BINARY
1453 ];
1454
1455 var geometryEdmTypes = [
1456 EDM_GEOMETRY,
1457 EDM_GEOMETRY_POINT,
1458 EDM_GEOMETRY_LINESTRING,
1459 EDM_GEOMETRY_POLYGON,
1460 EDM_GEOMETRY_COLLECTION,
1461 EDM_GEOMETRY_MULTIPOLYGON,
1462 EDM_GEOMETRY_MULTILINESTRING,
1463 EDM_GEOMETRY_MULTIPOINT
1464 ];
1465
1466 var geographyEdmTypes = [
1467 EDM_GEOGRAPHY,
1468 EDM_GEOGRAPHY_POINT,
1469 EDM_GEOGRAPHY_LINESTRING,
1470 EDM_GEOGRAPHY_POLYGON,
1471 EDM_GEOGRAPHY_COLLECTION,
1472 EDM_GEOGRAPHY_MULTIPOLYGON,
1473 EDM_GEOGRAPHY_MULTILINESTRING,
1474 EDM_GEOGRAPHY_MULTIPOINT
1475 ];
1476
1477 var forEachSchema = function (metadata, callback) {
1478 /// <summary>Invokes a function once per schema in metadata.</summary>
1479 /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
1480 /// <param name="callback" type="Function">Callback function to invoke once per schema.</param>
1481 /// <returns>
1482 /// The first truthy value to be returned from the callback; null or the last falsy value otherwise.
1483 /// </returns>
1484
1485 if (!metadata) {
1486 return null;
1487 }
1488
1489 if (isArray(metadata)) {
1490 var i, len, result;
1491 for (i = 0, len = metadata.length; i < len; i++) {
1492 result = forEachSchema(metadata[i], callback);
1493 if (result) {
1494 return result;
1495 }
1496 }
1497
1498 return null;
1499 } else {
1500 if (metadata.dataServices) {
1501 return forEachSchema(metadata.dataServices.schema, callback);
1502 }
1503
1504 return callback(metadata);
1505 }
1506 };
1507
1508 var formatMilliseconds = function (ms, ns) {
1509 /// <summary>Formats a millisecond and a nanosecond value into a single string.</summary>
1510 /// <param name="ms" type="Number" mayBeNull="false">Number of milliseconds to format.</param>
1511 /// <param name="ns" type="Number" mayBeNull="false">Number of nanoseconds to format.</param>
1512 /// <returns type="String">Formatted text.</returns>
1513 /// <remarks>If the value is already as string it's returned as-is.</remarks>
1514
1515 // Avoid generating milliseconds if not necessary.
1516 if (ms === 0) {
1517 ms = "";
1518 } else {
1519 ms = "." + formatNumberWidth(ms.toString(), 3);
1520 }
1521 if (ns > 0) {
1522 if (ms === "") {
1523 ms = ".000";
1524 }
1525 ms += formatNumberWidth(ns.toString(), 4);
1526 }
1527 return ms;
1528 };
1529
1530 var formatDateTimeOffset = function (value) {
1531 /// <summary>Formats a DateTime or DateTimeOffset value a string.</summary>
1532 /// <param name="value" type="Date" mayBeNull="false">Value to format.</param>
1533 /// <returns type="String">Formatted text.</returns>
1534 /// <remarks>If the value is already as string it's returned as-is.</remarks>
1535
1536 if (typeof value === "string") {
1537 return value;
1538 }
1539
1540 var hasOffset = isDateTimeOffset(value);
1541 var offset = getCanonicalTimezone(value.__offset);
1542 if (hasOffset && offset !== "Z") {
1543 // We're about to change the value, so make a copy.
1544 value = new Date(value.valueOf());
1545
1546 var timezone = parseTimezone(offset);
1547 var hours = value.getUTCHours() + (timezone.d * timezone.h);
1548 var minutes = value.getUTCMinutes() + (timezone.d * timezone.m);
1549
1550 value.setUTCHours(hours, minutes);
1551 } else if (!hasOffset) {
1552 // Don't suffix a 'Z' for Edm.DateTime values.
1553 offset = "";
1554 }
1555
1556 var year = value.getUTCFullYear();
1557 var month = value.getUTCMonth() + 1;
1558 var sign = "";
1559 if (year <= 0) {
1560 year = -(year - 1);
1561 sign = "-";
1562 }
1563
1564 var ms = formatMilliseconds(value.getUTCMilliseconds(), value.__ns);
1565
1566 return sign +
1567 formatNumberWidth(year, 4) + "-" +
1568 formatNumberWidth(month, 2) + "-" +
1569 formatNumberWidth(value.getUTCDate(), 2) + "T" +
1570 formatNumberWidth(value.getUTCHours(), 2) + ":" +
1571 formatNumberWidth(value.getUTCMinutes(), 2) + ":" +
1572 formatNumberWidth(value.getUTCSeconds(), 2) +
1573 ms + offset;
1574 };
1575
1576 var formatDuration = function (value) {
1577 /// <summary>Converts a duration to a string in xsd:duration format.</summary>
1578 /// <param name="value" type="Object">Object with ms and __edmType properties.</param>
1579 /// <returns type="String">String representation of the time object in xsd:duration format.</returns>
1580
1581 var ms = value.ms;
1582
1583 var sign = "";
1584 if (ms < 0) {
1585 sign = "-";
1586 ms = -ms;
1587 }
1588
1589 var days = Math.floor(ms / 86400000);
1590 ms -= 86400000 * days;
1591 var hours = Math.floor(ms / 3600000);
1592 ms -= 3600000 * hours;
1593 var minutes = Math.floor(ms / 60000);
1594 ms -= 60000 * minutes;
1595 var seconds = Math.floor(ms / 1000);
1596 ms -= seconds * 1000;
1597
1598 return sign + "P" +
1599 formatNumberWidth(days, 2) + "DT" +
1600 formatNumberWidth(hours, 2) + "H" +
1601 formatNumberWidth(minutes, 2) + "M" +
1602 formatNumberWidth(seconds, 2) +
1603 formatMilliseconds(ms, value.ns) + "S";
1604 };
1605
1606 var formatNumberWidth = function (value, width, append) {
1607 /// <summary>Formats the specified value to the given width.</summary>
1608 /// <param name="value" type="Number">Number to format (non-negative).</param>
1609 /// <param name="width" type="Number">Minimum width for number.</param>
1610 /// <param name="append" type="Boolean">Flag indicating if the value is padded at the beginning (false) or at the end (true).</param>
1611 /// <returns type="String">Text representation.</returns>
1612 var result = value.toString(10);
1613 while (result.length < width) {
1614 if (append) {
1615 result += "0";
1616 } else {
1617 result = "0" + result;
1618 }
1619 }
1620
1621 return result;
1622 };
1623
1624 var getCanonicalTimezone = function (timezone) {
1625 /// <summary>Gets the canonical timezone representation.</summary>
1626 /// <param name="timezone" type="String">Timezone representation.</param>
1627 /// <returns type="String">An 'Z' string if the timezone is absent or 0; the timezone otherwise.</returns>
1628
1629 return (!timezone || timezone === "Z" || timezone === "+00:00" || timezone === "-00:00") ? "Z" : timezone;
1630 };
1631
1632 var getCollectionType = function (typeName) {
1633 /// <summary>Gets the type of a collection type name.</summary>
1634 /// <param name="typeName" type="String">Type name of the collection.</param>
1635 /// <returns type="String">Type of the collection; null if the type name is not a collection type.</returns>
1636
1637 if (typeof typeName === "string") {
1638 var end = typeName.indexOf(")", 10);
1639 if (typeName.indexOf("Collection(") === 0 && end > 0) {
1640 return typeName.substring(11, end);
1641 }
1642 }
1643 return null;
1644 };
1645
1646 var invokeRequest = function (request, success, error, handler, httpClient, context) {
1647 /// <summary>Sends a request containing OData payload to a server.</summary>
1648 /// <param name="request">Object that represents the request to be sent..</param>
1649 /// <param name="success">Callback for a successful read operation.</param>
1650 /// <param name="error">Callback for handling errors.</param>
1651 /// <param name="handler">Handler for data serialization.</param>
1652 /// <param name="httpClient">HTTP client layer.</param>
1653 /// <param name="context">Context used for processing the request</param>
1654
1655 return httpClient.request(request, function (response) {
1656 try {
1657 if (response.headers) {
1658 normalizeHeaders(response.headers);
1659 }
1660
1661 if (response.data === undefined && response.statusCode !== 204) {
1662 handler.read(response, context);
1663 }
1664 } catch (err) {
1665 if (err.request === undefined) {
1666 err.request = request;
1667 }
1668 if (err.response === undefined) {
1669 err.response = response;
1670 }
1671 error(err);
1672 return;
1673 }
1674
1675 success(response.data, response);
1676 }, error);
1677 };
1678
1679 var isBatch = function (value) {
1680 /// <summary>Tests whether a value is a batch object in the library's internal representation.</summary>
1681 /// <param name="value">Value to test.</param>
1682 /// <returns type="Boolean">True is the value is a batch object; false otherwise.</returns>
1683
1684 return isComplex(value) && isArray(value.__batchRequests);
1685 };
1686
1687 // Regular expression used for testing and parsing for a collection type.
1688 var collectionTypeRE = /Collection\((.*)\)/;
1689
1690 var isCollection = function (value, typeName) {
1691 /// <summary>Tests whether a value is a collection value in the library's internal representation.</summary>
1692 /// <param name="value">Value to test.</param>
1693 /// <param name="typeName" type="Sting">Type name of the value. This is used to disambiguate from a collection property value.</param>
1694 /// <returns type="Boolean">True is the value is a feed value; false otherwise.</returns>
1695
1696 var colData = value && value.results || value;
1697 return !!colData &&
1698 (isCollectionType(typeName)) ||
1699 (!typeName && isArray(colData) && !isComplex(colData[0]));
1700 };
1701
1702 var isCollectionType = function (typeName) {
1703 /// <summary>Checks whether the specified type name is a collection type.</summary>
1704 /// <param name="typeName" type="String">Name of type to check.</param>
1705 /// <returns type="Boolean">True if the type is the name of a collection type; false otherwise.</returns>
1706 return collectionTypeRE.test(typeName);
1707 };
1708
1709 var isComplex = function (value) {
1710 /// <summary>Tests whether a value is a complex type value in the library's internal representation.</summary>
1711 /// <param name="value">Value to test.</param>
1712 /// <returns type="Boolean">True is the value is a complex type value; false otherwise.</returns>
1713
1714 return !!value &&
1715 isObject(value) &&
1716 !isArray(value) &&
1717 !isDate(value);
1718 };
1719
1720 var isDateTimeOffset = function (value) {
1721 /// <summary>Checks whether a Date object is DateTimeOffset value</summary>
1722 /// <param name="value" type="Date" mayBeNull="false">Value to check.</param>
1723 /// <returns type="Boolean">true if the value is a DateTimeOffset, false otherwise.</returns>
1724 return (value.__edmType === "Edm.DateTimeOffset" || (!value.__edmType && value.__offset));
1725 };
1726
1727 var isDeferred = function (value) {
1728 /// <summary>Tests whether a value is a deferred navigation property in the library's internal representation.</summary>
1729 /// <param name="value">Value to test.</param>
1730 /// <returns type="Boolean">True is the value is a deferred navigation property; false otherwise.</returns>
1731
1732 if (!value && !isComplex(value)) {
1733 return false;
1734 }
1735 var metadata = value.__metadata || {};
1736 var deferred = value.__deferred || {};
1737 return !metadata.type && !!deferred.uri;
1738 };
1739
1740 var isEntry = function (value) {
1741 /// <summary>Tests whether a value is an entry object in the library's internal representation.</summary>
1742 /// <param name="value">Value to test.</param>
1743 /// <returns type="Boolean">True is the value is an entry object; false otherwise.</returns>
1744
1745 return isComplex(value) && value.__metadata && "uri" in value.__metadata;
1746 };
1747
1748 var isFeed = function (value, typeName) {
1749 /// <summary>Tests whether a value is a feed value in the library's internal representation.</summary>
1750 /// <param name="value">Value to test.</param>
1751 /// <param name="typeName" type="Sting">Type name of the value. This is used to disambiguate from a collection property value.</param>
1752 /// <returns type="Boolean">True is the value is a feed value; false otherwise.</returns>
1753
1754 var feedData = value && value.results || value;
1755 return isArray(feedData) && (
1756 (!isCollectionType(typeName)) &&
1757 (isComplex(feedData[0]))
1758 );
1759 };
1760
1761 var isGeographyEdmType = function (typeName) {
1762 /// <summary>Checks whether the specified type name is a geography EDM type.</summary>
1763 /// <param name="typeName" type="String">Name of type to check.</param>
1764 /// <returns type="Boolean">True if the type is a geography EDM type; false otherwise.</returns>
1765
1766 return contains(geographyEdmTypes, typeName);
1767 };
1768
1769 var isGeometryEdmType = function (typeName) {
1770 /// <summary>Checks whether the specified type name is a geometry EDM type.</summary>
1771 /// <param name="typeName" type="String">Name of type to check.</param>
1772 /// <returns type="Boolean">True if the type is a geometry EDM type; false otherwise.</returns>
1773
1774 return contains(geometryEdmTypes, typeName);
1775 };
1776
1777 var isNamedStream = function (value) {
1778 /// <summary>Tests whether a value is a named stream value in the library's internal representation.</summary>
1779 /// <param name="value">Value to test.</param>
1780 /// <returns type="Boolean">True is the value is a named stream; false otherwise.</returns>
1781
1782 if (!value && !isComplex(value)) {
1783 return false;
1784 }
1785 var metadata = value.__metadata;
1786 var mediaResource = value.__mediaresource;
1787 return !metadata && !!mediaResource && !!mediaResource.media_src;
1788 };
1789
1790 var isPrimitive = function (value) {
1791 /// <summary>Tests whether a value is a primitive type value in the library's internal representation.</summary>
1792 /// <param name="value">Value to test.</param>
1793 /// <remarks>
1794 /// Date objects are considered primitive types by the library.
1795 /// </remarks>
1796 /// <returns type="Boolean">True is the value is a primitive type value.</returns>
1797
1798 return isDate(value) ||
1799 typeof value === "string" ||
1800 typeof value === "number" ||
1801 typeof value === "boolean";
1802 };
1803
1804 var isPrimitiveEdmType = function (typeName) {
1805 /// <summary>Checks whether the specified type name is a primitive EDM type.</summary>
1806 /// <param name="typeName" type="String">Name of type to check.</param>
1807 /// <returns type="Boolean">True if the type is a primitive EDM type; false otherwise.</returns>
1808
1809 return contains(primitiveEdmTypes, typeName);
1810 };
1811
1812 var navigationPropertyKind = function (value, propertyModel) {
1813 /// <summary>Gets the kind of a navigation property value.</summary>
1814 /// <param name="value">Value of the navigation property.</param>
1815 /// <param name="propertyModel" type="Object" optional="true">
1816 /// Object that describes the navigation property in an OData conceptual schema.
1817 /// </param>
1818 /// <remarks>
1819 /// The returned string is as follows
1820 /// </remarks>
1821 /// <returns type="String">String value describing the kind of the navigation property; null if the kind cannot be determined.</returns>
1822
1823 if (isDeferred(value)) {
1824 return "deferred";
1825 }
1826 if (isEntry(value)) {
1827 return "entry";
1828 }
1829 if (isFeed(value)) {
1830 return "feed";
1831 }
1832 if (propertyModel && propertyModel.relationship) {
1833 if (value === null || value === undefined || !isFeed(value)) {
1834 return "entry";
1835 }
1836 return "feed";
1837 }
1838 return null;
1839 };
1840
1841 var lookupProperty = function (properties, name) {
1842 /// <summary>Looks up a property by name.</summary>
1843 /// <param name="properties" type="Array" mayBeNull="true">Array of property objects as per EDM metadata.</param>
1844 /// <param name="name" type="String">Name to look for.</param>
1845 /// <returns type="Object">The property object; null if not found.</returns>
1846
1847 return find(properties, function (property) {
1848 return property.name === name;
1849 });
1850 };
1851
1852 var lookupInMetadata = function (name, metadata, kind) {
1853 /// <summary>Looks up a type object by name.</summary>
1854 /// <param name="name" type="String">Name, possibly null or empty.</param>
1855 /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
1856 /// <param name="kind" type="String">Kind of object to look for as per EDM metadata.</param>
1857 /// <returns>An type description if the name is found; null otherwise.</returns>
1858
1859 return (name) ? forEachSchema(metadata, function (schema) {
1860 return lookupInSchema(name, schema, kind);
1861 }) : null;
1862 };
1863
1864 var lookupEntitySet = function (entitySets, name) {
1865 /// <summary>Looks up a entity set by name.</summary>
1866 /// <param name="properties" type="Array" mayBeNull="true">Array of entity set objects as per EDM metadata.</param>
1867 /// <param name="name" type="String">Name to look for.</param>
1868 /// <returns type="Object">The entity set object; null if not found.</returns>
1869
1870 return find(entitySets, function (entitySet) {
1871 return entitySet.name === name;
1872 });
1873 };
1874
1875 var lookupComplexType = function (name, metadata) {
1876 /// <summary>Looks up a complex type object by name.</summary>
1877 /// <param name="name" type="String">Name, possibly null or empty.</param>
1878 /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
1879 /// <returns>A complex type description if the name is found; null otherwise.</returns>
1880
1881 return lookupInMetadata(name, metadata, "complexType");
1882 };
1883
1884 var lookupEntityType = function (name, metadata) {
1885 /// <summary>Looks up an entity type object by name.</summary>
1886 /// <param name="name" type="String">Name, possibly null or empty.</param>
1887 /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
1888 /// <returns>An entity type description if the name is found; null otherwise.</returns>
1889
1890 return lookupInMetadata(name, metadata, "entityType");
1891 };
1892
1893 var lookupDefaultEntityContainer = function (metadata) {
1894 /// <summary>Looks up an</summary>
1895 /// <param name="name" type="String">Name, possibly null or empty.</param>
1896 /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
1897 /// <returns>An entity container description if the name is found; null otherwise.</returns>
1898
1899 return forEachSchema(metadata, function (schema) {
1900 return find(schema.entityContainer, function (container) {
1901 return parseBool(container.isDefaultEntityContainer);
1902 });
1903 });
1904 };
1905
1906 var lookupEntityContainer = function (name, metadata) {
1907 /// <summary>Looks up an entity container object by name.</summary>
1908 /// <param name="name" type="String">Name, possibly null or empty.</param>
1909 /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
1910 /// <returns>An entity container description if the name is found; null otherwise.</returns>
1911
1912 return lookupInMetadata(name, metadata, "entityContainer");
1913 };
1914
1915 var lookupFunctionImport = function (functionImports, name) {
1916 /// <summary>Looks up a function import by name.</summary>
1917 /// <param name="properties" type="Array" mayBeNull="true">Array of function import objects as per EDM metadata.</param>
1918 /// <param name="name" type="String">Name to look for.</param>
1919 /// <returns type="Object">The entity set object; null if not found.</returns>
1920
1921 return find(functionImports, function (functionImport) {
1922 return functionImport.name === name;
1923 });
1924 };
1925
1926 var lookupNavigationPropertyType = function (navigationProperty, metadata) {
1927 /// <summary>Looks up the target entity type for a navigation property.</summary>
1928 /// <param name="navigationProperty" type="Object"></param>
1929 /// <param name="metadata" type="Object"></param>
1930 /// <returns type="String">The entity type name for the specified property, null if not found.</returns>
1931
1932 var result = null;
1933 if (navigationProperty) {
1934 var rel = navigationProperty.relationship;
1935 var association = forEachSchema(metadata, function (schema) {
1936 // The name should be the namespace qualified name in 'ns'.'type' format.
1937 var nameOnly = removeNamespace(schema["namespace"], rel);
1938 var associations = schema.association;
1939 if (nameOnly && associations) {
1940 var i, len;
1941 for (i = 0, len = associations.length; i < len; i++) {
1942 if (associations[i].name === nameOnly) {
1943 return associations[i];
1944 }
1945 }
1946 }
1947 return null;
1948 });
1949
1950 if (association) {
1951 var end = association.end[0];
1952 if (end.role !== navigationProperty.toRole) {
1953 end = association.end[1];
1954 // For metadata to be valid, end.role === navigationProperty.toRole now.
1955 }
1956 result = end.type;
1957 }
1958 }
1959 return result;
1960 };
1961
1962 var lookupNavigationPropertyEntitySet = function (navigationProperty, sourceEntitySetName, metadata) {
1963 /// <summary>Looks up the target entityset name for a navigation property.</summary>
1964 /// <param name="navigationProperty" type="Object"></param>
1965 /// <param name="metadata" type="Object"></param>
1966 /// <returns type="String">The entityset name for the specified property, null if not found.</returns>
1967
1968 if (navigationProperty) {
1969 var rel = navigationProperty.relationship;
1970 var associationSet = forEachSchema(metadata, function (schema) {
1971 var containers = schema.entityContainer;
1972 for (var i = 0; i < containers.length; i++) {
1973 var associationSets = containers[i].associationSet;
1974 if (associationSets) {
1975 for (var j = 0; j < associationSets.length; j++) {
1976 if (associationSets[j].association == rel) {
1977 return associationSets[j];
1978 }
1979 }
1980 }
1981 }
1982 return null;
1983 });
1984 if (associationSet && associationSet.end[0] && associationSet.end[1]) {
1985 return (associationSet.end[0].entitySet == sourceEntitySetName) ? associationSet.end[1].entitySet : associationSet.end[0].entitySet;
1986 }
1987 }
1988 return null;
1989 };
1990
1991 var getEntitySetInfo = function (entitySetName, metadata) {
1992 /// <summary>Gets the entitySet info, container name and functionImports for an entitySet</summary>
1993 /// <param name="navigationProperty" type="Object"></param>
1994 /// <param name="metadata" type="Object"></param>
1995 /// <returns type="Object">The info about the entitySet.</returns>
1996
1997 var info = forEachSchema(metadata, function (schema) {
1998 var containers = schema.entityContainer;
1999 for (var i = 0; i < containers.length; i++) {
2000 var entitySets = containers[i].entitySet;
2001 if (entitySets) {
2002 for (var j = 0; j < entitySets.length; j++) {
2003 if (entitySets[j].name == entitySetName) {
2004 return { entitySet: entitySets[j], containerName: containers[i].name, functionImport: containers[i].functionImport };
2005 }
2006 }
2007 }
2008 }
2009 return null;
2010 });
2011
2012 return info;
2013 };
2014
2015 var removeNamespace = function (ns, fullName) {
2016 /// <summary>Given an expected namespace prefix, removes it from a full name.</summary>
2017 /// <param name="ns" type="String">Expected namespace.</param>
2018 /// <param name="fullName" type="String">Full name in 'ns'.'name' form.</param>
2019 /// <returns type="String">The local name, null if it isn't found in the expected namespace.</returns>
2020
2021 if (fullName.indexOf(ns) === 0 && fullName.charAt(ns.length) === ".") {
2022 return fullName.substr(ns.length + 1);
2023 }
2024
2025 return null;
2026 };
2027
2028 var lookupInSchema = function (name, schema, kind) {
2029 /// <summary>Looks up a schema object by name.</summary>
2030 /// <param name="name" type="String">Name (assigned).</param>
2031 /// <param name="schema">Schema object as per EDM metadata.</param>
2032 /// <param name="kind" type="String">Kind of object to look for as per EDM metadata.</param>
2033 /// <returns>An entity type description if the name is found; null otherwise.</returns>
2034
2035 if (name && schema) {
2036 // The name should be the namespace qualified name in 'ns'.'type' format.
2037 var nameOnly = removeNamespace(schema["namespace"], name);
2038 if (nameOnly) {
2039 return find(schema[kind], function (item) {
2040 return item.name === nameOnly;
2041 });
2042 }
2043 }
2044 return null;
2045 };
2046
2047 var maxVersion = function (left, right) {
2048 /// <summary>Compares to version strings and returns the higher one.</summary>
2049 /// <param name="left" type="String">Version string in the form "major.minor.rev"</param>
2050 /// <param name="right" type="String">Version string in the form "major.minor.rev"</param>
2051 /// <returns type="String">The higher version string.</returns>
2052
2053 if (left === right) {
2054 return left;
2055 }
2056
2057 var leftParts = left.split(".");
2058 var rightParts = right.split(".");
2059
2060 var len = (leftParts.length >= rightParts.length) ?
2061 leftParts.length :
2062 rightParts.length;
2063
2064 for (var i = 0; i < len; i++) {
2065 var leftVersion = leftParts[i] && parseInt10(leftParts[i]);
2066 var rightVersion = rightParts[i] && parseInt10(rightParts[i]);
2067 if (leftVersion > rightVersion) {
2068 return left;
2069 }
2070 if (leftVersion < rightVersion) {
2071 return right;
2072 }
2073 }
2074 };
2075
2076 var normalHeaders = {
2077 "accept": "Accept",
2078 "content-type": "Content-Type",
2079 "dataserviceversion": "DataServiceVersion",
2080 "maxdataserviceversion": "MaxDataServiceVersion"
2081 };
2082
2083 var normalizeHeaders = function (headers) {
2084 /// <summary>Normalizes headers so they can be found with consistent casing.</summary>
2085 /// <param name="headers" type="Object">Dictionary of name/value pairs.</param>
2086
2087 for (var name in headers) {
2088 var lowerName = name.toLowerCase();
2089 var normalName = normalHeaders[lowerName];
2090 if (normalName && name !== normalName) {
2091 var val = headers[name];
2092 delete headers[name];
2093 headers[normalName] = val;
2094 }
2095 }
2096 };
2097
2098 var parseBool = function (propertyValue) {
2099 /// <summary>Parses a string into a boolean value.</summary>
2100 /// <param name="propertyValue">Value to parse.</param>
2101 /// <returns type="Boolean">true if the property value is 'true'; false otherwise.</returns>
2102
2103 if (typeof propertyValue === "boolean") {
2104 return propertyValue;
2105 }
2106
2107 return typeof propertyValue === "string" && propertyValue.toLowerCase() === "true";
2108 };
2109
2110
2111 // The captured indices for this expression are:
2112 // 0 - complete input
2113 // 1,2,3 - year with optional minus sign, month, day
2114 // 4,5,6 - hours, minutes, seconds
2115 // 7 - optional milliseconds
2116 // 8 - everything else (presumably offset information)
2117 var parseDateTimeRE = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d+))?(.*)$/;
2118
2119 var parseDateTimeMaybeOffset = function (value, withOffset, nullOnError) {
2120 /// <summary>Parses a string into a DateTime value.</summary>
2121 /// <param name="value" type="String">Value to parse.</param>
2122 /// <param name="withOffset" type="Boolean">Whether offset is expected.</param>
2123 /// <returns type="Date">The parsed value.</returns>
2124
2125 // We cannot parse this in cases of failure to match or if offset information is specified.
2126 var parts = parseDateTimeRE.exec(value);
2127 var offset = (parts) ? getCanonicalTimezone(parts[8]) : null;
2128
2129 if (!parts || (!withOffset && offset !== "Z")) {
2130 if (nullOnError) {
2131 return null;
2132 }
2133 throw { message: "Invalid date/time value" };
2134 }
2135
2136 // Pre-parse years, account for year '0' being invalid in dateTime.
2137 var year = parseInt10(parts[1]);
2138 if (year <= 0) {
2139 year++;
2140 }
2141
2142 // Pre-parse optional milliseconds, fill in default. Fail if value is too precise.
2143 var ms = parts[7];
2144 var ns = 0;
2145 if (!ms) {
2146 ms = 0;
2147 } else {
2148 if (ms.length > 7) {
2149 if (nullOnError) {
2150 return null;
2151 }
2152 throw { message: "Cannot parse date/time value to given precision." };
2153 }
2154
2155 ns = formatNumberWidth(ms.substring(3), 4, true);
2156 ms = formatNumberWidth(ms.substring(0, 3), 3, true);
2157
2158 ms = parseInt10(ms);
2159 ns = parseInt10(ns);
2160 }
2161
2162 // Pre-parse other time components and offset them if necessary.
2163 var hours = parseInt10(parts[4]);
2164 var minutes = parseInt10(parts[5]);
2165 var seconds = parseInt10(parts[6]) || 0;
2166 if (offset !== "Z") {
2167 // The offset is reversed to get back the UTC date, which is
2168 // what the API will eventually have.
2169 var timezone = parseTimezone(offset);
2170 var direction = -(timezone.d);
2171 hours += timezone.h * direction;
2172 minutes += timezone.m * direction;
2173 }
2174
2175 // Set the date and time separately with setFullYear, so years 0-99 aren't biased like in Date.UTC.
2176 var result = new Date();
2177 result.setUTCFullYear(
2178 year, // Year.
2179 parseInt10(parts[2]) - 1, // Month (zero-based for Date.UTC and setFullYear).
2180 parseInt10(parts[3]) // Date.
2181 );
2182 result.setUTCHours(hours, minutes, seconds, ms);
2183
2184 if (isNaN(result.valueOf())) {
2185 if (nullOnError) {
2186 return null;
2187 }
2188 throw { message: "Invalid date/time value" };
2189 }
2190
2191 if (withOffset) {
2192 result.__edmType = "Edm.DateTimeOffset";
2193 result.__offset = offset;
2194 }
2195
2196 if (ns) {
2197 result.__ns = ns;
2198 }
2199
2200 return result;
2201 };
2202
2203 var parseDateTime = function (propertyValue, nullOnError) {
2204 /// <summary>Parses a string into a DateTime value.</summary>
2205 /// <param name="propertyValue" type="String">Value to parse.</param>
2206 /// <returns type="Date">The parsed value.</returns>
2207
2208 return parseDateTimeMaybeOffset(propertyValue, false, nullOnError);
2209 };
2210
2211 var parseDateTimeOffset = function (propertyValue, nullOnError) {
2212 /// <summary>Parses a string into a DateTimeOffset value.</summary>
2213 /// <param name="propertyValue" type="String">Value to parse.</param>
2214 /// <returns type="Date">The parsed value.</returns>
2215 /// <remarks>
2216 /// The resulting object is annotated with an __edmType property and
2217 /// an __offset property reflecting the original intended offset of
2218 /// the value. The time is adjusted for UTC time, as the current
2219 /// timezone-aware Date APIs will only work with the local timezone.
2220 /// </remarks>
2221
2222 return parseDateTimeMaybeOffset(propertyValue, true, nullOnError);
2223 };
2224
2225 // The captured indices for this expression are:
2226 // 0 - complete input
2227 // 1 - direction
2228 // 2,3,4 - years, months, days
2229 // 5,6,7,8 - hours, minutes, seconds, miliseconds
2230
2231 var parseTimeRE = /^([+-])?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?/;
2232
2233 var isEdmDurationValue = function(value) {
2234 parseTimeRE.test(value);
2235 };
2236
2237 var parseDuration = function (duration) {
2238 /// <summary>Parses a string in xsd:duration format.</summary>
2239 /// <param name="duration" type="String">Duration value.</param>
2240 /// <remarks>
2241 /// This method will throw an exception if the input string has a year or a month component.
2242 /// </remarks>
2243 /// <returns type="Object">Object representing the time</returns>
2244
2245 var parts = parseTimeRE.exec(duration);
2246
2247 if (parts === null) {
2248 throw { message: "Invalid duration value." };
2249 }
2250
2251 var years = parts[2] || "0";
2252 var months = parts[3] || "0";
2253 var days = parseInt10(parts[4] || 0);
2254 var hours = parseInt10(parts[5] || 0);
2255 var minutes = parseInt10(parts[6] || 0);
2256 var seconds = parseFloat(parts[7] || 0);
2257
2258 if (years !== "0" || months !== "0") {
2259 throw { message: "Unsupported duration value." };
2260 }
2261
2262 var ms = parts[8];
2263 var ns = 0;
2264 if (!ms) {
2265 ms = 0;
2266 } else {
2267 if (ms.length > 7) {
2268 throw { message: "Cannot parse duration value to given precision." };
2269 }
2270
2271 ns = formatNumberWidth(ms.substring(3), 4, true);
2272 ms = formatNumberWidth(ms.substring(0, 3), 3, true);
2273
2274 ms = parseInt10(ms);
2275 ns = parseInt10(ns);
2276 }
2277
2278 ms += seconds * 1000 + minutes * 60000 + hours * 3600000 + days * 86400000;
2279
2280 if (parts[1] === "-") {
2281 ms = -ms;
2282 }
2283
2284 var result = { ms: ms, __edmType: "Edm.Time" };
2285
2286 if (ns) {
2287 result.ns = ns;
2288 }
2289 return result;
2290 };
2291
2292 var parseTimezone = function (timezone) {
2293 /// <summary>Parses a timezone description in (+|-)nn:nn format.</summary>
2294 /// <param name="timezone" type="String">Timezone offset.</param>
2295 /// <returns type="Object">
2296 /// An object with a (d)irection property of 1 for + and -1 for -,
2297 /// offset (h)ours and offset (m)inutes.
2298 /// </returns>
2299
2300 var direction = timezone.substring(0, 1);
2301 direction = (direction === "+") ? 1 : -1;
2302
2303 var offsetHours = parseInt10(timezone.substring(1));
2304 var offsetMinutes = parseInt10(timezone.substring(timezone.indexOf(":") + 1));
2305 return { d: direction, h: offsetHours, m: offsetMinutes };
2306 };
2307
2308 var prepareRequest = function (request, handler, context) {
2309 /// <summary>Prepares a request object so that it can be sent through the network.</summary>
2310 /// <param name="request">Object that represents the request to be sent.</param>
2311 /// <param name="handler">Handler for data serialization</param>
2312 /// <param name="context">Context used for preparing the request</param>
2313
2314 // Default to GET if no method has been specified.
2315 if (!request.method) {
2316 request.method = "GET";
2317 }
2318
2319 if (!request.headers) {
2320 request.headers = {};
2321 } else {
2322 normalizeHeaders(request.headers);
2323 }
2324
2325 if (request.headers.Accept === undefined) {
2326 request.headers.Accept = handler.accept;
2327 }
2328
2329 if (assigned(request.data) && request.body === undefined) {
2330 handler.write(request, context);
2331 }
2332
2333 if (!assigned(request.headers.MaxDataServiceVersion)) {
2334 request.headers.MaxDataServiceVersion = handler.maxDataServiceVersion || "1.0";
2335 }
2336 };
2337
2338 var traverseInternal = function (item, owner, callback) {
2339 /// <summary>Traverses a tree of objects invoking callback for every value.</summary>
2340 /// <param name="item" type="Object">Object or array to traverse.</param>
2341 /// <param name="callback" type="Function">
2342 /// Callback function with key and value, similar to JSON.parse reviver.
2343 /// </param>
2344 /// <returns type="Object">The object with traversed properties.</returns>
2345 /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks>
2346
2347 if (item && typeof item === "object") {
2348 for (var name in item) {
2349 var value = item[name];
2350 var result = traverseInternal(value, name, callback);
2351 result = callback(name, result, owner);
2352 if (result !== value) {
2353 if (value === undefined) {
2354 delete item[name];
2355 } else {
2356 item[name] = result;
2357 }
2358 }
2359 }
2360 }
2361
2362 return item;
2363 };
2364
2365 var traverse = function (item, callback) {
2366 /// <summary>Traverses a tree of objects invoking callback for every value.</summary>
2367 /// <param name="item" type="Object">Object or array to traverse.</param>
2368 /// <param name="callback" type="Function">
2369 /// Callback function with key and value, similar to JSON.parse reviver.
2370 /// </param>
2371 /// <returns type="Object">The traversed object.</returns>
2372 /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks>
2373
2374 return callback("", traverseInternal(item, "", callback));
2375 };
2376
2377
2378 var ticks = 0;
2379
2380 var canUseJSONP = function (request) {
2381 /// <summary>
2382 /// Checks whether the specified request can be satisfied with a JSONP request.
2383 /// </summary>
2384 /// <param name="request">Request object to check.</param>
2385 /// <returns type="Boolean">true if the request can be satisfied; false otherwise.</returns>
2386
2387 // Requests that 'degrade' without changing their meaning by going through JSONP
2388 // are considered usable.
2389 //
2390 // We allow data to come in a different format, as the servers SHOULD honor the Accept
2391 // request but may in practice return content with a different MIME type.
2392 if (request.method && request.method !== "GET") {
2393 return false;
2394 }
2395
2396 return true;
2397 };
2398
2399 var createIFrame = function (url) {
2400 /// <summary>Creates an IFRAME tag for loading the JSONP script</summary>
2401 /// <param name="url" type="String">The source URL of the script</param>
2402 /// <returns type="HTMLElement">The IFRAME tag</returns>
2403 var iframe = window.document.createElement("IFRAME");
2404 iframe.style.display = "none";
2405
2406 var attributeEncodedUrl = url.replace(/&/g, "&").replace(/"/g, """).replace(/\</g, "<");
2407 var html = "<html><head><script type=\"text/javascript\" src=\"" + attributeEncodedUrl + "\"><\/script><\/head><body><\/body><\/html>";
2408
2409 var body = window.document.getElementsByTagName("BODY")[0];
2410 body.appendChild(iframe);
2411
2412 writeHtmlToIFrame(iframe, html);
2413 return iframe;
2414 };
2415
2416 var createXmlHttpRequest = function () {
2417 /// <summary>Creates a XmlHttpRequest object.</summary>
2418 /// <returns type="XmlHttpRequest">XmlHttpRequest object.</returns>
2419 if (window.XMLHttpRequest) {
2420 return new window.XMLHttpRequest();
2421 }
2422 var exception;
2423 if (window.ActiveXObject) {
2424 try {
2425 return new window.ActiveXObject("Msxml2.XMLHTTP.6.0");
2426 } catch (_) {
2427 try {
2428 return new window.ActiveXObject("Msxml2.XMLHTTP.3.0");
2429 } catch (e) {
2430 exception = e;
2431 }
2432 }
2433 } else {
2434 exception = { message: "XMLHttpRequest not supported" };
2435 }
2436 throw exception;
2437 };
2438
2439 var isAbsoluteUrl = function (url) {
2440 /// <summary>Checks whether the specified URL is an absolute URL.</summary>
2441 /// <param name="url" type="String">URL to check.</param>
2442 /// <returns type="Boolean">true if the url is an absolute URL; false otherwise.</returns>
2443
2444 return url.indexOf("http://") === 0 ||
2445 url.indexOf("https://") === 0 ||
2446 url.indexOf("file://") === 0;
2447 };
2448
2449 var isLocalUrl = function (url) {
2450 /// <summary>Checks whether the specified URL is local to the current context.</summary>
2451 /// <param name="url" type="String">URL to check.</param>
2452 /// <returns type="Boolean">true if the url is a local URL; false otherwise.</returns>
2453
2454 if (!isAbsoluteUrl(url)) {
2455 return true;
2456 }
2457
2458 // URL-embedded username and password will not be recognized as same-origin URLs.
2459 var location = window.location;
2460 var locationDomain = location.protocol + "//" + location.host + "/";
2461 return (url.indexOf(locationDomain) === 0);
2462 };
2463
2464 var removeCallback = function (name, tick) {
2465 /// <summary>Removes a callback used for a JSONP request.</summary>
2466 /// <param name="name" type="String">Function name to remove.</param>
2467 /// <param name="tick" type="Number">Tick count used on the callback.</param>
2468 try {
2469 delete window[name];
2470 } catch (err) {
2471 window[name] = undefined;
2472 if (tick === ticks - 1) {
2473 ticks -= 1;
2474 }
2475 }
2476 };
2477
2478 var removeIFrame = function (iframe) {
2479 /// <summary>Removes an iframe.</summary>
2480 /// <param name="iframe" type="Object">The iframe to remove.</param>
2481 /// <returns type="Object">Null value to be assigned to iframe reference.</returns>
2482 if (iframe) {
2483 writeHtmlToIFrame(iframe, "");
2484 iframe.parentNode.removeChild(iframe);
2485 }
2486
2487 return null;
2488 };
2489
2490 var readResponseHeaders = function (xhr, headers) {
2491 /// <summary>Reads response headers into array.</summary>
2492 /// <param name="xhr" type="XMLHttpRequest">HTTP request with response available.</param>
2493 /// <param name="headers" type="Array">Target array to fill with name/value pairs.</param>
2494
2495 var responseHeaders = xhr.getAllResponseHeaders().split(/\r?\n/);
2496 var i, len;
2497 for (i = 0, len = responseHeaders.length; i < len; i++) {
2498 if (responseHeaders[i]) {
2499 var header = responseHeaders[i].split(": ");
2500 headers[header[0]] = header[1];
2501 }
2502 }
2503 };
2504
2505 var writeHtmlToIFrame = function (iframe, html) {
2506 /// <summary>Writes HTML to an IFRAME document.</summary>
2507 /// <param name="iframe" type="HTMLElement">The IFRAME element to write to.</param>
2508 /// <param name="html" type="String">The HTML to write.</param>
2509 var frameDocument = (iframe.contentWindow) ? iframe.contentWindow.document : iframe.contentDocument.document;
2510 frameDocument.open();
2511 frameDocument.write(html);
2512 frameDocument.close();
2513 };
2514
2515 odata.defaultHttpClient = {
2516 callbackParameterName: "$callback",
2517
2518 formatQueryString: "$format=json",
2519
2520 enableJsonpCallback: false,
2521
2522 request: function (request, success, error) {
2523 /// <summary>Performs a network request.</summary>
2524 /// <param name="request" type="Object">Request description.</request>
2525 /// <param name="success" type="Function">Success callback with the response object.</param>
2526 /// <param name="error" type="Function">Error callback with an error object.</param>
2527 /// <returns type="Object">Object with an 'abort' method for the operation.</returns>
2528
2529 var result = {};
2530 var xhr = null;
2531 var done = false;
2532 var iframe;
2533
2534 result.abort = function () {
2535 iframe = removeIFrame(iframe);
2536 if (done) {
2537 return;
2538 }
2539
2540 done = true;
2541 if (xhr) {
2542 xhr.abort();
2543 xhr = null;
2544 }
2545
2546 error({ message: "Request aborted" });
2547 };
2548
2549 var handleTimeout = function () {
2550 iframe = removeIFrame(iframe);
2551 if (!done) {
2552 done = true;
2553 xhr = null;
2554 error({ message: "Request timed out" });
2555 }
2556 };
2557
2558 var name;
2559 var url = request.requestUri;
2560 var enableJsonpCallback = defined(request.enableJsonpCallback, this.enableJsonpCallback);
2561 var callbackParameterName = defined(request.callbackParameterName, this.callbackParameterName);
2562 var formatQueryString = defined(request.formatQueryString, this.formatQueryString);
2563 if (!enableJsonpCallback || isLocalUrl(url)) {
2564
2565 xhr = createXmlHttpRequest();
2566 xhr.onreadystatechange = function () {
2567 if (done || xhr === null || xhr.readyState !== 4) {
2568 return;
2569 }
2570
2571 // Workaround for XHR behavior on IE.
2572 var statusText = xhr.statusText;
2573 var statusCode = xhr.status;
2574 if (statusCode === 1223) {
2575 statusCode = 204;
2576 statusText = "No Content";
2577 }
2578
2579 var headers = [];
2580 readResponseHeaders(xhr, headers);
2581
2582 var response = { requestUri: url, statusCode: statusCode, statusText: statusText, headers: headers, body: xhr.responseText };
2583
2584 done = true;
2585 xhr = null;
2586 if (statusCode >= 200 && statusCode <= 299) {
2587 success(response);
2588 } else {
2589 error({ message: "HTTP request failed", request: request, response: response });
2590 }
2591 };
2592
2593 xhr.open(request.method || "GET", url, true, request.user, request.password);
2594
2595 // Set the name/value pairs.
2596 if (request.headers) {
2597 for (name in request.headers) {
2598 xhr.setRequestHeader(name, request.headers[name]);
2599 }
2600 }
2601
2602 // Set the timeout if available.
2603 if (request.timeoutMS) {
2604 xhr.timeout = request.timeoutMS;
2605 xhr.ontimeout = handleTimeout;
2606 }
2607
2608 xhr.send(request.body);
2609 } else {
2610 if (!canUseJSONP(request)) {
2611 throw { message: "Request is not local and cannot be done through JSONP." };
2612 }
2613
2614 var tick = ticks;
2615 ticks += 1;
2616 var tickText = tick.toString();
2617 var succeeded = false;
2618 var timeoutId;
2619 name = "handleJSONP_" + tickText;
2620 window[name] = function (data) {
2621 iframe = removeIFrame(iframe);
2622 if (!done) {
2623 succeeded = true;
2624 window.clearTimeout(timeoutId);
2625 removeCallback(name, tick);
2626
2627 // Workaround for IE8 and IE10 below where trying to access data.constructor after the IFRAME has been removed
2628 // throws an "unknown exception"
2629 if (window.ActiveXObject) {
2630 data = window.JSON.parse(window.JSON.stringify(data));
2631 }
2632
2633
2634 var headers;
2635 // Adding dataServiceVersion in case of json light ( data.d doesn't exist )
2636 if (data.d === undefined) {
2637 headers = { "Content-Type": "application/json;odata=minimalmetadata", dataServiceVersion: "3.0" };
2638 } else {
2639 headers = { "Content-Type": "application/json" };
2640 }
2641 // Call the success callback in the context of the parent window, instead of the IFRAME
2642 delay(function () {
2643 removeIFrame(iframe);
2644 success({ body: data, statusCode: 200, headers: headers });
2645 });
2646 }
2647 };
2648
2649 // Default to two minutes before timing out, 1000 ms * 60 * 2 = 120000.
2650 var timeoutMS = (request.timeoutMS) ? request.timeoutMS : 120000;
2651 timeoutId = window.setTimeout(handleTimeout, timeoutMS);
2652
2653 var queryStringParams = callbackParameterName + "=parent." + name;
2654 if (this.formatQueryString) {
2655 queryStringParams += "&" + formatQueryString;
2656 }
2657
2658 var qIndex = url.indexOf("?");
2659 if (qIndex === -1) {
2660 url = url + "?" + queryStringParams;
2661 } else if (qIndex === url.length - 1) {
2662 url = url + queryStringParams;
2663 } else {
2664 url = url + "&" + queryStringParams;
2665 }
2666
2667 iframe = createIFrame(url);
2668 }
2669
2670 return result;
2671 }
2672 };
2673
2674
2675
2676 var MAX_DATA_SERVICE_VERSION = "3.0";
2677
2678 var contentType = function (str) {
2679 /// <summary>Parses a string into an object with media type and properties.</summary>
2680 /// <param name="str" type="String">String with media type to parse.</param>
2681 /// <returns>null if the string is empty; an object with 'mediaType' and a 'properties' dictionary otherwise.</returns>
2682
2683 if (!str) {
2684 return null;
2685 }
2686
2687 var contentTypeParts = str.split(";");
2688 var properties = {};
2689
2690 var i, len;
2691 for (i = 1, len = contentTypeParts.length; i < len; i++) {
2692 var contentTypeParams = contentTypeParts[i].split("=");
2693 properties[trimString(contentTypeParams[0])] = contentTypeParams[1];
2694 }
2695
2696 return { mediaType: trimString(contentTypeParts[0]), properties: properties };
2697 };
2698
2699 var contentTypeToString = function (contentType) {
2700 /// <summary>Serializes an object with media type and properties dictionary into a string.</summary>
2701 /// <param name="contentType">Object with media type and properties dictionary to serialize.</param>
2702 /// <returns>String representation of the media type object; undefined if contentType is null or undefined.</returns>
2703
2704 if (!contentType) {
2705 return undefined;
2706 }
2707
2708 var result = contentType.mediaType;
2709 var property;
2710 for (property in contentType.properties) {
2711 result += ";" + property + "=" + contentType.properties[property];
2712 }
2713 return result;
2714 };
2715
2716 var createReadWriteContext = function (contentType, dataServiceVersion, context, handler) {
2717 /// <summary>Creates an object that is going to be used as the context for the handler's parser and serializer.</summary>
2718 /// <param name="contentType">Object with media type and properties dictionary.</param>
2719 /// <param name="dataServiceVersion" type="String">String indicating the version of the protocol to use.</param>
2720 /// <param name="context">Operation context.</param>
2721 /// <param name="handler">Handler object that is processing a resquest or response.</param>
2722 /// <returns>Context object.</returns>
2723
2724 var rwContext = {};
2725 extend(rwContext, context);
2726 extend(rwContext, {
2727 contentType: contentType,
2728 dataServiceVersion: dataServiceVersion,
2729 handler: handler
2730 });
2731
2732 return rwContext;
2733 };
2734
2735 var fixRequestHeader = function (request, name, value) {
2736 /// <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>
2737 /// <param name="request">Request object on which the header will be set.</param>
2738 /// <param name="name" type="String">Header name.</param>
2739 /// <param name="value" type="String">Header value.</param>
2740 if (!request) {
2741 return;
2742 }
2743
2744 var headers = request.headers;
2745 if (!headers[name]) {
2746 headers[name] = value;
2747 }
2748 };
2749
2750 var fixDataServiceVersionHeader = function (request, version) {
2751 /// <summary>Sets the DataServiceVersion header of the request if its value is not yet defined or of a lower version.</summary>
2752 /// <param name="request">Request object on which the header will be set.</param>
2753 /// <param name="version" type="String">Version value.</param>
2754 /// <remarks>
2755 /// If the request has already a version value higher than the one supplied the this function does nothing.
2756 /// </remarks>
2757
2758 if (request) {
2759 var headers = request.headers;
2760 var dsv = headers["DataServiceVersion"];
2761 headers["DataServiceVersion"] = dsv ? maxVersion(dsv, version) : version;
2762 }
2763 };
2764
2765 var getRequestOrResponseHeader = function (requestOrResponse, name) {
2766 /// <summary>Gets the value of a request or response header.</summary>
2767 /// <param name="requestOrResponse">Object representing a request or a response.</param>
2768 /// <param name="name" type="String">Name of the header to retrieve.</param>
2769 /// <returns type="String">String value of the header; undefined if the header cannot be found.</returns>
2770
2771 var headers = requestOrResponse.headers;
2772 return (headers && headers[name]) || undefined;
2773 };
2774
2775 var getContentType = function (requestOrResponse) {
2776 /// <summary>Gets the value of the Content-Type header from a request or response.</summary>
2777 /// <param name="requestOrResponse">Object representing a request or a response.</param>
2778 /// <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>
2779
2780 return contentType(getRequestOrResponseHeader(requestOrResponse, "Content-Type"));
2781 };
2782
2783 var versionRE = /^\s?(\d+\.\d+);?.*$/;
2784 var getDataServiceVersion = function (requestOrResponse) {
2785 /// <summary>Gets the value of the DataServiceVersion header from a request or response.</summary>
2786 /// <param name="requestOrResponse">Object representing a request or a response.</param>
2787 /// <returns type="String">Data service version; undefined if the header cannot be found.</returns>
2788
2789 var value = getRequestOrResponseHeader(requestOrResponse, "DataServiceVersion");
2790 if (value) {
2791 var matches = versionRE.exec(value);
2792 if (matches && matches.length) {
2793 return matches[1];
2794 }
2795 }
2796
2797 // Fall through and return undefined.
2798 };
2799
2800 var handlerAccepts = function (handler, cType) {
2801 /// <summary>Checks that a handler can process a particular mime type.</summary>
2802 /// <param name="handler">Handler object that is processing a resquest or response.</param>
2803 /// <param name="cType">Object with 'mediaType' and a 'properties' dictionary.</param>
2804 /// <returns type="Boolean">True if the handler can process the mime type; false otherwise.</returns>
2805
2806 // The following check isn't as strict because if cType.mediaType = application/; it will match an accept value of "application/xml";
2807 // however in practice we don't not expect to see such "suffixed" mimeTypes for the handlers.
2808 return handler.accept.indexOf(cType.mediaType) >= 0;
2809 };
2810
2811 var handlerRead = function (handler, parseCallback, response, context) {
2812 /// <summary>Invokes the parser associated with a handler for reading the payload of a HTTP response.</summary>
2813 /// <param name="handler">Handler object that is processing the response.</param>
2814 /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param>
2815 /// <param name="response">HTTP response whose payload is going to be processed.</param>
2816 /// <param name="context">Object used as the context for processing the response.</param>
2817 /// <returns type="Boolean">True if the handler processed the response payload and the response.data property was set; false otherwise.</returns>
2818
2819 if (!response || !response.headers) {
2820 return false;
2821 }
2822
2823 var cType = getContentType(response);
2824 var version = getDataServiceVersion(response) || "";
2825 var body = response.body;
2826
2827 if (!assigned(body)) {
2828 return false;
2829 }
2830
2831 if (handlerAccepts(handler, cType)) {
2832 var readContext = createReadWriteContext(cType, version, context, handler);
2833 readContext.response = response;
2834 response.data = parseCallback(handler, body, readContext);
2835 return response.data !== undefined;
2836 }
2837
2838 return false;
2839 };
2840
2841 var handlerWrite = function (handler, serializeCallback, request, context) {
2842 /// <summary>Invokes the serializer associated with a handler for generating the payload of a HTTP request.</summary>
2843 /// <param name="handler">Handler object that is processing the request.</param>
2844 /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param>
2845 /// <param name="response">HTTP request whose payload is going to be generated.</param>
2846 /// <param name="context">Object used as the context for serializing the request.</param>
2847 /// <returns type="Boolean">True if the handler serialized the request payload and the request.body property was set; false otherwise.</returns>
2848 if (!request || !request.headers) {
2849 return false;
2850 }
2851
2852 var cType = getContentType(request);
2853 var version = getDataServiceVersion(request);
2854
2855 if (!cType || handlerAccepts(handler, cType)) {
2856 var writeContext = createReadWriteContext(cType, version, context, handler);
2857 writeContext.request = request;
2858
2859 request.body = serializeCallback(handler, request.data, writeContext);
2860
2861 if (request.body !== undefined) {
2862 fixDataServiceVersionHeader(request, writeContext.dataServiceVersion || "1.0");
2863
2864 fixRequestHeader(request, "Content-Type", contentTypeToString(writeContext.contentType));
2865 fixRequestHeader(request, "MaxDataServiceVersion", handler.maxDataServiceVersion);
2866 return true;
2867 }
2868 }
2869
2870 return false;
2871 };
2872
2873 var handler = function (parseCallback, serializeCallback, accept, maxDataServiceVersion) {
2874 /// <summary>Creates a handler object for processing HTTP requests and responses.</summary>
2875 /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param>
2876 /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param>
2877 /// <param name="accept" type="String">String containing a comma separated list of the mime types that this handler can work with.</param>
2878 /// <param name="maxDataServiceVersion" type="String">String indicating the highest version of the protocol that this handler can work with.</param>
2879 /// <returns type="Object">Handler object.</returns>
2880
2881 return {
2882 accept: accept,
2883 maxDataServiceVersion: maxDataServiceVersion,
2884
2885 read: function (response, context) {
2886 return handlerRead(this, parseCallback, response, context);
2887 },
2888
2889 write: function (request, context) {
2890 return handlerWrite(this, serializeCallback, request, context);
2891 }
2892 };
2893 };
2894
2895 var textParse = function (handler, body /*, context */) {
2896 return body;
2897 };
2898
2899 var textSerialize = function (handler, data /*, context */) {
2900 if (assigned(data)) {
2901 return data.toString();
2902 } else {
2903 return undefined;
2904 }
2905 };
2906
2907 odata.textHandler = handler(textParse, textSerialize, "text/plain", MAX_DATA_SERVICE_VERSION);
2908
2909
2910 var gmlOpenGis = http + "www.opengis.net"; // http://www.opengis.net
2911 var gmlXmlNs = gmlOpenGis + "/gml"; // http://www.opengis.net/gml
2912 var gmlSrsPrefix = gmlOpenGis + "/def/crs/EPSG/0/"; // http://www.opengis.net/def/crs/EPSG/0/
2913
2914 var gmlPrefix = "gml";
2915
2916 var gmlCreateGeoJSONOBject = function (type, member, data) {
2917 /// <summary>Creates a GeoJSON object with the specified type, member and value.</summary>
2918 /// <param name="type" type="String">GeoJSON object type.</param>
2919 /// <param name="member" type="String">Name for the data member in the GeoJSON object.</param>
2920 /// <param name="data">Data to be contained by the GeoJSON object.</param>
2921 /// <returns type="Object">GeoJSON object.</returns>
2922
2923 var result = { type: type };
2924 result[member] = data;
2925 return result;
2926 };
2927
2928 var gmlSwapLatLong = function (coordinates) {
2929 /// <summary>Swaps the longitude and latitude in the coordinates array.</summary>
2930 /// <param name="coordinates" type="Array">Array of doubles descrbing a set of coordinates.</param>
2931 /// <returns type="Array">Array of doubles with the latitude and longitude components swapped.</returns>
2932
2933 if (isArray(coordinates) && coordinates.length >= 2) {
2934 var tmp = coordinates[0];
2935 coordinates[0] = coordinates[1];
2936 coordinates[1] = tmp;
2937 }
2938 return coordinates;
2939 };
2940
2941 var gmlReadODataMultiItem = function (domElement, type, member, members, valueReader, isGeography) {
2942 /// <summary>
2943 /// Reads a GML DOM element that represents a composite structure like a multi-point or a
2944 /// multi-geometry returnig its GeoJSON representation.
2945 /// </summary>
2946 /// <param name="domElement">GML DOM element.</param>
2947 /// <param name="type" type="String">GeoJSON object type.</param>
2948 /// <param name="member" type="String">Name for the child element representing a single item in the composite structure.</param>
2949 /// <param name="members" type="String">Name for the child element representing a collection of items in the composite structure.</param>
2950 /// <param name="valueReader" type="Function">Callback function invoked to get the coordinates of each item in the comoposite structure.</param>
2951 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
2952 /// <remarks>
2953 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
2954 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
2955 /// </remarks>
2956 /// <returns type="Object">GeoJSON object.</returns>
2957
2958 var coordinates = gmlReadODataMultiItemValue(domElement, member, members, valueReader, isGeography);
2959 return gmlCreateGeoJSONOBject(type, "coordinates", coordinates);
2960 };
2961
2962 var gmlReadODataMultiItemValue = function (domElement, member, members, valueReader, isGeography) {
2963 /// <summary>
2964 /// Reads the value of a GML DOM element that represents a composite structure like a multi-point or a
2965 /// multi-geometry returnig its items.
2966 /// </summary>
2967 /// <param name="domElement">GML DOM element.</param>
2968 /// <param name="type" type="String">GeoJSON object type.</param>
2969 /// <param name="member" type="String">Name for the child element representing a single item in the composite structure.</param>
2970 /// <param name="members" type="String">Name for the child element representing a collection of items in the composite structure.</param>
2971 /// <param name="valueReader" type="Function">Callback function invoked to get the transformed value of each item in the comoposite structure.</param>
2972 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
2973 /// <remarks>
2974 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
2975 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
2976 /// </remarks>
2977 /// <returns type="Array">Array containing the transformed value of each item in the multi-item.</returns>
2978
2979 var items = [];
2980
2981 xmlChildElements(domElement, function (child) {
2982 if (xmlNamespaceURI(child) !== gmlXmlNs) {
2983 return;
2984 }
2985
2986 var localName = xmlLocalName(child);
2987
2988 if (localName === member) {
2989 var valueElement = xmlFirstChildElement(child, gmlXmlNs);
2990 if (valueElement) {
2991 var value = valueReader(valueElement, isGeography);
2992 if (value) {
2993 items.push(value);
2994 }
2995 }
2996 return;
2997 }
2998
2999 if (localName === members) {
3000 xmlChildElements(child, function (valueElement) {
3001 if (xmlNamespaceURI(valueElement) !== gmlXmlNs) {
3002 return;
3003 }
3004
3005 var value = valueReader(valueElement, isGeography);
3006 if (value) {
3007 items.push(value);
3008 }
3009 });
3010 }
3011 });
3012 return items;
3013 };
3014
3015 var gmlReadODataCollection = function (domElement, isGeography) {
3016 /// <summary>Reads a GML DOM element representing a multi-geometry returning its GeoJSON representation.</summary>
3017 /// <param name="domElement">DOM element.</param>
3018 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3019 /// <remarks>
3020 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3021 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3022 /// </remarks>
3023 /// <returns type="Object">MultiGeometry object in GeoJSON format.</returns>
3024
3025 var geometries = gmlReadODataMultiItemValue(domElement, "geometryMember", "geometryMembers", gmlReadODataSpatialValue, isGeography);
3026 return gmlCreateGeoJSONOBject(GEOJSON_GEOMETRYCOLLECTION, "geometries", geometries);
3027 };
3028
3029 var gmlReadODataLineString = function (domElement, isGeography) {
3030 /// <summary>Reads a GML DOM element representing a line string returning its GeoJSON representation.</summary>
3031 /// <param name="domElement">DOM element.</param>
3032 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3033 /// <remarks>
3034 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3035 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3036 /// </remarks>
3037 /// <returns type="Object">LineString object in GeoJSON format.</returns>
3038
3039 return gmlCreateGeoJSONOBject(GEOJSON_LINESTRING, "coordinates", gmlReadODataLineValue(domElement, isGeography));
3040 };
3041
3042 var gmlReadODataMultiLineString = function (domElement, isGeography) {
3043 /// <summary>Reads a GML DOM element representing a multi-line string returning its GeoJSON representation.</summary>
3044 /// <param name="domElement">DOM element.</param>
3045 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3046 /// <remarks>
3047 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3048 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3049 /// </remarks>
3050 /// <returns type="Object">MultiLineString object in GeoJSON format.</returns>
3051
3052 return gmlReadODataMultiItem(domElement, GEOJSON_MULTILINESTRING, "curveMember", "curveMembers", gmlReadODataLineValue, isGeography);
3053 };
3054
3055 var gmlReadODataMultiPoint = function (domElement, isGeography) {
3056 /// <summary>Reads a GML DOM element representing a multi-point returning its GeoJSON representation.</summary>
3057 /// <param name="domElement">DOM element.</param>
3058 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3059 /// <remarks>
3060 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3061 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3062 /// </remarks>
3063 /// <returns type="Object">MultiPoint object in GeoJSON format.</returns>
3064
3065 return gmlReadODataMultiItem(domElement, GEOJSON_MULTIPOINT, "pointMember", "pointMembers", gmlReadODataPointValue, isGeography);
3066 };
3067
3068 var gmlReadODataMultiPolygon = function (domElement, isGeography) {
3069 /// <summary>Reads a GML DOM element representing a multi-polygon returning its GeoJSON representation.</summary>
3070 /// <param name="domElement">DOM element.</param>
3071 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3072 /// <remarks>
3073 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3074 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3075 /// </remarks>
3076 /// <returns type="Object">MultiPolygon object in GeoJSON format.</returns>
3077
3078 return gmlReadODataMultiItem(domElement, GEOJSON_MULTIPOLYGON, "surfaceMember", "surfaceMembers", gmlReadODataPolygonValue, isGeography);
3079 };
3080
3081 var gmlReadODataPoint = function (domElement, isGeography) {
3082 /// <summary>Reads a GML DOM element representing a point returning its GeoJSON representation.</summary>
3083 /// <param name="domElement">DOM element.</param>
3084 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3085 /// <remarks>
3086 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3087 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3088 /// </remarks>
3089 /// <returns type="Object">Point object in GeoJSON format.</returns>
3090
3091 return gmlCreateGeoJSONOBject(GEOJSON_POINT, "coordinates", gmlReadODataPointValue(domElement, isGeography));
3092 };
3093
3094 var gmlReadODataPolygon = function (domElement, isGeography) {
3095 /// <summary>Reads a GML DOM element representing a polygon returning its GeoJSON representation.</summary>
3096 /// <param name="domElement">DOM element.</param>
3097 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3098 /// <remarks>
3099 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3100 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3101 /// </remarks>
3102 /// <returns type="Object">Polygon object in GeoJSON format.</returns>
3103
3104 return gmlCreateGeoJSONOBject(GEOJSON_POLYGON, "coordinates", gmlReadODataPolygonValue(domElement, isGeography));
3105 };
3106
3107 var gmlReadODataLineValue = function (domElement, isGeography) {
3108 /// <summary>Reads the value of a GML DOM element representing a line returning its set of coordinates.</summary>
3109 /// <param name="domElement">DOM element.</param>
3110 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3111 /// <remarks>
3112 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3113 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3114 /// </remarks>
3115 /// <returns type="Array">Array containing an array of doubles for each coordinate of the line.</returns>
3116
3117 var coordinates = [];
3118
3119 xmlChildElements(domElement, function (child) {
3120 var nsURI = xmlNamespaceURI(child);
3121
3122 if (nsURI !== gmlXmlNs) {
3123 return;
3124 }
3125
3126 var localName = xmlLocalName(child);
3127
3128 if (localName === "posList") {
3129 coordinates = gmlReadODataPosListValue(child, isGeography);
3130 return;
3131 }
3132 if (localName === "pointProperty") {
3133 coordinates.push(gmlReadODataPointWrapperValue(child, isGeography));
3134 return;
3135 }
3136 if (localName === "pos") {
3137 coordinates.push(gmlReadODataPosValue(child, isGeography));
3138 return;
3139 }
3140 });
3141
3142 return coordinates;
3143 };
3144
3145 var gmlReadODataPointValue = function (domElement, isGeography) {
3146 /// <summary>Reads the value of a GML DOM element representing a point returning its coordinates.</summary>
3147 /// <param name="domElement">DOM element.</param>
3148 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3149 /// <remarks>
3150 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3151 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3152 /// </remarks>
3153 /// <returns type="Array">Array of doubles containing the point coordinates.</returns>
3154
3155 var pos = xmlFirstChildElement(domElement, gmlXmlNs, "pos");
3156 return pos ? gmlReadODataPosValue(pos, isGeography) : [];
3157 };
3158
3159 var gmlReadODataPointWrapperValue = function (domElement, isGeography) {
3160 /// <summary>Reads the value of a GML DOM element wrapping an element representing a point returning its coordinates.</summary>
3161 /// <param name="domElement">DOM element.</param>
3162 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3163 /// <remarks>
3164 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3165 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3166 /// </remarks>
3167 /// <returns type="Array">Array of doubles containing the point coordinates.</returns>
3168
3169 var point = xmlFirstChildElement(domElement, gmlXmlNs, "Point");
3170 return point ? gmlReadODataPointValue(point, isGeography) : [];
3171 };
3172
3173 var gmlReadODataPolygonValue = function (domElement, isGeography) {
3174 /// <summary>Reads the value of a GML DOM element representing a polygon returning its set of coordinates.</summary>
3175 /// <param name="domElement">DOM element.</param>
3176 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3177 /// <remarks>
3178 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3179 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3180 /// </remarks>
3181 /// <returns type="Array">Array containing an array of array of doubles for each ring of the polygon.</returns>
3182
3183 var coordinates = [];
3184 var exteriorFound = false;
3185 xmlChildElements(domElement, function (child) {
3186 if (xmlNamespaceURI(child) !== gmlXmlNs) {
3187 return;
3188 }
3189
3190 // Only the exterior and the interior rings are interesting
3191 var localName = xmlLocalName(child);
3192 if (localName === "exterior") {
3193 exteriorFound = true;
3194 coordinates.unshift(gmlReadODataPolygonRingValue(child, isGeography));
3195 return;
3196 }
3197 if (localName === "interior") {
3198 coordinates.push(gmlReadODataPolygonRingValue(child, isGeography));
3199 return;
3200 }
3201 });
3202
3203 if (!exteriorFound && coordinates.length > 0) {
3204 // Push an empty exterior ring.
3205 coordinates.unshift([[]]);
3206 }
3207
3208 return coordinates;
3209 };
3210
3211 var gmlReadODataPolygonRingValue = function (domElement, isGeography) {
3212 /// <summary>Reads the value of a GML DOM element representing a linear ring in a GML Polygon element.</summary>
3213 /// <param name="domElement">DOM element.</param>
3214 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3215 /// <remarks>
3216 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3217 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3218 /// </remarks>
3219 /// <returns type="Array">Array containing an array of doubles for each coordinate of the linear ring.</returns>
3220
3221 var value = [];
3222 xmlChildElements(domElement, function (child) {
3223 if (xmlNamespaceURI(child) !== gmlXmlNs || xmlLocalName(child) !== "LinearRing") {
3224 return;
3225 }
3226 value = gmlReadODataLineValue(child, isGeography);
3227 });
3228 return value;
3229 };
3230
3231 var gmlReadODataPosListValue = function (domElement, isGeography) {
3232 /// <summary>Reads the value of a GML DOM element representing a list of positions retruning its set of coordinates.</summary>
3233 /// <param name="domElement">DOM element.</param>
3234 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3235 /// <remarks>
3236 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3237 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3238 ///
3239 /// The positions described by the list are assumed to be 2D, so
3240 /// an exception will be thrown if the list has an odd number elements.
3241 /// </remarks>
3242 /// <returns type="Array">Array containing an array of doubles for each coordinate in the list.</returns>
3243
3244 var coordinates = gmlReadODataPosValue(domElement, false);
3245 var len = coordinates.length;
3246
3247 if (len % 2 !== 0) {
3248 throw { message: "GML posList element has an uneven number of numeric values" };
3249 }
3250
3251 var value = [];
3252 for (var i = 0; i < len; i += 2) {
3253 var pos = coordinates.slice(i, i + 2);
3254 value.push(isGeography ? gmlSwapLatLong(pos) : pos);
3255 }
3256 return value;
3257 };
3258
3259 var gmlReadODataPosValue = function (domElement, isGeography) {
3260 /// <summary>Reads the value of a GML element describing a position or a set of coordinates in an OData spatial property value.</summary>
3261 /// <param name="property">DOM element for the GML element.</param>
3262 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3263 /// <remarks>
3264 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3265 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3266 /// </remarks>
3267 /// <returns type="Array">Array of doubles containing the coordinates.</returns>
3268
3269 var value = [];
3270 var delims = " \t\r\n";
3271 var text = xmlInnerText(domElement);
3272
3273 if (text) {
3274 var len = text.length;
3275 var start = 0;
3276 var end = 0;
3277
3278 while (end <= len) {
3279 if (delims.indexOf(text.charAt(end)) !== -1) {
3280 var coord = text.substring(start, end);
3281 if (coord) {
3282 value.push(parseFloat(coord));
3283 }
3284 start = end + 1;
3285 }
3286 end++;
3287 }
3288 }
3289
3290 return isGeography ? gmlSwapLatLong(value) : value;
3291 };
3292
3293 var gmlReadODataSpatialValue = function (domElement, isGeography) {
3294 /// <summary>Reads the value of a GML DOM element a spatial value in an OData XML document.</summary>
3295 /// <param name="domElement">DOM element.</param>
3296 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3297 /// <remarks>
3298 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3299 /// will be deserialized as the second component of each position coordinates in the resulting GeoJSON object.
3300 /// </remarks>
3301 /// <returns type="Array">Array containing an array of doubles for each coordinate of the polygon.</returns>
3302
3303 var localName = xmlLocalName(domElement);
3304 var reader;
3305
3306 switch (localName) {
3307 case "Point":
3308 reader = gmlReadODataPoint;
3309 break;
3310 case "Polygon":
3311 reader = gmlReadODataPolygon;
3312 break;
3313 case "LineString":
3314 reader = gmlReadODataLineString;
3315 break;
3316 case "MultiPoint":
3317 reader = gmlReadODataMultiPoint;
3318 break;
3319 case "MultiCurve":
3320 reader = gmlReadODataMultiLineString;
3321 break;
3322 case "MultiSurface":
3323 reader = gmlReadODataMultiPolygon;
3324 break;
3325 case "MultiGeometry":
3326 reader = gmlReadODataCollection;
3327 break;
3328 default:
3329 throw { message: "Unsupported element: " + localName, element: domElement };
3330 }
3331
3332 var value = reader(domElement, isGeography);
3333 // Read the CRS
3334 // WCF Data Services qualifies the srsName attribute withing the GML namespace; however
3335 // other end points might no do this as per the standard.
3336
3337 var srsName = xmlAttributeValue(domElement, "srsName", gmlXmlNs) ||
3338 xmlAttributeValue(domElement, "srsName");
3339
3340 if (srsName) {
3341 if (srsName.indexOf(gmlSrsPrefix) !== 0) {
3342 throw { message: "Unsupported srs name: " + srsName, element: domElement };
3343 }
3344
3345 var crsId = srsName.substring(gmlSrsPrefix.length);
3346 if (crsId) {
3347 value.crs = {
3348 type: "name",
3349 properties: {
3350 name: "EPSG:" + crsId
3351 }
3352 };
3353 }
3354 }
3355 return value;
3356 };
3357
3358 var gmlNewODataSpatialValue = function (dom, value, type, isGeography) {
3359 /// <summary>Creates a new GML DOM element for the value of an OData spatial property or GeoJSON object.</summary>
3360 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3361 /// <param name="value" type="Object">Spatial property value in GeoJSON format.</param>
3362 /// <param name="type" type="String">String indicating the GeoJSON type of the value to serialize.</param>
3363 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3364 /// <remarks>
3365 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3366 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3367 /// </remarks>
3368 /// <returns>New DOM element in the GML namespace for the spatial value. </returns>
3369
3370 var gmlWriter;
3371
3372 switch (type) {
3373 case GEOJSON_POINT:
3374 gmlWriter = gmlNewODataPoint;
3375 break;
3376 case GEOJSON_LINESTRING:
3377 gmlWriter = gmlNewODataLineString;
3378 break;
3379 case GEOJSON_POLYGON:
3380 gmlWriter = gmlNewODataPolygon;
3381 break;
3382 case GEOJSON_MULTIPOINT:
3383 gmlWriter = gmlNewODataMultiPoint;
3384 break;
3385 case GEOJSON_MULTILINESTRING:
3386 gmlWriter = gmlNewODataMultiLineString;
3387 break;
3388 case GEOJSON_MULTIPOLYGON:
3389 gmlWriter = gmlNewODataMultiPolygon;
3390 break;
3391 case GEOJSON_GEOMETRYCOLLECTION:
3392 gmlWriter = gmlNewODataGeometryCollection;
3393 break;
3394 default:
3395 return null;
3396 }
3397
3398 var gml = gmlWriter(dom, value, isGeography);
3399
3400 // Set the srsName attribute if applicable.
3401 var crs = value.crs;
3402 if (crs) {
3403 if (crs.type === "name") {
3404 var properties = crs.properties;
3405 var name = properties && properties.name;
3406 if (name && name.indexOf("ESPG:") === 0 && name.length > 5) {
3407 var crsId = name.substring(5);
3408 var srsName = xmlNewAttribute(dom, null, "srsName", gmlPrefix + crsId);
3409 xmlAppendChild(gml, srsName);
3410 }
3411 }
3412 }
3413
3414 return gml;
3415 };
3416
3417 var gmlNewODataElement = function (dom, name, children) {
3418 /// <summary>Creates a new DOM element in the GML namespace.</summary>
3419 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3420 /// <param name="name" type="String">Local name of the GML element to create.</param>
3421 /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
3422 /// <returns>New DOM element in the GML namespace.</returns>
3423 /// <remarks>
3424 /// If a value in the children collection is a string, then a new DOM text node is going to be created
3425 /// for it and then appended as a child of the new DOM Element.
3426 /// </remarks>
3427
3428 return xmlNewElement(dom, gmlXmlNs, xmlQualifiedName(gmlPrefix, name), children);
3429 };
3430
3431 var gmlNewODataPosElement = function (dom, coordinates, isGeography) {
3432 /// <summary>Creates a new GML pos DOM element.</summary>
3433 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3434 /// <param name="coordinates" type="Array">Array of doubles describing the coordinates of the pos element.</param>
3435 /// <param name="isGeography" type="Boolean">Flag indicating if the coordinates use a geographic reference system or not.<param>
3436 /// <remarks>
3437 /// When using a geographic reference system, the first coordinate is the Longitude and
3438 /// will be serialized as the second component of the <pos> element in the GML DOM tree.
3439 /// </remarks>
3440 /// <returns>New pos DOM element in the GML namespace.</returns>
3441
3442 var posValue = isArray(coordinates) ? coordinates : [];
3443
3444 // If using a geographic reference system, then the first coordinate is the longitude and it has to
3445 // swapped with the latitude.
3446 posValue = isGeography ? gmlSwapLatLong(posValue) : posValue;
3447
3448 return gmlNewODataElement(dom, "pos", posValue.join(" "));
3449 };
3450
3451 var gmlNewODataLineElement = function (dom, name, coordinates, isGeography) {
3452 /// <summary>Creates a new GML DOM element representing a line.</summary>
3453 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3454 /// <param name="name" type="String">Name of the element to create.</param>
3455 /// <param name="coordinates" type="Array">Array of array of doubles describing the coordinates of the line element.</param>
3456 /// <param name="isGeography" type="Boolean">Flag indicating if the coordinates use a geographic reference system or not.<param>
3457 /// <remarks>
3458 /// When using a geographic reference system, the first component of all the coordinates is the Longitude and
3459 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3460 /// </remarks>
3461 /// <returns>New DOM element in the GML namespace.</returns>
3462
3463 var element = gmlNewODataElement(dom, name);
3464 if (isArray(coordinates)) {
3465 var i, len;
3466 for (i = 0, len = coordinates.length; i < len; i++) {
3467 xmlAppendChild(element, gmlNewODataPosElement(dom, coordinates[i], isGeography));
3468 }
3469
3470 if (len === 0) {
3471 xmlAppendChild(element, gmlNewODataElement(dom, "posList"));
3472 }
3473 }
3474 return element;
3475 };
3476
3477 var gmlNewODataPointElement = function (dom, coordinates, isGeography) {
3478 /// <summary>Creates a new GML Point DOM element.</summary>
3479 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3480 /// <param name="value" type="Object">GeoJSON Point object.</param>
3481 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3482 /// <remarks>
3483 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3484 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3485 /// </remarks>
3486 /// <returns>New DOM element in the GML namespace for the GeoJSON Point.</returns>
3487
3488 return gmlNewODataElement(dom, "Point", gmlNewODataPosElement(dom, coordinates, isGeography));
3489 };
3490
3491 var gmlNewODataLineStringElement = function (dom, coordinates, isGeography) {
3492 /// <summary>Creates a new GML LineString DOM element.</summary>
3493 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3494 /// <param name="coordinates" type="Array">Array of array of doubles describing the coordinates of the line element.</param>
3495 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3496 /// <remarks>
3497 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3498 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3499 /// </remarks>
3500 /// <returns>New DOM element in the GML namespace for the GeoJSON LineString.</returns>
3501
3502 return gmlNewODataLineElement(dom, "LineString", coordinates, isGeography);
3503 };
3504
3505 var gmlNewODataPolygonRingElement = function (dom, name, coordinates, isGeography) {
3506 /// <summary>Creates a new GML DOM element representing a polygon ring.</summary>
3507 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3508 /// <param name="name" type="String">Name of the element to create.</param>
3509 /// <param name="coordinates" type="Array">Array of array of doubles describing the coordinates of the polygon ring.</param>
3510 /// <param name="isGeography" type="Boolean">Flag indicating if the coordinates use a geographic reference system or not.<param>
3511 /// <remarks>
3512 /// When using a geographic reference system, the first component of all the coordinates is the Longitude and
3513 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3514 /// </remarks>
3515 /// <returns>New DOM element in the GML namespace.</returns>
3516
3517 var ringElement = gmlNewODataElement(dom, name);
3518 if (isArray(coordinates) && coordinates.length > 0) {
3519 var linearRing = gmlNewODataLineElement(dom, "LinearRing", coordinates, isGeography);
3520 xmlAppendChild(ringElement, linearRing);
3521 }
3522 return ringElement;
3523 };
3524
3525 var gmlNewODataPolygonElement = function (dom, coordinates, isGeography) {
3526 /// <summary>Creates a new GML Polygon DOM element for a GeoJSON Polygon object.</summary>
3527 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3528 /// <param name="coordinates" type="Array">Array of array of array of doubles describing the coordinates of the polygon.</param>
3529 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3530 /// <remarks>
3531 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3532 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3533 /// </remarks>
3534 /// <returns>New DOM element in the GML namespace.</returns>
3535
3536 var len = coordinates && coordinates.length;
3537 var element = gmlNewODataElement(dom, "Polygon");
3538
3539 if (isArray(coordinates) && len > 0) {
3540 xmlAppendChild(element, gmlNewODataPolygonRingElement(dom, "exterior", coordinates[0], isGeography));
3541
3542 var i;
3543 for (i = 1; i < len; i++) {
3544 xmlAppendChild(element, gmlNewODataPolygonRingElement(dom, "interior", coordinates[i], isGeography));
3545 }
3546 }
3547 return element;
3548 };
3549
3550 var gmlNewODataPoint = function (dom, value, isGeography) {
3551 /// <summary>Creates a new GML Point DOM element for a GeoJSON Point object.</summary>
3552 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3553 /// <param name="value" type="Object">GeoJSON Point object.</param>
3554 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3555 /// <remarks>
3556 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3557 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3558 /// </remarks>
3559 /// <returns>New DOM element in the GML namespace for the GeoJSON Point.</returns>
3560
3561 return gmlNewODataPointElement(dom, value.coordinates, isGeography);
3562 };
3563
3564 var gmlNewODataLineString = function (dom, value, isGeography) {
3565 /// <summary>Creates a new GML LineString DOM element for a GeoJSON LineString object.</summary>
3566 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3567 /// <param name="value" type="Object">GeoJSON LineString object.</param>
3568 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3569 /// <remarks>
3570 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3571 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3572 /// </remarks>
3573 /// <returns>New DOM element in the GML namespace for the GeoJSON LineString.</returns>
3574
3575 return gmlNewODataLineStringElement(dom, value.coordinates, isGeography);
3576 };
3577
3578 var gmlNewODataPolygon = function (dom, value, isGeography) {
3579 /// <summary>Creates a new GML Polygon DOM element for a GeoJSON Polygon object.</summary>
3580 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3581 /// <param name="value" type="Object">GeoJSON Polygon object.</param>
3582 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3583 /// <remarks>
3584 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3585 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3586 /// </remarks>
3587 /// <returns>New DOM element in the GML namespace for the GeoJSON Polygon.</returns>
3588
3589 return gmlNewODataPolygonElement(dom, value.coordinates, isGeography);
3590 };
3591
3592 var gmlNewODataMultiItem = function (dom, name, members, items, itemWriter, isGeography) {
3593 /// <summary>Creates a new GML DOM element for a composite structure like a multi-point or a multi-geometry.</summary>
3594 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3595 /// <param name="name" type="String">Name of the element to create.</param>
3596 /// <param name="items" type="Array">Array of items in the composite structure.</param>
3597 /// <param name="isGeography" type="Boolean">Flag indicating if the multi-item uses a geographic reference system or not.<param>
3598 /// <remarks>
3599 /// When using a geographic reference system, the first component of all the coordinates in each of the items is the Longitude and
3600 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3601 /// </remarks>
3602 /// <returns>New DOM element in the GML namespace.</returns>
3603
3604 var len = items && items.length;
3605 var element = gmlNewODataElement(dom, name);
3606
3607 if (isArray(items) && len > 0) {
3608 var membersElement = gmlNewODataElement(dom, members);
3609 var i;
3610 for (i = 0; i < len; i++) {
3611 xmlAppendChild(membersElement, itemWriter(dom, items[i], isGeography));
3612 }
3613 xmlAppendChild(element, membersElement);
3614 }
3615 return element;
3616 };
3617
3618 var gmlNewODataMultiPoint = function (dom, value, isGeography) {
3619 /// <summary>Creates a new GML MultiPoint DOM element for a GeoJSON MultiPoint object.</summary>
3620 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3621 /// <param name="value" type="Object">GeoJSON MultiPoint object.</param>
3622 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3623 /// <remarks>
3624 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3625 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3626 /// </remarks>
3627 /// <returns>New DOM element in the GML namespace for the GeoJSON MultiPoint.</returns>
3628
3629 return gmlNewODataMultiItem(dom, "MultiPoint", "pointMembers", value.coordinates, gmlNewODataPointElement, isGeography);
3630 };
3631
3632 var gmlNewODataMultiLineString = function (dom, value, isGeography) {
3633 /// <summary>Creates a new GML MultiCurve DOM element for a GeoJSON MultiLineString object.</summary>
3634 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3635 /// <param name="value" type="Object">GeoJSON MultiLineString object.</param>
3636 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3637 /// <remarks>
3638 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3639 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3640 /// </remarks>
3641 /// <returns>New DOM element in the GML namespace for the GeoJSON MultiLineString.</returns>
3642
3643 return gmlNewODataMultiItem(dom, "MultiCurve", "curveMembers", value.coordinates, gmlNewODataLineStringElement, isGeography);
3644 };
3645
3646 var gmlNewODataMultiPolygon = function (dom, value, isGeography) {
3647 /// <summary>Creates a new GML MultiSurface DOM element for a GeoJSON MultiPolygon object.</summary>
3648 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3649 /// <param name="value" type="Object">GeoJSON MultiPolygon object.</param>
3650 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3651 /// <remarks>
3652 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3653 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3654 /// </remarks>
3655 /// <returns>New DOM element in the GML namespace for the GeoJSON MultiPolygon.</returns>
3656
3657 return gmlNewODataMultiItem(dom, "MultiSurface", "surfaceMembers", value.coordinates, gmlNewODataPolygonElement, isGeography);
3658 };
3659
3660 var gmlNewODataGeometryCollectionItem = function (dom, value, isGeography) {
3661 /// <summary>Creates a new GML element for an item in a geometry collection object.</summary>
3662 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3663 /// <param name="item" type="Object">GeoJSON object in the geometry collection.</param>
3664 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3665 /// <remarks>
3666 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3667 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3668 /// </remarks>
3669 /// <returns>New DOM element in the GML namespace.</returns>
3670
3671 return gmlNewODataSpatialValue(dom, value, value.type, isGeography);
3672 };
3673
3674 var gmlNewODataGeometryCollection = function (dom, value, isGeography) {
3675 /// <summary>Creates a new GML MultiGeometry DOM element for a GeoJSON GeometryCollection object.</summary>
3676 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
3677 /// <param name="value" type="Object">GeoJSON GeometryCollection object.</param>
3678 /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
3679 /// <remarks>
3680 /// When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
3681 /// will be serialized as the second component of each <pos> element in the GML DOM tree.
3682 /// </remarks>
3683 /// <returns>New DOM element in the GML namespace for the GeoJSON GeometryCollection.</returns>
3684
3685 return gmlNewODataMultiItem(dom, "MultiGeometry", "geometryMembers", value.geometries, gmlNewODataGeometryCollectionItem, isGeography);
3686 };
3687
3688
3689
3690 var xmlMediaType = "application/xml";
3691
3692 var ado = http + "schemas.microsoft.com/ado/"; // http://schemas.microsoft.com/ado/
3693 var adoDs = ado + "2007/08/dataservices"; // http://schemas.microsoft.com/ado/2007/08/dataservices
3694
3695 var edmxNs = ado + "2007/06/edmx"; // http://schemas.microsoft.com/ado/2007/06/edmx
3696 var edmNs1 = ado + "2006/04/edm"; // http://schemas.microsoft.com/ado/2006/04/edm
3697 var edmNs1_1 = ado + "2007/05/edm"; // http://schemas.microsoft.com/ado/2007/05/edm
3698 var edmNs1_2 = ado + "2008/01/edm"; // http://schemas.microsoft.com/ado/2008/01/edm
3699
3700 // There are two valid namespaces for Edm 2.0
3701 var edmNs2a = ado + "2008/09/edm"; // http://schemas.microsoft.com/ado/2008/09/edm
3702 var edmNs2b = ado + "2009/08/edm"; // http://schemas.microsoft.com/ado/2009/08/edm
3703
3704 var edmNs3 = ado + "2009/11/edm"; // http://schemas.microsoft.com/ado/2009/11/edm
3705
3706 var odataXmlNs = adoDs; // http://schemas.microsoft.com/ado/2007/08/dataservices
3707 var odataMetaXmlNs = adoDs + "/metadata"; // http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
3708 var odataRelatedPrefix = adoDs + "/related/"; // http://schemas.microsoft.com/ado/2007/08/dataservices/related
3709 var odataScheme = adoDs + "/scheme"; // http://schemas.microsoft.com/ado/2007/08/dataservices/scheme
3710
3711 var odataPrefix = "d";
3712 var odataMetaPrefix = "m";
3713
3714 var createAttributeExtension = function (domNode, useNamespaceURI) {
3715 /// <summary>Creates an extension object for the specified attribute.</summary>
3716 /// <param name="domNode">DOM node for the attribute.</param>
3717 /// <param name="useNamespaceURI" type="Boolean">Flag indicating if the namespaceURI property should be added to the extension object instead of the namespace property.</param>
3718 /// <remarks>
3719 /// The useNamespaceURI flag is used to prevent a breaking change from older versions of datajs in which extension
3720 /// objects created for Atom extension attributes have the namespaceURI property instead of the namespace one.
3721 ///
3722 /// This flag and the namespaceURI property should be deprecated in future major versions of the library.
3723 /// </remarks>
3724 /// <returns type="Object">The new extension object.</returns>
3725
3726 var extension = { name: xmlLocalName(domNode), value: domNode.value };
3727 extension[useNamespaceURI ? "namespaceURI" : "namespace"] = xmlNamespaceURI(domNode);
3728
3729 return extension;
3730 };
3731
3732 var createElementExtension = function (domNode, useNamespaceURI) {
3733 /// <summary>Creates an extension object for the specified element.</summary>
3734 /// <param name="domNode">DOM node for the element.</param>
3735 /// <param name="useNamespaceURI" type="Boolean">Flag indicating if the namespaceURI property should be added to the extension object instead of the namespace property.</param>
3736 /// <remarks>
3737 /// The useNamespaceURI flag is used to prevent a breaking change from older versions of datajs in which extension
3738 /// objects created for Atom extension attributes have the namespaceURI property instead of the namespace one.
3739 ///
3740 /// This flag and the namespaceURI property should be deprecated in future major versions of the library.
3741 /// </remarks>
3742 /// <returns type="Object">The new extension object.</returns>
3743
3744
3745 var attributeExtensions = [];
3746 var childrenExtensions = [];
3747
3748 var i, len;
3749 var attributes = domNode.attributes;
3750 for (i = 0, len = attributes.length; i < len; i++) {
3751 var attr = attributes[i];
3752 if (xmlNamespaceURI(attr) !== xmlnsNS) {
3753 attributeExtensions.push(createAttributeExtension(attr, useNamespaceURI));
3754 }
3755 }
3756
3757 var child = domNode.firstChild;
3758 while (child != null) {
3759 if (child.nodeType === 1) {
3760 childrenExtensions.push(createElementExtension(child, useNamespaceURI));
3761 }
3762 child = child.nextSibling;
3763 }
3764
3765 var extension = {
3766 name: xmlLocalName(domNode),
3767 value: xmlInnerText(domNode),
3768 attributes: attributeExtensions,
3769 children: childrenExtensions
3770 };
3771
3772 extension[useNamespaceURI ? "namespaceURI" : "namespace"] = xmlNamespaceURI(domNode);
3773 return extension;
3774 };
3775
3776 var isCollectionItemElement = function (domElement) {
3777 /// <summary>Checks whether the domElement is a collection item.</summary>
3778 /// <param name="domElement">DOM element possibliy represnting a collection item.</param>
3779 /// <returns type="Boolean">True if the domeElement belongs to the OData metadata namespace and its local name is "element"; false otherwise.</returns>
3780
3781 return xmlNamespaceURI(domElement) === odataXmlNs && xmlLocalName(domElement) === "element";
3782 };
3783
3784 var makePropertyMetadata = function (type, extensions) {
3785 /// <summary>Creates an object containing property metadata.</summary>
3786 /// <param type="String" name="type">Property type name.</param>
3787 /// <param type="Array" name="extensions">Array of attribute extension objects.</param>
3788 /// <returns type="Object">Property metadata object cotaining type and extensions fields.</returns>
3789
3790 return { type: type, extensions: extensions };
3791 };
3792
3793 var odataInferTypeFromPropertyXmlDom = function (domElement) {
3794 /// <summary>Infers type of a property based on its xml DOM tree.</summary>
3795 /// <param name="domElement">DOM element for the property.</param>
3796 /// <returns type="String">Inferred type name; null if the type cannot be determined.</returns>
3797
3798 if (xmlFirstChildElement(domElement, gmlXmlNs)) {
3799 return EDM_GEOMETRY;
3800 }
3801
3802 var firstChild = xmlFirstChildElement(domElement, odataXmlNs);
3803 if (!firstChild) {
3804 return EDM_STRING;
3805 }
3806
3807 if (isCollectionItemElement(firstChild)) {
3808 var sibling = xmlSiblingElement(firstChild, odataXmlNs);
3809 if (sibling && isCollectionItemElement(sibling)) {
3810 // More than one <element> tag have been found, it can be safely assumed that this is a collection property.
3811 return "Collection()";
3812 }
3813 }
3814
3815 return null;
3816 };
3817
3818 var xmlReadODataPropertyAttributes = function (domElement) {
3819 /// <summary>Reads the attributes of a property DOM element in an OData XML document.</summary>
3820 /// <param name="domElement">DOM element for the property.</param>
3821 /// <returns type="Object">Object containing the property type, if it is null, and its attribute extensions.</returns>
3822
3823 var type = null;
3824 var isNull = false;
3825 var extensions = [];
3826
3827 xmlAttributes(domElement, function (attribute) {
3828 var nsURI = xmlNamespaceURI(attribute);
3829 var localName = xmlLocalName(attribute);
3830 var value = xmlNodeValue(attribute);
3831
3832 if (nsURI === odataMetaXmlNs) {
3833 if (localName === "null") {
3834 isNull = (value.toLowerCase() === "true");
3835 return;
3836 }
3837
3838 if (localName === "type") {
3839 type = value;
3840 return;
3841 }
3842 }
3843
3844 if (nsURI !== xmlNS && nsURI !== xmlnsNS) {
3845 extensions.push(createAttributeExtension(attribute, true));
3846 return;
3847 }
3848 });
3849
3850 return { type: (!type && isNull ? EDM_STRING : type), isNull: isNull, extensions: extensions };
3851 };
3852
3853 var xmlReadODataProperty = function (domElement) {
3854 /// <summary>Reads a property DOM element in an OData XML document.</summary>
3855 /// <param name="domElement">DOM element for the property.</param>
3856 /// <returns type="Object">Object with name, value, and metadata for the property.</returns>
3857
3858 if (xmlNamespaceURI(domElement) !== odataXmlNs) {
3859 // domElement is not a proprety element because it is not in the odata xml namespace.
3860 return null;
3861 }
3862
3863 var propertyName = xmlLocalName(domElement);
3864 var propertyAttributes = xmlReadODataPropertyAttributes(domElement);
3865
3866 var propertyIsNull = propertyAttributes.isNull;
3867 var propertyType = propertyAttributes.type;
3868
3869 var propertyMetadata = makePropertyMetadata(propertyType, propertyAttributes.extensions);
3870 var propertyValue = propertyIsNull ? null : xmlReadODataPropertyValue(domElement, propertyType, propertyMetadata);
3871
3872 return { name: propertyName, value: propertyValue, metadata: propertyMetadata };
3873 };
3874
3875 var xmlReadODataPropertyValue = function (domElement, propertyType, propertyMetadata) {
3876 /// <summary>Reads the value of a property in an OData XML document.</summary>
3877 /// <param name="domElement">DOM element for the property.</param>
3878 /// <param name="propertyType" type="String">Property type name.</param>
3879 /// <param name="propertyMetadata" type="Object">Object that will store metadata about the property.</param>
3880 /// <returns>Property value.</returns>
3881
3882 if (!propertyType) {
3883 propertyType = odataInferTypeFromPropertyXmlDom(domElement);
3884 propertyMetadata.type = propertyType;
3885 }
3886
3887 var isGeograhpyType = isGeographyEdmType(propertyType);
3888 if (isGeograhpyType || isGeometryEdmType(propertyType)) {
3889 return xmlReadODataSpatialPropertyValue(domElement, propertyType, isGeograhpyType);
3890 }
3891
3892 if (isPrimitiveEdmType(propertyType)) {
3893 return xmlReadODataEdmPropertyValue(domElement, propertyType);
3894 }
3895
3896 if (isCollectionType(propertyType)) {
3897 return xmlReadODataCollectionPropertyValue(domElement, propertyType, propertyMetadata);
3898 }
3899
3900 return xmlReadODataComplexPropertyValue(domElement, propertyType, propertyMetadata);
3901 };
3902
3903 var xmlReadODataSpatialPropertyValue = function (domElement, propertyType, isGeography) {
3904 /// <summary>Reads the value of an spatial property in an OData XML document.</summary>
3905 /// <param name="property">DOM element for the spatial property.</param>
3906 /// <param name="propertyType" type="String">Property type name.</param>
3907 /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
3908 /// <remarks>
3909 /// When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
3910 /// will be deserialized as the second component of each <pos> element in the GML DOM tree.
3911 /// </remarks>
3912 /// <returns>Spatial property value in GeoJSON format.</returns>
3913
3914 var gmlRoot = xmlFirstChildElement(domElement, gmlXmlNs);
3915
3916 var value = gmlReadODataSpatialValue(gmlRoot, isGeography);
3917 value.__metadata = { type: propertyType };
3918 return value;
3919 };
3920
3921 var xmlReadODataEdmPropertyValue = function (domNode, propertyType) {
3922 /// <summary>Reads the value of an EDM property in an OData XML document.</summary>
3923 /// <param name="donNode">DOM node for the EDM property.</param>
3924 /// <param name="propertyType" type="String">Property type name.</param>
3925 /// <returns>EDM property value.</returns>
3926
3927 var propertyValue = xmlNodeValue(domNode) || "";
3928
3929 switch (propertyType) {
3930 case EDM_BOOLEAN:
3931 return parseBool(propertyValue);
3932 case EDM_BINARY:
3933 case EDM_DECIMAL:
3934 case EDM_GUID:
3935 case EDM_INT64:
3936 case EDM_STRING:
3937 return propertyValue;
3938 case EDM_BYTE:
3939 case EDM_INT16:
3940 case EDM_INT32:
3941 case EDM_SBYTE:
3942 return parseInt10(propertyValue);
3943 case EDM_DOUBLE:
3944 case EDM_SINGLE:
3945 return parseFloat(propertyValue);
3946 case EDM_TIME:
3947 return parseDuration(propertyValue);
3948 case EDM_DATETIME:
3949 return parseDateTime(propertyValue);
3950 case EDM_DATETIMEOFFSET:
3951 return parseDateTimeOffset(propertyValue);
3952 }
3953
3954 return propertyValue;
3955 };
3956
3957 var xmlReadODataComplexPropertyValue = function(domElement, propertyType, propertyMetadata) {
3958 /// <summary>Reads the value of a complex type property in an OData XML document.</summary>
3959 /// <param name="property">DOM element for the complex type property.</param>
3960 /// <param name="propertyType" type="String">Property type name.</param>
3961 /// <param name="propertyMetadata" type="Object">Object that will store metadata about the property.</param>
3962 /// <returns type="Object">Complex type property value.</returns>
3963
3964 var propertyValue = { __metadata: { type: propertyType } };
3965 xmlChildElements(domElement, function(child) {
3966 var childProperty = xmlReadODataProperty(child);
3967 var childPropertyName = childProperty.name;
3968
3969 propertyMetadata.properties = propertyMetadata.properties || {};
3970 propertyMetadata.properties[childPropertyName] = childProperty.metadata;
3971 propertyValue[childPropertyName] = childProperty.value;
3972 });
3973
3974 return propertyValue;
3975 };
3976
3977 var xmlReadODataCollectionPropertyValue = function (domElement, propertyType, propertyMetadata) {
3978 /// <summary>Reads the value of a collection property in an OData XML document.</summary>
3979 /// <param name="property">DOM element for the collection property.</param>
3980 /// <param name="propertyType" type="String">Property type name.</param>
3981 /// <param name="propertyMetadata" type="Object">Object that will store metadata about the property.</param>
3982 /// <returns type="Object">Collection property value.</returns>
3983
3984 var items = [];
3985 var itemsMetadata = propertyMetadata.elements = [];
3986 var collectionType = getCollectionType(propertyType);
3987
3988 xmlChildElements(domElement, function (child) {
3989 if (isCollectionItemElement(child)) {
3990 var itemAttributes = xmlReadODataPropertyAttributes(child);
3991 var itemExtensions = itemAttributes.extensions;
3992 var itemType = itemAttributes.type || collectionType;
3993 var itemMetadata = makePropertyMetadata(itemType, itemExtensions);
3994
3995 var item = xmlReadODataPropertyValue(child, itemType, itemMetadata);
3996
3997 items.push(item);
3998 itemsMetadata.push(itemMetadata);
3999 }
4000 });
4001
4002 return { __metadata: { type: propertyType === "Collection()" ? null : propertyType }, results: items };
4003 };
4004
4005 var readODataXmlDocument = function (xmlRoot, baseURI) {
4006 /// <summary>Reads an OData link(s) producing an object model in return.</summary>
4007 /// <param name="xmlRoot">Top-level element to read.</param>
4008 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the XML payload.</param>
4009 /// <returns type="Object">The object model representing the specified element.</returns>
4010
4011 if (xmlNamespaceURI(xmlRoot) === odataXmlNs) {
4012 baseURI = xmlBaseURI(xmlRoot, baseURI);
4013 var localName = xmlLocalName(xmlRoot);
4014
4015 if (localName === "links") {
4016 return readLinks(xmlRoot, baseURI);
4017 }
4018 if (localName === "uri") {
4019 return readUri(xmlRoot, baseURI);
4020 }
4021 }
4022 return undefined;
4023 };
4024
4025 var readLinks = function (linksElement, baseURI) {
4026 /// <summary>Deserializes an OData XML links element.</summary>
4027 /// <param name="linksElement">XML links element.</param>
4028 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the XML payload.</param>
4029 /// <returns type="Object">A new object representing the links collection.</returns>
4030
4031 var uris = [];
4032
4033 xmlChildElements(linksElement, function (child) {
4034 if (xmlLocalName(child) === "uri" && xmlNamespaceURI(child) === odataXmlNs) {
4035 uris.push(readUri(child, baseURI));
4036 }
4037 });
4038
4039 return { results: uris };
4040 };
4041
4042 var readUri = function (uriElement, baseURI) {
4043 /// <summary>Deserializes an OData XML uri element.</summary>
4044 /// <param name="uriElement">XML uri element.</param>
4045 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the XML payload.</param>
4046 /// <returns type="Object">A new object representing the uri.</returns>
4047
4048 var uri = xmlInnerText(uriElement) || "";
4049 return { uri: normalizeURI(uri, baseURI) };
4050 };
4051
4052 var xmlODataInferSpatialValueGeoJsonType = function (value, edmType) {
4053 /// <summary>Infers the GeoJSON type from the spatial property value and the edm type name.</summary>
4054 /// <param name="value" type="Object">Spatial property value in GeoJSON format.</param>
4055 /// <param name="edmType" type="String" mayBeNull="true" optional="true">Spatial property edm type.<param>
4056 /// <remarks>
4057 /// If the edmType parameter is null, undefined, "Edm.Geometry" or "Edm.Geography", then the function returns
4058 /// the GeoJSON type indicated by the value's type property.
4059 ///
4060 /// If the edmType parameter is specified or is not one of the base spatial types, then it is used to
4061 /// determine the GeoJSON type and the value's type property is ignored.
4062 /// </remarks>
4063 /// <returns>New DOM element in the GML namespace for the spatial value. </returns>
4064
4065 if (edmType === EDM_GEOMETRY || edmType === EDM_GEOGRAPHY) {
4066 return value && value.type;
4067 }
4068
4069 if (edmType === EDM_GEOMETRY_POINT || edmType === EDM_GEOGRAPHY_POINT) {
4070 return GEOJSON_POINT;
4071 }
4072
4073 if (edmType === EDM_GEOMETRY_LINESTRING || edmType === EDM_GEOGRAPHY_LINESTRING) {
4074 return GEOJSON_LINESTRING;
4075 }
4076
4077 if (edmType === EDM_GEOMETRY_POLYGON || edmType === EDM_GEOGRAPHY_POLYGON) {
4078 return GEOJSON_POLYGON;
4079 }
4080
4081 if (edmType === EDM_GEOMETRY_COLLECTION || edmType === EDM_GEOGRAPHY_COLLECTION) {
4082 return GEOJSON_GEOMETRYCOLLECTION;
4083 }
4084
4085 if (edmType === EDM_GEOMETRY_MULTIPOLYGON || edmType === EDM_GEOGRAPHY_MULTIPOLYGON) {
4086 return GEOJSON_MULTIPOLYGON;
4087 }
4088
4089 if (edmType === EDM_GEOMETRY_MULTILINESTRING || edmType === EDM_GEOGRAPHY_MULTILINESTRING) {
4090 return GEOJSON_MULTILINESTRING;
4091 }
4092
4093 if (edmType === EDM_GEOMETRY_MULTIPOINT || edmType === EDM_GEOGRAPHY_MULTIPOINT) {
4094 return GEOJSON_MULTIPOINT;
4095 }
4096
4097 return null;
4098 };
4099
4100 var xmlNewODataMetaElement = function (dom, name, children) {
4101 /// <summary>Creates a new DOM element in the OData metadata namespace.</summary>
4102 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4103 /// <param name="name" type="String">Local name of the OData metadata element to create.</param>
4104 /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
4105 /// <returns>New DOM element in the OData metadata namespace.</returns>
4106 /// <remarks>
4107 /// If a value in the children collection is a string, then a new DOM text node is going to be created
4108 /// for it and then appended as a child of the new DOM Element.
4109 /// </remarks>
4110
4111 return xmlNewElement(dom, odataMetaXmlNs, xmlQualifiedName(odataMetaPrefix, name), children);
4112 };
4113
4114 var xmlNewODataMetaAttribute = function (dom, name, value) {
4115 /// <summary>Creates a new DOM attribute in the odata namespace.</summary>
4116 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4117 /// <param name="name" type="String">Local name of the OData attribute to create.</param>
4118 /// <param name="value">Attribute value.</param>
4119 /// <returns>New DOM attribute in the odata namespace.</returns>
4120
4121 return xmlNewAttribute(dom, odataMetaXmlNs, xmlQualifiedName(odataMetaPrefix, name), value);
4122 };
4123
4124 var xmlNewODataElement = function (dom, name, children) {
4125 /// <summary>Creates a new DOM element in the OData namespace.</summary>
4126 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4127 /// <param name="name" type="String">Local name of the OData element to create.</param>
4128 /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
4129 /// <returns>New DOM element in the OData namespace.</returns>
4130 /// <remarks>
4131 /// If a value in the children collection is a string, then a new DOM text node is going to be created
4132 /// for it and then appended as a child of the new DOM Element.
4133 /// </remarks>
4134
4135 return xmlNewElement(dom, odataXmlNs, xmlQualifiedName(odataPrefix, name), children);
4136 };
4137
4138 var xmlNewODataPrimitiveValue = function (value, typeName) {
4139 /// <summary>Returns the string representation of primitive value for an OData XML document.</summary>
4140 /// <param name="value">Primivite value to format.</param>
4141 /// <param name="typeName" type="String" optional="true">Type name of the primitive value.</param>
4142 /// <returns type="String">Formatted primitive value.</returns>
4143
4144 if (typeName === EDM_DATETIME || typeName === EDM_DATETIMEOFFSET || isDate(value)) {
4145 return formatDateTimeOffset(value);
4146 }
4147 if (typeName === EDM_TIME) {
4148 return formatDuration(value);
4149 }
4150 return value.toString();
4151 };
4152
4153 var xmlNewODataElementInfo = function (domElement, dataServiceVersion) {
4154 /// <summary>Creates an object that represents a new DOM element for an OData XML document and the data service version it requires.</summary>
4155 /// <param name="domElement">New DOM element for an OData XML document.</param>
4156 /// <param name="dataServiceVersion" type="String">Required data service version by the new DOM element.</param>
4157 /// <returns type="Object">Object containing new DOM element and its required data service version.</returns>
4158
4159 return { element: domElement, dsv: dataServiceVersion };
4160 };
4161
4162 var xmlNewODataProperty = function (dom, name, typeName, children) {
4163 /// <summary>Creates a new DOM element for an entry property in an OData XML document.</summary>
4164 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4165 /// <param name="name" type="String">Property name.</param>
4166 /// <param name="typeName" type="String" optional="true">Property type name.</param>
4167 /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
4168 /// <remarks>
4169 /// If a value in the children collection is a string, then a new DOM text node is going to be created
4170 /// for it and then appended as a child of the new DOM Element.
4171 /// </remarks>
4172 /// <returns>New DOM element in the OData namespace for the entry property.</returns>
4173
4174 var typeAttribute = typeName ? xmlNewODataMetaAttribute(dom, "type", typeName) : null;
4175 var property = xmlNewODataElement(dom, name, typeAttribute);
4176 return xmlAppendChildren(property, children);
4177 };
4178
4179 var xmlNewODataEdmProperty = function (dom, name, value, typeName) {
4180 /// <summary>Creates a new DOM element for an EDM property in an OData XML document.</summary>
4181 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4182 /// <param name="name" type="String">Property name.</param>
4183 /// <param name="value">Property value.</param>
4184 /// <param name="typeName" type="String" optional="true">Property type name.</param>
4185 /// <returns type="Object">
4186 /// Object containing the new DOM element in the OData namespace for the EDM property and the
4187 /// required data service version for this property.
4188 /// </returns>
4189
4190 var propertyValue = xmlNewODataPrimitiveValue(value, typeName);
4191 var property = xmlNewODataProperty(dom, name, typeName, propertyValue);
4192 return xmlNewODataElementInfo(property, /*dataServiceVersion*/"1.0");
4193 };
4194
4195 var xmlNewODataNullProperty = function (dom, name, typeName, model) {
4196 /// <summary>Creates a new DOM element for a null property in an OData XML document.</summary>
4197 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4198 /// <param name="name" type="String">Property name.</param>
4199 /// <param name="typeName" type="String" optional="true">Property type name.</param>
4200 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
4201 /// <remarks>
4202 /// If no typeName is specified, then it will be assumed that this is a primitive type property.
4203 /// </remarks>
4204 /// <returns type="Object">
4205 /// Object containing the new DOM element in the OData namespace for the null property and the
4206 /// required data service version for this property.
4207 /// </returns>
4208
4209 var nullAttribute = xmlNewODataMetaAttribute(dom, "null", "true");
4210 var property = xmlNewODataProperty(dom, name, typeName, nullAttribute);
4211 var dataServiceVersion = lookupComplexType(typeName, model) ? "2.0" : "1.0";
4212
4213 return xmlNewODataElementInfo(property, dataServiceVersion);
4214 };
4215
4216 var xmlNewODataCollectionProperty = function (dom, name, value, typeName, collectionMetadata, collectionModel, model) {
4217 /// <summary>Creates a new DOM element for a collection property in an OData XML document.</summary>
4218 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4219 /// <param name="name" type="String">Property name.</param>
4220 /// <param name="value">Property value either as an array or an object representing a collection in the library's internal representation.</param>
4221 /// <param name="typeName" type="String" optional="true">Property type name.</param>
4222 /// <param name="collectionMetadata" type="Object" optional="true">Object containing metadata about the collection property.</param>
4223 /// <param name="collectionModel" type="Object" optional="true">Object describing the collection property in an OData conceptual schema.</param>
4224 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
4225 /// <returns type="Object">
4226 /// Object containing the new DOM element in the OData namespace for the collection property and the
4227 /// required data service version for this property.
4228 /// </returns>
4229
4230 var itemTypeName = getCollectionType(typeName);
4231 var items = isArray(value) ? value : value.results;
4232 var itemMetadata = typeName ? { type: itemTypeName} : {};
4233 itemMetadata.properties = collectionMetadata.properties;
4234
4235 var xmlProperty = xmlNewODataProperty(dom, name, itemTypeName ? typeName : null);
4236
4237 var i, len;
4238 for (i = 0, len = items.length; i < len; i++) {
4239 var itemValue = items[i];
4240 var item = xmlNewODataDataElement(dom, "element", itemValue, itemMetadata, collectionModel, model);
4241
4242 xmlAppendChild(xmlProperty, item.element);
4243 }
4244 return xmlNewODataElementInfo(xmlProperty, /*dataServiceVersion*/"3.0");
4245 };
4246
4247 var xmlNewODataComplexProperty = function (dom, name, value, typeName, propertyMetadata, propertyModel, model) {
4248 /// <summary>Creates a new DOM element for a complex type property in an OData XML document.</summary>
4249 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4250 /// <param name="name" type="String">Property name.</param>
4251 /// <param name="value">Property value as an object in the library's internal representation.</param>
4252 /// <param name="typeName" type="String" optional="true">Property type name.</param>
4253 /// <param name="propertyMetadata" type="Object" optional="true">Object containing metadata about the complex type property.</param>
4254 /// <param name="propertyModel" type="Object" optional="true">Object describing the complex type property in an OData conceptual schema.</param>
4255 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
4256 /// <returns type="Object">
4257 /// Object containing the new DOM element in the OData namespace for the complex type property and the
4258 /// required data service version for this property.
4259 /// </returns>
4260
4261 var xmlProperty = xmlNewODataProperty(dom, name, typeName);
4262 var complexTypePropertiesMetadata = propertyMetadata.properties || {};
4263 var complexTypeModel = lookupComplexType(typeName, model) || {};
4264
4265 var dataServiceVersion = "1.0";
4266
4267 for (var key in value) {
4268 if (key !== "__metadata") {
4269 var memberValue = value[key];
4270 var memberModel = lookupProperty(complexTypeModel.property, key);
4271 var memberMetadata = complexTypePropertiesMetadata[key] || {};
4272 var member = xmlNewODataDataElement(dom, key, memberValue, memberMetadata, memberModel, model);
4273
4274 dataServiceVersion = maxVersion(dataServiceVersion, member.dsv);
4275 xmlAppendChild(xmlProperty, member.element);
4276 }
4277 }
4278 return xmlNewODataElementInfo(xmlProperty, dataServiceVersion);
4279 };
4280
4281 var xmlNewODataSpatialProperty = function (dom, name, value, typeName, isGeography) {
4282 /// <summary>Creates a new DOM element for an EDM spatial property in an OData XML document.</summary>
4283 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4284 /// <param name="name" type="String">Property name.</param>
4285 /// <param name="value" type="Object">GeoJSON object containing the property value.</param>
4286 /// <param name="typeName" type="String" optional="true">Property type name.</param>
4287 /// <returns type="Object">
4288 /// Object containing the new DOM element in the OData namespace for the EDM property and the
4289 /// required data service version for this property.
4290 /// </returns>
4291
4292 var geoJsonType = xmlODataInferSpatialValueGeoJsonType(value, typeName);
4293
4294 var gmlRoot = gmlNewODataSpatialValue(dom, value, geoJsonType, isGeography);
4295 var xmlProperty = xmlNewODataProperty(dom, name, typeName, gmlRoot);
4296
4297 return xmlNewODataElementInfo(xmlProperty, "3.0");
4298 };
4299
4300 var xmlNewODataDataElement = function (dom, name, value, dataItemMetadata, dataItemModel, model) {
4301 /// <summary>Creates a new DOM element for a data item in an entry, complex property, or collection property.</summary>
4302 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
4303 /// <param name="name" type="String">Data item name.</param>
4304 /// <param name="value" optional="true" mayBeNull="true">Value of the data item, if any.</param>
4305 /// <param name="dataItemMetadata" type="Object" optional="true">Object containing metadata about the data item.</param>
4306 /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
4307 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
4308 /// <returns type="Object">
4309 /// Object containing the new DOM element in the appropriate namespace for the data item and the
4310 /// required data service version for it.
4311 /// </returns>
4312
4313 var typeName = dataItemTypeName(value, dataItemMetadata, dataItemModel);
4314 if (isPrimitive(value)) {
4315 return xmlNewODataEdmProperty(dom, name, value, typeName || EDM_STRING);
4316 }
4317
4318 var isGeography = isGeographyEdmType(typeName);
4319 if (isGeography || isGeometryEdmType(typeName)) {
4320 return xmlNewODataSpatialProperty(dom, name, value, typeName, isGeography);
4321 }
4322
4323 if (isCollection(value, typeName)) {
4324 return xmlNewODataCollectionProperty(dom, name, value, typeName, dataItemMetadata, dataItemModel, model);
4325 }
4326
4327 if (isNamedStream(value)) {
4328 return null;
4329 }
4330
4331 // This may be a navigation property.
4332 var navPropKind = navigationPropertyKind(value, dataItemModel);
4333 if (navPropKind !== null) {
4334 return null;
4335 }
4336
4337 if (value === null) {
4338 return xmlNewODataNullProperty(dom, name, typeName);
4339 }
4340
4341 return xmlNewODataComplexProperty(dom, name, value, typeName, dataItemMetadata, dataItemModel, model);
4342 };
4343
4344 var odataNewLinkDocument = function (data) {
4345 /// <summary>Writes the specified data into an OData XML document.</summary>
4346 /// <param name="data">Data to write.</param>
4347 /// <returns>The root of the DOM tree built.</returns>
4348
4349 if (data && isObject(data)) {
4350 var dom = xmlDom();
4351 return xmlAppendChild(dom, xmlNewODataElement(dom, "uri", data.uri));
4352 }
4353 // Allow for undefined to be returned.
4354 };
4355
4356 var xmlParser = function (handler, text) {
4357 /// <summary>Parses an OData XML document.</summary>
4358 /// <param name="handler">This handler.</param>
4359 /// <param name="text" type="String">Document text.</param>
4360 /// <returns>An object representation of the document; undefined if not applicable.</returns>
4361
4362 if (text) {
4363 var doc = xmlParse(text);
4364 var root = xmlFirstChildElement(doc);
4365 if (root) {
4366 return readODataXmlDocument(root);
4367 }
4368 }
4369
4370 // Allow for undefined to be returned.
4371 };
4372
4373 var xmlSerializer = function (handler, data, context) {
4374 /// <summary>Serializes an OData XML object into a document.</summary>
4375 /// <param name="handler">This handler.</param>
4376 /// <param name="data" type="Object">Representation of feed or entry.</param>
4377 /// <param name="context" type="Object">Object with parsing context.</param>
4378 /// <returns>A text representation of the data object; undefined if not applicable.</returns>
4379
4380 var cType = context.contentType = context.contentType || contentType(xmlMediaType);
4381 if (cType && cType.mediaType === xmlMediaType) {
4382 return xmlSerialize(odataNewLinkDocument(data));
4383 }
4384 return undefined;
4385 };
4386
4387 odata.xmlHandler = handler(xmlParser, xmlSerializer, xmlMediaType, MAX_DATA_SERVICE_VERSION);
4388
4389
4390
4391 var atomPrefix = "a";
4392
4393 var atomXmlNs = w3org + "2005/Atom"; // http://www.w3.org/2005/Atom
4394 var appXmlNs = w3org + "2007/app"; // http://www.w3.org/2007/app
4395
4396 var odataEditMediaPrefix = adoDs + "/edit-media/"; // http://schemas.microsoft.com/ado/2007/08/dataservices/edit-media
4397 var odataMediaResourcePrefix = adoDs + "/mediaresource/"; // http://schemas.microsoft.com/ado/2007/08/dataservices/mediaresource
4398 var odataRelatedLinksPrefix = adoDs + "/relatedlinks/"; // http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks
4399
4400 var atomAcceptTypes = ["application/atom+xml", "application/atomsvc+xml", "application/xml"];
4401 var atomMediaType = atomAcceptTypes[0];
4402
4403 // These are the namespaces that are not considered ATOM extension namespaces.
4404 var nonExtensionNamepaces = [atomXmlNs, appXmlNs, xmlNS, xmlnsNS];
4405
4406 // These are entity property mapping paths that have well-known paths.
4407 var knownCustomizationPaths = {
4408 SyndicationAuthorEmail: "author/email",
4409 SyndicationAuthorName: "author/name",
4410 SyndicationAuthorUri: "author/uri",
4411 SyndicationContributorEmail: "contributor/email",
4412 SyndicationContributorName: "contributor/name",
4413 SyndicationContributorUri: "contributor/uri",
4414 SyndicationPublished: "published",
4415 SyndicationRights: "rights",
4416 SyndicationSummary: "summary",
4417 SyndicationTitle: "title",
4418 SyndicationUpdated: "updated"
4419 };
4420
4421 var expandedFeedCustomizationPath = function (path) {
4422 /// <summary>Returns an expanded customization path if it's well-known.</summary>
4423 /// <param name="path" type="String">Path to expand.</param>
4424 /// <returns type="String">Expanded path or just 'path' otherwise.</returns>
4425
4426 return knownCustomizationPaths[path] || path;
4427 };
4428
4429 var isExtensionNs = function (nsURI) {
4430 /// <summary>Checks whether the specified namespace is an extension namespace to ATOM.</summary>
4431 /// <param type="String" name="nsURI">Namespace to check.</param>
4432 /// <returns type="Boolean">true if nsURI is an extension namespace to ATOM; false otherwise.</returns>
4433
4434 return !(contains(nonExtensionNamepaces, nsURI));
4435 };
4436
4437 var atomFeedCustomization = function (customizationModel, entityType, model, propertyName, suffix) {
4438 /// <summary>Creates an object describing a feed customization that was delcared in an OData conceptual schema.</summary>
4439 /// <param name="customizationModel" type="Object">Object describing the customization delcared in the conceptual schema.</param>
4440 /// <param name="entityType" type="Object">Object describing the entity type that owns the customization in an OData conceputal schema.</param>
4441 /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
4442 /// <param name="propertyName" type="String" optional="true">Name of the property to which this customization applies.</param>
4443 /// <param name="suffix" type="String" optional="true">Suffix to feed customization properties in the conceptual schema.</param>
4444 /// <returns type="Object">Object that describes an applicable feed customization.</returns>
4445
4446 suffix = suffix || "";
4447 var targetPath = customizationModel["FC_TargetPath" + suffix];
4448 if (!targetPath) {
4449 return null;
4450 }
4451
4452 var sourcePath = customizationModel["FC_SourcePath" + suffix];
4453 var targetXmlPath = expandedFeedCustomizationPath(targetPath);
4454
4455 var propertyPath = propertyName ? propertyName + (sourcePath ? "/" + sourcePath : "") : sourcePath;
4456 var propertyType = propertyPath && lookupPropertyType(model, entityType, propertyPath);
4457 var nsURI = customizationModel["FC_NsUri" + suffix] || null;
4458 var nsPrefix = customizationModel["FC_NsPrefix" + suffix] || null;
4459 var keepinContent = customizationModel["FC_KeepInContent" + suffix] || "";
4460
4461 if (targetPath !== targetXmlPath) {
4462 nsURI = atomXmlNs;
4463 nsPrefix = atomPrefix;
4464 }
4465
4466 return {
4467 contentKind: customizationModel["FC_ContentKind" + suffix],
4468 keepInContent: keepinContent.toLowerCase() === "true",
4469 nsPrefix: nsPrefix,
4470 nsURI: nsURI,
4471 propertyPath: propertyPath,
4472 propertyType: propertyType,
4473 entryPath: targetXmlPath
4474 };
4475 };
4476
4477 var atomApplyAllFeedCustomizations = function (entityType, model, callback) {
4478 /// <summary>Gets all the feed customizations that have to be applied to an entry as per the enity type declared in an OData conceptual schema.</summary>
4479 /// <param name="entityType" type="Object">Object describing an entity type in a conceptual schema.</param>
4480 /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
4481 /// <param name="callback" type="Function">Callback function to be invoked for each feed customization that needs to be applied.</param>
4482
4483 var customizations = [];
4484 while (entityType) {
4485 var sourcePath = entityType.FC_SourcePath;
4486 var customization = atomFeedCustomization(entityType, entityType, model);
4487 if (customization) {
4488 callback(customization);
4489 }
4490
4491 var properties = entityType.property || [];
4492 var i, len;
4493 for (i = 0, len = properties.length; i < len; i++) {
4494 var property = properties[i];
4495 var suffixCounter = 0;
4496 var suffix = "";
4497
4498 while (customization = atomFeedCustomization(property, entityType, model, property.name, suffix)) {
4499 callback(customization);
4500 suffixCounter++;
4501 suffix = "_" + suffixCounter;
4502 }
4503 }
4504 entityType = lookupEntityType(entityType.baseType, model);
4505 }
4506 return customizations;
4507 };
4508
4509 var atomReadExtensionAttributes = function (domElement) {
4510 /// <summary>Reads ATOM extension attributes (any attribute not in the Atom namespace) from a DOM element.</summary>
4511 /// <param name="domElement">DOM element with zero or more extension attributes.</param>
4512 /// <returns type="Array">An array of extension attribute representations.</returns>
4513
4514 var extensions = [];
4515 xmlAttributes(domElement, function (attribute) {
4516 var nsURI = xmlNamespaceURI(attribute);
4517 if (isExtensionNs(nsURI)) {
4518 extensions.push(createAttributeExtension(attribute, true));
4519 }
4520 });
4521 return extensions;
4522 };
4523
4524 var atomReadExtensionElement = function (domElement) {
4525 /// <summary>Reads an ATOM extension element (an element not in the ATOM namespaces).</summary>
4526 /// <param name="domElement">DOM element not part of the atom namespace.</param>
4527 /// <returns type="Object">Object representing the extension element.</returns>
4528
4529 return createElementExtension(domElement, /*addNamespaceURI*/true);
4530 };
4531
4532 var atomReadDocument = function (domElement, baseURI, model) {
4533 /// <summary>Reads an ATOM entry, feed or service document, producing an object model in return.</summary>
4534 /// <param name="domElement">Top-level ATOM DOM element to read.</param>
4535 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the ATOM document.</param>
4536 /// <param name="model" type="Object">Object that describes the conceptual schema.</param>
4537 /// <returns type="Object">The object model representing the specified element, undefined if the top-level element is not part of the ATOM specification.</returns>
4538
4539 var nsURI = xmlNamespaceURI(domElement);
4540 var localName = xmlLocalName(domElement);
4541
4542 // Handle service documents.
4543 if (nsURI === appXmlNs && localName === "service") {
4544 return atomReadServiceDocument(domElement, baseURI);
4545 }
4546
4547 // Handle feed and entry elements.
4548 if (nsURI === atomXmlNs) {
4549 if (localName === "feed") {
4550 return atomReadFeed(domElement, baseURI, model);
4551 }
4552 if (localName === "entry") {
4553 return atomReadEntry(domElement, baseURI, model);
4554 }
4555 }
4556
4557 // Allow undefined to be returned.
4558 };
4559
4560 var atomReadAdvertisedActionOrFunction = function (domElement, baseURI) {
4561 /// <summary>Reads the DOM element for an action or a function in an OData Atom document.</summary>
4562 /// <param name="domElement">DOM element to read.</param>
4563 /// <param name="baseURI" type="String">Base URI for normalizing the action or function target url.</param>
4564 /// <returns type="Object">Object with title, target, and metadata fields.</returns>
4565
4566 var extensions = [];
4567 var result = { extensions: extensions };
4568 xmlAttributes(domElement, function (attribute) {
4569 var localName = xmlLocalName(attribute);
4570 var nsURI = xmlNamespaceURI(attribute);
4571 var value = xmlNodeValue(attribute);
4572
4573 if (nsURI === null) {
4574 if (localName === "title" || localName === "metadata") {
4575 result[localName] = value;
4576 return;
4577 }
4578 if (localName === "target") {
4579 result.target = normalizeURI(value, xmlBaseURI(domElement, baseURI));
4580 return;
4581 }
4582 }
4583
4584 if (isExtensionNs(nsURI)) {
4585 extensions.push(createAttributeExtension(attribute, true));
4586 }
4587 });
4588 return result;
4589 };
4590
4591 var atomReadAdvertisedAction = function (domElement, baseURI, parentMetadata) {
4592 /// <summary>Reads the DOM element for an action in an OData Atom document.</summary>
4593 /// <param name="domElement">DOM element to read.</param>
4594 /// <param name="baseURI" type="String">Base URI for normalizing the action or target url.</param>
4595 /// <param name="parentMetadata" type="Object">Object to update with the action metadata.</param>
4596
4597 var actions = parentMetadata.actions = parentMetadata.actions || [];
4598 actions.push(atomReadAdvertisedActionOrFunction(domElement, baseURI));
4599 };
4600
4601 var atomReadAdvertisedFunction = function (domElement, baseURI, parentMetadata) {
4602 /// <summary>Reads the DOM element for an action in an OData Atom document.</summary>
4603 /// <param name="domElement">DOM element to read.</param>
4604 /// <param name="baseURI" type="String">Base URI for normalizing the action or target url.</param>
4605 /// <param name="parentMetadata" type="Object">Object to update with the action metadata.</param>
4606
4607 var functions = parentMetadata.functions = parentMetadata.functions || [];
4608 functions.push(atomReadAdvertisedActionOrFunction(domElement, baseURI));
4609 };
4610
4611 var atomReadFeed = function (domElement, baseURI, model) {
4612 /// <summary>Reads a DOM element for an ATOM feed, producing an object model in return.</summary>
4613 /// <param name="domElement">ATOM feed DOM element.</param>
4614 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the ATOM feed.</param>
4615 /// <param name="model">Metadata that describes the conceptual schema.</param>
4616 /// <returns type="Object">A new object representing the feed.</returns>
4617
4618 var extensions = atomReadExtensionAttributes(domElement);
4619 var feedMetadata = { feed_extensions: extensions };
4620 var results = [];
4621
4622 var feed = { __metadata: feedMetadata, results: results };
4623
4624 baseURI = xmlBaseURI(domElement, baseURI);
4625
4626 xmlChildElements(domElement, function (child) {
4627 var nsURI = xmlNamespaceURI(child);
4628 var localName = xmlLocalName(child);
4629
4630 if (nsURI === odataMetaXmlNs) {
4631 if (localName === "count") {
4632 feed.__count = parseInt(xmlInnerText(child), 10);
4633 return;
4634 }
4635 if (localName === "action") {
4636 atomReadAdvertisedAction(child, baseURI, feedMetadata);
4637 return;
4638 }
4639 if (localName === "function") {
4640 atomReadAdvertisedFunction(child, baseURI, feedMetadata);
4641 return;
4642 }
4643 }
4644
4645 if (isExtensionNs(nsURI)) {
4646 extensions.push(createElementExtension(child));
4647 return;
4648 }
4649
4650 // The element should belong to the ATOM namespace.
4651
4652 if (localName === "entry") {
4653 results.push(atomReadEntry(child, baseURI, model));
4654 return;
4655 }
4656 if (localName === "link") {
4657 atomReadFeedLink(child, feed, baseURI);
4658 return;
4659 }
4660 if (localName === "id") {
4661 feedMetadata.uri = normalizeURI(xmlInnerText(child), baseURI);
4662 feedMetadata.uri_extensions = atomReadExtensionAttributes(child);
4663 return;
4664 }
4665 if (localName === "title") {
4666 feedMetadata.title = xmlInnerText(child) || "";
4667 feedMetadata.title_extensions = atomReadExtensionAttributes(child);
4668 return;
4669 }
4670 });
4671
4672 return feed;
4673 };
4674
4675 var atomReadFeedLink = function (domElement, feed, baseURI) {
4676 /// <summary>Reads an ATOM link DOM element for a feed.</summary>
4677 /// <param name="domElement">ATOM link DOM element.</param>
4678 /// <param name="feed">Feed object to be annotated with the link data.</param>
4679 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
4680
4681 var link = atomReadLink(domElement, baseURI);
4682 var href = link.href;
4683 var rel = link.rel;
4684 var extensions = link.extensions;
4685 var metadata = feed.__metadata;
4686
4687 if (rel === "next") {
4688 feed.__next = href;
4689 metadata.next_extensions = extensions;
4690 return;
4691 }
4692 if (rel === "self") {
4693 metadata.self = href;
4694 metadata.self_extensions = extensions;
4695 return;
4696 }
4697 };
4698
4699 var atomReadLink = function (domElement, baseURI) {
4700 /// <summary>Reads an ATOM link DOM element.</summary>
4701 /// <param name="linkElement">DOM element to read.</param>
4702 /// <param name="baseURI" type="String">Base URI for normalizing the link href.</param>
4703 /// <returns type="Object">A link element representation.</returns>
4704
4705 baseURI = xmlBaseURI(domElement, baseURI);
4706
4707 var extensions = [];
4708 var link = { extensions: extensions, baseURI: baseURI };
4709
4710 xmlAttributes(domElement, function (attribute) {
4711 var nsURI = xmlNamespaceURI(attribute);
4712 var localName = xmlLocalName(attribute);
4713 var value = attribute.value;
4714
4715 if (localName === "href") {
4716 link.href = normalizeURI(value, baseURI);
4717 return;
4718 }
4719 if (localName === "type" || localName === "rel") {
4720 link[localName] = value;
4721 return;
4722 }
4723
4724 if (isExtensionNs(nsURI)) {
4725 extensions.push(createAttributeExtension(attribute, true));
4726 }
4727 });
4728
4729 if (!link.href) {
4730 throw { error: "href attribute missing on link element", element: domElement };
4731 }
4732
4733 return link;
4734 };
4735
4736 var atomGetObjectValueByPath = function (path, item) {
4737 /// <summary>Gets a slashed path value from the specified item.</summary>
4738 /// <param name="path" type="String">Property path to read ('/'-separated).</param>
4739 /// <param name="item" type="Object">Object to get value from.</param>
4740 /// <returns>The property value, possibly undefined if any path segment is missing.</returns>
4741
4742 // Fast path.
4743 if (path.indexOf('/') === -1) {
4744 return item[path];
4745 } else {
4746 var parts = path.split('/');
4747 var i, len;
4748 for (i = 0, len = parts.length; i < len; i++) {
4749 // Avoid traversing a null object.
4750 if (item === null) {
4751 return undefined;
4752 }
4753
4754 item = item[parts[i]];
4755 if (item === undefined) {
4756 return item;
4757 }
4758 }
4759
4760 return item;
4761 }
4762 };
4763
4764 var atomSetEntryValueByPath = function (path, target, value, propertyType) {
4765 /// <summary>Sets a slashed path value on the specified target.</summary>
4766 /// <param name="path" type="String">Property path to set ('/'-separated).</param>
4767 /// <param name="target" type="Object">Object to set value on.</param>
4768 /// <param name="value">Value to set.</param>
4769 /// <param name="propertyType" type="String" optional="true">Property type to set in metadata.</param>
4770
4771 var propertyName;
4772 if (path.indexOf('/') === -1) {
4773 target[path] = value;
4774 propertyName = path;
4775 } else {
4776 var parts = path.split('/');
4777 var i, len;
4778 for (i = 0, len = (parts.length - 1); i < len; i++) {
4779 // We construct each step of the way if the property is missing;
4780 // if it's already initialized to null, we stop further processing.
4781 var next = target[parts[i]];
4782 if (next === undefined) {
4783 next = {};
4784 target[parts[i]] = next;
4785 } else if (next === null) {
4786 return;
4787 }
4788 target = next;
4789 }
4790 propertyName = parts[i];
4791 target[propertyName] = value;
4792 }
4793
4794 if (propertyType) {
4795 var metadata = target.__metadata = target.__metadata || {};
4796 var properties = metadata.properties = metadata.properties || {};
4797 var property = properties[propertyName] = properties[propertyName] || {};
4798 property.type = propertyType;
4799 }
4800 };
4801
4802 var atomApplyCustomizationToEntryObject = function (customization, domElement, entry) {
4803 /// <summary>Applies a specific feed customization item to an object.</summary>
4804 /// <param name="customization">Object with customization description.</param>
4805 /// <param name="sourcePath">Property path to set ('source' in the description).</param>
4806 /// <param name="entryElement">XML element for the entry that corresponds to the object being read.</param>
4807 /// <param name="entryObject">Object being read.</param>
4808 /// <param name="propertyType" type="String">Name of property type to set.</param>
4809 /// <param name="suffix" type="String">Suffix to feed customization properties.</param>
4810
4811 var propertyPath = customization.propertyPath;
4812 // If keepInConent equals true or the property value is null we do nothing as this overrides any other customization.
4813 if (customization.keepInContent || atomGetObjectValueByPath(propertyPath, entry) === null) {
4814 return;
4815 }
4816
4817 var xmlNode = xmlFindNodeByPath(domElement, customization.nsURI, customization.entryPath);
4818
4819 // If the XML tree does not contain the necessary elements to read the value,
4820 // then it shouldn't be considered null, but rather ignored at all. This prevents
4821 // the customization from generating the object path down to the property.
4822 if (!xmlNode) {
4823 return;
4824 }
4825
4826 var propertyType = customization.propertyType;
4827 var propertyValue;
4828
4829 if (customization.contentKind === "xhtml") {
4830 // Treat per XHTML in http://tools.ietf.org/html/rfc4287#section-3.1.1, including the DIV
4831 // in the content.
4832 propertyValue = xmlSerializeDescendants(xmlNode);
4833 } else {
4834 propertyValue = xmlReadODataEdmPropertyValue(xmlNode, propertyType || "Edm.String");
4835 }
4836 // Set the value on the entry.
4837 atomSetEntryValueByPath(propertyPath, entry, propertyValue, propertyType);
4838 };
4839
4840 var lookupPropertyType = function (metadata, owningType, path) {
4841 /// <summary>Looks up the type of a property given its path in an entity type.</summary>
4842 /// <param name="metadata">Metadata in which to search for base and complex types.</param>
4843 /// <param name="owningType">Type to which property belongs.</param>
4844 /// <param name="path" type="String" mayBeNull="false">Property path to look at.</param>
4845 /// <returns type="String">The name of the property type; possibly null.</returns>
4846
4847 var parts = path.split("/");
4848 var i, len;
4849 while (owningType) {
4850 // Keep track of the type being traversed, necessary for complex types.
4851 var traversedType = owningType;
4852
4853 for (i = 0, len = parts.length; i < len; i++) {
4854 // Traverse down the structure as necessary.
4855 var properties = traversedType.property;
4856 if (!properties) {
4857 break;
4858 }
4859
4860 // Find the property by scanning the property list (might be worth pre-processing).
4861 var propertyFound = lookupProperty(properties, parts[i]);
4862 if (!propertyFound) {
4863 break;
4864 }
4865
4866 var propertyType = propertyFound.type;
4867
4868 // We could in theory still be missing types, but that would
4869 // be caused by a malformed path.
4870 if (!propertyType || isPrimitiveEdmType(propertyType)) {
4871 return propertyType || null;
4872 }
4873
4874 traversedType = lookupComplexType(propertyType, metadata);
4875 if (!traversedType) {
4876 return null;
4877 }
4878 }
4879
4880 // Traverse up the inheritance chain.
4881 owningType = lookupEntityType(owningType.baseType, metadata);
4882 }
4883
4884 return null;
4885 };
4886
4887 var atomReadEntry = function (domElement, baseURI, model) {
4888 /// <summary>Reads a DOM element for an ATOM entry, producing an object model in return.</summary>
4889 /// <param name="domElement">ATOM entry DOM element.</param>
4890 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the ATOM entry.</param>
4891 /// <param name="model">Metadata that describes the conceptual schema.</param>
4892 /// <returns type="Object">A new object representing the entry.</returns>
4893
4894 var entryMetadata = {};
4895 var entry = { __metadata: entryMetadata };
4896
4897 var etag = xmlAttributeValue(domElement, "etag", odataMetaXmlNs);
4898 if (etag) {
4899 entryMetadata.etag = etag;
4900 }
4901
4902 baseURI = xmlBaseURI(domElement, baseURI);
4903
4904 xmlChildElements(domElement, function (child) {
4905 var nsURI = xmlNamespaceURI(child);
4906 var localName = xmlLocalName(child);
4907
4908 if (nsURI === atomXmlNs) {
4909 if (localName === "id") {
4910 atomReadEntryId(child, entryMetadata, baseURI);
4911 return;
4912 }
4913 if (localName === "category") {
4914 atomReadEntryType(child, entryMetadata);
4915 return;
4916 }
4917 if (localName === "content") {
4918 atomReadEntryContent(child, entry, entryMetadata, baseURI);
4919 return;
4920 }
4921 if (localName === "link") {
4922 atomReadEntryLink(child, entry, entryMetadata, baseURI, model);
4923 return;
4924 }
4925 return;
4926 }
4927
4928 if (nsURI === odataMetaXmlNs) {
4929 if (localName === "properties") {
4930 atomReadEntryStructuralObject(child, entry, entryMetadata);
4931 return;
4932 }
4933 if (localName === "action") {
4934 atomReadAdvertisedAction(child, baseURI, entryMetadata);
4935 return;
4936 }
4937 if (localName === "function") {
4938 atomReadAdvertisedFunction(child, baseURI, entryMetadata);
4939 return;
4940 }
4941 }
4942 });
4943
4944 // Apply feed customizations if applicable
4945 var entityType = lookupEntityType(entryMetadata.type, model);
4946 atomApplyAllFeedCustomizations(entityType, model, function (customization) {
4947 atomApplyCustomizationToEntryObject(customization, domElement, entry);
4948 });
4949
4950 return entry;
4951 };
4952
4953 var atomReadEntryId = function (domElement, entryMetadata, baseURI) {
4954 /// <summary>Reads an ATOM entry id DOM element.</summary>
4955 /// <param name="domElement">ATOM id DOM element.</param>
4956 /// <param name="entryMetadata">Entry metadata object to update with the id information.</param>
4957
4958 entryMetadata.uri = normalizeURI(xmlInnerText(domElement), xmlBaseURI(domElement, baseURI));
4959 entryMetadata.uri_extensions = atomReadExtensionAttributes(domElement);
4960 };
4961
4962 var atomReadEntryType = function (domElement, entryMetadata) {
4963 /// <summary>Reads type information from an ATOM category DOM element.</summary>
4964 /// <param name="domElement">ATOM category DOM element.</param>
4965 /// <param name="entryMetadata">Entry metadata object to update with the type information.</param>
4966
4967 if (xmlAttributeValue(domElement, "scheme") === odataScheme) {
4968 if (entryMetadata.type) {
4969 throw { message: "Invalid AtomPub document: multiple category elements defining the entry type were encounterd withing an entry", element: domElement };
4970 }
4971
4972 var typeExtensions = [];
4973 xmlAttributes(domElement, function (attribute) {
4974 var nsURI = xmlNamespaceURI(attribute);
4975 var localName = xmlLocalName(attribute);
4976
4977 if (!nsURI) {
4978 if (localName !== "scheme" && localName !== "term") {
4979 typeExtensions.push(createAttributeExtension(attribute, true));
4980 }
4981 return;
4982 }
4983
4984 if (isExtensionNs(nsURI)) {
4985 typeExtensions.push(createAttributeExtension(attribute, true));
4986 }
4987 });
4988
4989 entryMetadata.type = xmlAttributeValue(domElement, "term");
4990 entryMetadata.type_extensions = typeExtensions;
4991 }
4992 };
4993
4994 var atomReadEntryContent = function (domElement, entry, entryMetadata, baseURI) {
4995 /// <summary>Reads an ATOM content DOM element.</summary>
4996 /// <param name="domElement">ATOM content DOM element.</param>
4997 /// <param name="entry">Entry object to update with information.</param>
4998 /// <param name="entryMetadata">Entry metadata object to update with the content information.</param>
4999 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the Atom entry content.</param>
5000
5001 var src = xmlAttributeValue(domElement, "src");
5002 var type = xmlAttributeValue(domElement, "type");
5003
5004 if (src) {
5005 if (!type) {
5006 throw {
5007 message: "Invalid AtomPub document: content element must specify the type attribute if the src attribute is also specified",
5008 element: domElement
5009 };
5010 }
5011
5012 entryMetadata.media_src = normalizeURI(src, xmlBaseURI(domElement, baseURI));
5013 entryMetadata.content_type = type;
5014 }
5015
5016 xmlChildElements(domElement, function (child) {
5017 if (src) {
5018 throw { message: "Invalid AtomPub document: content element must not have child elements if the src attribute is specified", element: domElement };
5019 }
5020
5021 if (xmlNamespaceURI(child) === odataMetaXmlNs && xmlLocalName(child) === "properties") {
5022 atomReadEntryStructuralObject(child, entry, entryMetadata);
5023 }
5024 });
5025 };
5026
5027 var atomReadEntryLink = function (domElement, entry, entryMetadata, baseURI, model) {
5028 /// <summary>Reads a link element on an entry.</summary>
5029 /// <param name="atomEntryLink">'link' element on the entry.</param>
5030 /// <param name="entry" type="Object">Entry object to update with the link data.</param>
5031 /// <param name="entryMetadata">Entry metadata object to update with the link metadata.</param>
5032 /// <param name="baseURI" type="String">Base URI for normalizing the link href.</param>
5033 /// <param name="model" type="Object">Metadata that describes the conceptual schema.</param>
5034
5035 var link = atomReadLink(domElement, baseURI);
5036
5037 var rel = link.rel;
5038 var href = link.href;
5039 var extensions = link.extensions;
5040
5041 if (rel === "self") {
5042 entryMetadata.self = href;
5043 entryMetadata.self_link_extensions = extensions;
5044 return;
5045 }
5046
5047 if (rel === "edit") {
5048 entryMetadata.edit = href;
5049 entryMetadata.edit_link_extensions = extensions;
5050 return;
5051 }
5052
5053 if (rel === "edit-media") {
5054 entryMetadata.edit_media = link.href;
5055 entryMetadata.edit_media_extensions = extensions;
5056 atomReadLinkMediaEtag(link, entryMetadata);
5057 return;
5058 }
5059
5060 // This might be a named stream edit link
5061 if (rel.indexOf(odataEditMediaPrefix) === 0) {
5062 atomReadNamedStreamEditLink(link, entry, entryMetadata);
5063 return;
5064 }
5065
5066 // This might be a named stram media resource (read) link
5067 if (rel.indexOf(odataMediaResourcePrefix) === 0) {
5068 atomReadNamedStreamSelfLink(link, entry, entryMetadata);
5069 return;
5070 }
5071
5072 // This might be a navigation property
5073 if (rel.indexOf(odataRelatedPrefix) === 0) {
5074 atomReadNavPropLink(domElement, link, entry, entryMetadata, model);
5075 return;
5076 }
5077
5078 if (rel.indexOf(odataRelatedLinksPrefix) === 0) {
5079 atomReadNavPropRelatedLink(link, entryMetadata);
5080 return;
5081 }
5082 };
5083
5084 var atomReadNavPropRelatedLink = function (link, entryMetadata) {
5085 /// <summary>Reads a link represnting the links related to a navigation property in an OData Atom document.</summary>
5086 /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
5087 /// <param name="entryMetadata" type="Object">Entry metadata object to update with the related links information.</param>
5088
5089 var propertyName = link.rel.substring(odataRelatedLinksPrefix.length);
5090
5091 // Set the extra property information on the entry object metadata.
5092 entryMetadata.properties = entryMetadata.properties || {};
5093 var propertyMetadata = entryMetadata.properties[propertyName] = entryMetadata.properties[propertyName] || {};
5094
5095 propertyMetadata.associationuri = link.href;
5096 propertyMetadata.associationuri_extensions = link.extensions;
5097 };
5098
5099 var atomReadNavPropLink = function (domElement, link, entry, entryMetadata, model) {
5100 /// <summary>Reads a link representing a navigation property in an OData Atom document.</summary>
5101 /// <param name="domElement">DOM element for a navigation property in an OData Atom document.</summary>
5102 /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
5103 /// <param name="entry" type="Object">Entry object to update with the navigation property.</param>
5104 /// <param name="entryMetadata">Entry metadata object to update with the navigation property metadata.</param>
5105 /// <param name="model" type="Object">Metadata that describes the conceptual schema.</param>
5106
5107 // Get any inline data.
5108 var inlineData;
5109 var inlineElement = xmlFirstChildElement(domElement, odataMetaXmlNs, "inline");
5110 if (inlineElement) {
5111 var inlineDocRoot = xmlFirstChildElement(inlineElement);
5112 var inlineBaseURI = xmlBaseURI(inlineElement, link.baseURI);
5113 inlineData = inlineDocRoot ? atomReadDocument(inlineDocRoot, inlineBaseURI, model) : null;
5114 } else {
5115 // If the link has no inline content, we consider it deferred.
5116 inlineData = { __deferred: { uri: link.href} };
5117 }
5118
5119 var propertyName = link.rel.substring(odataRelatedPrefix.length);
5120
5121 // Set the property value on the entry object.
5122 entry[propertyName] = inlineData;
5123
5124 // Set the extra property information on the entry object metadata.
5125 entryMetadata.properties = entryMetadata.properties || {};
5126 var propertyMetadata = entryMetadata.properties[propertyName] = entryMetadata.properties[propertyName] || {};
5127
5128 propertyMetadata.extensions = link.extensions;
5129 };
5130
5131 var atomReadNamedStreamEditLink = function (link, entry, entryMetadata) {
5132 /// <summary>Reads a link representing the edit-media url of a named stream in an OData Atom document.</summary>
5133 /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
5134 /// <param name="entry" type="Object">Entry object to update with the named stream data.</param>
5135 /// <param name="entryMetadata">Entry metadata object to update with the named stream metadata.</param>
5136
5137 var propertyName = link.rel.substring(odataEditMediaPrefix.length);
5138
5139 var namedStreamMediaResource = atomGetEntryNamedStreamMediaResource(propertyName, entry, entryMetadata);
5140 var mediaResource = namedStreamMediaResource.value;
5141 var mediaResourceMetadata = namedStreamMediaResource.metadata;
5142
5143 var editMedia = link.href;
5144
5145 mediaResource.edit_media = editMedia;
5146 mediaResource.content_type = link.type;
5147 mediaResourceMetadata.edit_media_extensions = link.extensions;
5148
5149 // If there is only the edit link, make it the media self link as well.
5150 mediaResource.media_src = mediaResource.media_src || editMedia;
5151 mediaResourceMetadata.media_src_extensions = mediaResourceMetadata.media_src_extensions || [];
5152
5153 atomReadLinkMediaEtag(link, mediaResource);
5154 };
5155
5156 var atomReadNamedStreamSelfLink = function (link, entry, entryMetadata) {
5157 /// <summary>Reads a link representing the self url of a named stream in an OData Atom document.</summary>
5158 /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
5159 /// <param name="entry" type="Object">Entry object to update with the named stream data.</param>
5160 /// <param name="entryMetadata">Entry metadata object to update with the named stream metadata.</param>
5161
5162 var propertyName = link.rel.substring(odataMediaResourcePrefix.length);
5163
5164 var namedStreamMediaResource = atomGetEntryNamedStreamMediaResource(propertyName, entry, entryMetadata);
5165 var mediaResource = namedStreamMediaResource.value;
5166 var mediaResourceMetadata = namedStreamMediaResource.metadata;
5167
5168 mediaResource.media_src = link.href;
5169 mediaResourceMetadata.media_src_extensions = link.extensions;
5170 mediaResource.content_type = link.type;
5171 };
5172
5173 var atomGetEntryNamedStreamMediaResource = function (name, entry, entryMetadata) {
5174 /// <summary>Gets the media resource object and metadata object for a named stream in an entry object.</summary>
5175 /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
5176 /// <param name="entry" type="Object">Entry object from which the media resource object will be obtained.</param>
5177 /// <param name="entryMetadata" type="Object">Entry metadata object from which the media resource metadata object will be obtained.</param>
5178 /// <remarks>
5179 /// If the entry doest' have a media resource for the named stream indicated by the name argument, then this function will create a new
5180 /// one along with its metadata object.
5181 /// <remarks>
5182 /// <returns type="Object"> Object containing the value and metadata of the named stream's media resource. <returns>
5183
5184 entryMetadata.properties = entryMetadata.properties || {};
5185
5186 var mediaResourceMetadata = entryMetadata.properties[name];
5187 var mediaResource = entry[name] && entry[name].__mediaresource;
5188
5189 if (!mediaResource) {
5190 mediaResource = {};
5191 entry[name] = { __mediaresource: mediaResource };
5192 entryMetadata.properties[name] = mediaResourceMetadata = {};
5193 }
5194 return { value: mediaResource, metadata: mediaResourceMetadata };
5195 };
5196
5197 var atomReadLinkMediaEtag = function (link, mediaResource) {
5198 /// <summary>Gets the media etag from the link extensions and updates the media resource object with it.</summary>
5199 /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
5200 /// <param name="mediaResource" type="Object">Object containing media information for an OData Atom entry.</param>
5201 /// <remarks>
5202 /// The function will remove the extension object for the etag if it finds it in the link extensions and will set
5203 /// its value under the media_etag property of the mediaResource object.
5204 /// <remarks>
5205 /// <returns type="Object"> Object containing the value and metadata of the named stream's media resource. <returns>
5206
5207 var extensions = link.extensions;
5208 var i, len;
5209 for (i = 0, len = extensions.length; i < len; i++) {
5210 if (extensions[i].namespaceURI === odataMetaXmlNs && extensions[i].name === "etag") {
5211 mediaResource.media_etag = extensions[i].value;
5212 extensions.splice(i, 1);
5213 return;
5214 }
5215 }
5216 };
5217
5218 var atomReadEntryStructuralObject = function (domElement, parent, parentMetadata) {
5219 /// <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>
5220 /// <param name="propertiesElement">XML element for the 'properties' node.</param>
5221 /// <param name="parent">
5222 /// Object that will contain the property value. It can be either an antom entry or
5223 /// an atom complex property object.
5224 /// </param>
5225 /// <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>
5226
5227 xmlChildElements(domElement, function (child) {
5228 var property = xmlReadODataProperty(child);
5229 if (property) {
5230 var propertyName = property.name;
5231 var propertiesMetadata = parentMetadata.properties = parentMetadata.properties || {};
5232 propertiesMetadata[propertyName] = property.metadata;
5233 parent[propertyName] = property.value;
5234 }
5235 });
5236 };
5237
5238 var atomReadServiceDocument = function (domElement, baseURI) {
5239 /// <summary>Reads an AtomPub service document</summary>
5240 /// <param name="atomServiceDoc">DOM element for the root of an AtomPub service document</param>
5241 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the AtomPub service document.</param>
5242 /// <returns type="Object">An object that contains the properties of the service document</returns>
5243
5244 var workspaces = [];
5245 var extensions = [];
5246
5247 baseURI = xmlBaseURI(domElement, baseURI);
5248 // Find all the workspace elements.
5249 xmlChildElements(domElement, function (child) {
5250 if (xmlNamespaceURI(child) === appXmlNs && xmlLocalName(child) === "workspace") {
5251 workspaces.push(atomReadServiceDocumentWorkspace(child, baseURI));
5252 return;
5253 }
5254 extensions.push(createElementExtension(child));
5255 });
5256
5257 // AtomPub (RFC 5023 Section 8.3.1) says a service document MUST contain one or
5258 // more workspaces. Throw if we don't find any.
5259 if (workspaces.length === 0) {
5260 throw { message: "Invalid AtomPub service document: No workspace element found.", element: domElement };
5261 }
5262
5263 return { workspaces: workspaces, extensions: extensions };
5264 };
5265
5266 var atomReadServiceDocumentWorkspace = function (domElement, baseURI) {
5267 /// <summary>Reads a single workspace element from an AtomPub service document</summary>
5268 /// <param name="domElement">DOM element that represents a workspace of an AtomPub service document</param>
5269 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the AtomPub service document workspace.</param>
5270 /// <returns type="Object">An object that contains the properties of the workspace</returns>
5271
5272 var collections = [];
5273 var extensions = [];
5274 var title; // = undefined;
5275
5276 baseURI = xmlBaseURI(domElement, baseURI);
5277
5278 xmlChildElements(domElement, function (child) {
5279 var nsURI = xmlNamespaceURI(child);
5280 var localName = xmlLocalName(child);
5281
5282 if (nsURI === atomXmlNs) {
5283 if (localName === "title") {
5284 if (title !== undefined) {
5285 throw { message: "Invalid AtomPub service document: workspace has more than one child title element", element: child };
5286 }
5287
5288 title = xmlInnerText(child);
5289 return;
5290 }
5291 }
5292
5293 if (nsURI === appXmlNs) {
5294 if (localName === "collection") {
5295 collections.push(atomReadServiceDocumentCollection(child, baseURI));
5296 }
5297 return;
5298 }
5299 extensions.push(atomReadExtensionElement(child));
5300 });
5301
5302 return { title: title || "", collections: collections, extensions: extensions };
5303 };
5304
5305 var atomReadServiceDocumentCollection = function (domElement, baseURI) {
5306 /// <summary>Reads a service document collection element into an object.</summary>
5307 /// <param name="domElement">DOM element that represents a collection of an AtomPub service document.</param>
5308 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the AtomPub service document collection.</param>
5309 /// <returns type="Object">An object that contains the properties of the collection.</returns>
5310
5311
5312 var href = xmlAttributeValue(domElement, "href");
5313
5314 if (!href) {
5315 throw { message: "Invalid AtomPub service document: collection has no href attribute", element: domElement };
5316 }
5317
5318 baseURI = xmlBaseURI(domElement, baseURI);
5319 href = normalizeURI(href, xmlBaseURI(domElement, baseURI));
5320 var extensions = [];
5321 var title; // = undefined;
5322
5323 xmlChildElements(domElement, function (child) {
5324 var nsURI = xmlNamespaceURI(child);
5325 var localName = xmlLocalName(child);
5326
5327 if (nsURI === atomXmlNs) {
5328 if (localName === "title") {
5329 if (title !== undefined) {
5330 throw { message: "Invalid AtomPub service document: collection has more than one child title element", element: child };
5331 }
5332 title = xmlInnerText(child);
5333 }
5334 return;
5335 }
5336
5337 if (nsURI !== appXmlNs) {
5338 extensions.push(atomReadExtensionElement(domElement));
5339 }
5340 });
5341
5342 // AtomPub (RFC 5023 Section 8.3.3) says the collection element MUST contain
5343 // a title element. It's likely to be problematic if the service doc doesn't
5344 // have one so here we throw.
5345 if (!title) {
5346 throw { message: "Invalid AtomPub service document: collection has no title element", element: domElement };
5347 }
5348
5349 return { title: title, href: href, extensions: extensions };
5350 };
5351
5352 var atomNewElement = function (dom, name, children) {
5353 /// <summary>Creates a new DOM element in the Atom namespace.</summary>
5354 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
5355 /// <param name="name" type="String">Local name of the Atom element to create.</param>
5356 /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
5357 /// <returns>New DOM element in the Atom namespace.</returns>
5358 /// <remarks>
5359 /// If a value in the children collection is a string, then a new DOM text node is going to be created
5360 /// for it and then appended as a child of the new DOM Element.
5361 /// </remarks>
5362
5363 return xmlNewElement(dom, atomXmlNs, xmlQualifiedName(atomPrefix, name), children);
5364 };
5365
5366 var atomNewAttribute = function (dom, name, value) {
5367 /// <summary>Creates a new DOM attribute for an Atom element in the default namespace.</summary>
5368 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
5369 /// <param name="name" type="String">Local name of the OData attribute to create.</param>
5370 /// <param name="value">Attribute value.</param>
5371 /// <returns>New DOM attribute in the default namespace.</returns>
5372
5373 return xmlNewAttribute(dom, null, name, value);
5374 };
5375
5376 var atomCanRemoveProperty = function (propertyElement) {
5377 /// <summary>Checks whether the property represented by domElement can be removed from the atom document DOM tree.</summary>
5378 /// <param name="propertyElement">DOM element for the property to test.</param>
5379 /// <remarks>
5380 /// The property can only be removed if it doens't have any children and only has namespace or type declaration attributes.
5381 /// </remarks>
5382 /// <returns type="Boolean">True is the property can be removed; false otherwise.</returns>
5383
5384 if (propertyElement.childNodes.length > 0) {
5385 return false;
5386 }
5387
5388 var isEmpty = true;
5389 var attributes = propertyElement.attributes;
5390 var i, len;
5391 for (i = 0, len = attributes.length; i < len && isEmpty; i++) {
5392 var attribute = attributes[i];
5393
5394 isEmpty = isEmpty && isXmlNSDeclaration(attribute) ||
5395 (xmlNamespaceURI(attribute) == odataMetaXmlNs && xmlLocalName(attribute) === "type");
5396 }
5397 return isEmpty;
5398 };
5399
5400 var atomNewODataNavigationProperty = function (dom, name, kind, value, model) {
5401 /// <summary>Creates a new Atom link DOM element for a navigation property in an OData Atom document.</summary>
5402 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
5403 /// <param name="name" type="String">Property name.</param>
5404 /// <param name="kind" type="String">Navigation property kind. Expected values are "deferred", "entry", or "feed".</param>
5405 /// <param name="value" optional="true" mayBeNull="true">Value of the navigation property, if any.</param>
5406 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
5407 /// <returns type="Object">
5408 /// Object containing the new Atom link DOM element for the navigation property and the
5409 /// required data service version for this property.
5410 /// </returns>
5411
5412 var linkType = null;
5413 var linkContent = null;
5414 var linkContentBodyData = null;
5415 var href = "";
5416
5417 if (kind !== "deferred") {
5418 linkType = atomNewAttribute(dom, "type", "application/atom+xml;type=" + kind);
5419 linkContent = xmlNewODataMetaElement(dom, "inline");
5420
5421 if (value) {
5422 href = value.__metadata && value.__metadata.uri || "";
5423 linkContentBodyData =
5424 atomNewODataFeed(dom, value, model) ||
5425 atomNewODataEntry(dom, value, model);
5426 xmlAppendChild(linkContent, linkContentBodyData.element);
5427 }
5428 } else {
5429 href = value.__deferred.uri;
5430 }
5431
5432 var navProp = atomNewElement(dom, "link", [
5433 atomNewAttribute(dom, "href", href),
5434 atomNewAttribute(dom, "rel", normalizeURI(name, odataRelatedPrefix)),
5435 linkType,
5436 linkContent
5437 ]);
5438
5439 return xmlNewODataElementInfo(navProp, linkContentBodyData ? linkContentBodyData.dsv : "1.0");
5440 };
5441
5442 var atomNewODataEntryDataItem = function (dom, name, value, dataItemMetadata, dataItemModel, model) {
5443 /// <summary>Creates a new DOM element for a data item in an entry, complex property, or collection property.</summary>
5444 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
5445 /// <param name="name" type="String">Data item name.</param>
5446 /// <param name="value" optional="true" mayBeNull="true">Value of the data item, if any.</param>
5447 /// <param name="dataItemMetadata" type="Object" optional="true">Object containing metadata about the data item.</param>
5448 /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
5449 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
5450 /// <returns type="Object">
5451 /// Object containing the new DOM element in the appropriate namespace for the data item and the
5452 /// required data service version for it.
5453 /// </returns>
5454
5455 if (isNamedStream(value)) {
5456 return null;
5457 }
5458
5459 var dataElement = xmlNewODataDataElement(dom, name, value, dataItemMetadata, dataItemModel, model);
5460 if (!dataElement) {
5461 // This may be a navigation property.
5462 var navPropKind = navigationPropertyKind(value, dataItemModel);
5463
5464 dataElement = atomNewODataNavigationProperty(dom, name, navPropKind, value, model);
5465 }
5466 return dataElement;
5467 };
5468
5469 var atomEntryCustomization = function (dom, entry, entryProperties, customization) {
5470 /// <summary>Applies a feed customization by transforming an Atom entry DOM element as needed.</summary>
5471 /// <param name="dom">DOM document used for creating any new DOM nodes required by the customization.</param>
5472 /// <param name="entry">DOM element for the Atom entry to which the customization is going to be applied.</param>
5473 /// <param name="entryProperties">DOM element containing the properties of the Atom entry.</param>
5474 /// <param name="customization" type="Object">Object describing an applicable feed customization.</param>
5475 /// <remarks>
5476 /// Look into the atomfeedCustomization function for a description of the customization object.
5477 /// </remarks>
5478 /// <returns type="String">Data service version required by the applied customization</returns>
5479
5480 var atomProperty = xmlFindElementByPath(entryProperties, odataXmlNs, customization.propertyPath);
5481 var atomPropertyNullAttribute = atomProperty && xmlAttributeNode(atomProperty, "null", odataMetaXmlNs);
5482 var atomPropertyValue;
5483 var dataServiceVersion = "1.0";
5484
5485 if (atomPropertyNullAttribute && atomPropertyNullAttribute.value === "true") {
5486 return dataServiceVersion;
5487 }
5488
5489 if (atomProperty) {
5490 atomPropertyValue = xmlInnerText(atomProperty) || "";
5491 if (!customization.keepInContent) {
5492 dataServiceVersion = "2.0";
5493 var parent = atomProperty.parentNode;
5494 var candidate = parent;
5495
5496 parent.removeChild(atomProperty);
5497 while (candidate !== entryProperties && atomCanRemoveProperty(candidate)) {
5498 parent = candidate.parentNode;
5499 parent.removeChild(candidate);
5500 candidate = parent;
5501 }
5502 }
5503 }
5504
5505 var targetNode = xmlNewNodeByPath(dom, entry,
5506 customization.nsURI, customization.nsPrefix, customization.entryPath);
5507
5508 if (targetNode.nodeType === 2) {
5509 targetNode.value = atomPropertyValue;
5510 return dataServiceVersion;
5511 }
5512
5513 var contentKind = customization.contentKind;
5514 xmlAppendChildren(targetNode, [
5515 contentKind && xmlNewAttribute(dom, null, "type", contentKind),
5516 contentKind === "xhtml" ? xmlNewFragment(dom, atomPropertyValue) : atomPropertyValue
5517 ]);
5518
5519 return dataServiceVersion;
5520 };
5521
5522 var atomNewODataEntry = function (dom, data, model) {
5523 /// <summary>Creates a new DOM element for an Atom entry.</summary>
5524 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
5525 /// <param name="data" type="Object">Entry object in the library's internal representation.</param>
5526 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
5527 /// <returns type="Object">
5528 /// Object containing the new DOM element for the Atom entry and the required data service version for it.
5529 /// </returns>
5530
5531 var payloadMetadata = data.__metadata || {};
5532 var propertiesMetadata = payloadMetadata.properties || {};
5533
5534 var etag = payloadMetadata.etag;
5535 var uri = payloadMetadata.uri;
5536 var typeName = payloadMetadata.type;
5537 var entityType = lookupEntityType(typeName, model);
5538
5539 var properties = xmlNewODataMetaElement(dom, "properties");
5540 var entry = atomNewElement(dom, "entry", [
5541 atomNewElement(dom, "author",
5542 atomNewElement(dom, "name")
5543 ),
5544 etag && xmlNewODataMetaAttribute(dom, "etag", etag),
5545 uri && atomNewElement(dom, "id", uri),
5546 typeName && atomNewElement(dom, "category", [
5547 atomNewAttribute(dom, "term", typeName),
5548 atomNewAttribute(dom, "scheme", odataScheme)
5549 ]),
5550 // TODO: MLE support goes here.
5551 atomNewElement(dom, "content", [
5552 atomNewAttribute(dom, "type", "application/xml"),
5553 properties
5554 ])
5555 ]);
5556
5557 var dataServiceVersion = "1.0";
5558 for (var name in data) {
5559 if (name !== "__metadata") {
5560 var entryDataItemMetadata = propertiesMetadata[name] || {};
5561 var entryDataItemModel = entityType && (
5562 lookupProperty(entityType.property, name) ||
5563 lookupProperty(entityType.navigationProperty, name));
5564
5565 var entryDataItem = atomNewODataEntryDataItem(dom, name, data[name], entryDataItemMetadata, entryDataItemModel, model);
5566 if (entryDataItem) {
5567 var entryElement = entryDataItem.element;
5568 var entryElementParent = (xmlNamespaceURI(entryElement) === atomXmlNs) ? entry : properties;
5569
5570 xmlAppendChild(entryElementParent, entryElement);
5571 dataServiceVersion = maxVersion(dataServiceVersion, entryDataItem.dsv);
5572 }
5573 }
5574 }
5575
5576 atomApplyAllFeedCustomizations(entityType, model, function (customization) {
5577 var customizationDsv = atomEntryCustomization(dom, entry, properties, customization);
5578 dataServiceVersion = maxVersion(dataServiceVersion, customizationDsv);
5579 });
5580
5581 return xmlNewODataElementInfo(entry, dataServiceVersion);
5582 };
5583
5584 var atomNewODataFeed = function (dom, data, model) {
5585 /// <summary>Creates a new DOM element for an Atom feed.</summary>
5586 /// <param name="dom">DOM document used for creating the new DOM Element.</param>
5587 /// <param name="data" type="Object">Feed object in the library's internal representation.</param>
5588 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
5589 /// <returns type="Object">
5590 /// Object containing the new DOM element for the Atom feed and the required data service version for it.
5591 /// </returns>
5592
5593 var entries = isArray(data) ? data : data.results;
5594
5595 if (!entries) {
5596 return null;
5597 }
5598
5599 var dataServiceVersion = "1.0";
5600 var atomFeed = atomNewElement(dom, "feed");
5601
5602 var i, len;
5603 for (i = 0, len = entries.length; i < len; i++) {
5604 var atomEntryData = atomNewODataEntry(dom, entries[i], model);
5605 xmlAppendChild(atomFeed, atomEntryData.element);
5606 dataServiceVersion = maxVersion(dataServiceVersion, atomEntryData.dsv);
5607 }
5608 return xmlNewODataElementInfo(atomFeed, dataServiceVersion);
5609 };
5610
5611 var atomNewODataDocument = function (data, model) {
5612 /// <summary>Creates a new OData Atom document.</summary>
5613 /// <param name="data" type="Object">Feed or entry object in the libary's internal representaion.</param>
5614 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
5615 /// <returns type="Object">
5616 /// Object containing the new DOM document for the Atom document and the required data service version for it.
5617 /// </returns>
5618
5619 if (data) {
5620 var atomRootWriter = isFeed(data) && atomNewODataFeed ||
5621 isObject(data) && atomNewODataEntry;
5622
5623 if (atomRootWriter) {
5624 var dom = xmlDom();
5625 var atomRootData = atomRootWriter(dom, data, model);
5626
5627 if (atomRootData) {
5628 var atomRootElement = atomRootData.element;
5629 xmlAppendChildren(atomRootElement, [
5630 xmlNewNSDeclaration(dom, odataMetaXmlNs, odataMetaPrefix),
5631 xmlNewNSDeclaration(dom, odataXmlNs, odataPrefix)
5632 ]);
5633 return xmlNewODataElementInfo(xmlAppendChild(dom, atomRootElement), atomRootData.dsv);
5634 }
5635 }
5636 }
5637 return null;
5638 };
5639
5640 var atomParser = function (handler, text, context) {
5641 /// <summary>Parses an ATOM document (feed, entry or service document).</summary>
5642 /// <param name="handler">This handler.</param>
5643 /// <param name="text" type="String">Document text.</param>
5644 /// <param name="context" type="Object">Object with parsing context.</param>
5645 /// <returns>An object representation of the document; undefined if not applicable.</returns>
5646
5647 if (text) {
5648 var atomDoc = xmlParse(text);
5649 var atomRoot = xmlFirstChildElement(atomDoc);
5650 if (atomRoot) {
5651 return atomReadDocument(atomRoot, null, context.metadata);
5652 }
5653 }
5654 };
5655
5656 var atomSerializer = function (handler, data, context) {
5657 /// <summary>Serializes an ATOM object into a document (feed or entry).</summary>
5658 /// <param name="handler">This handler.</param>
5659 /// <param name="data" type="Object">Representation of feed or entry.</param>
5660 /// <param name="context" type="Object">Object with parsing context.</param>
5661 /// <returns>An text representation of the data object; undefined if not applicable.</returns>
5662
5663 var cType = context.contentType = context.contentType || contentType(atomMediaType);
5664 if (cType && cType.mediaType === atomMediaType) {
5665 var atomDoc = atomNewODataDocument(data, context.metadata);
5666 if (atomDoc) {
5667 context.dataServiceVersion = maxVersion(context.dataServiceVersion || "1.0", atomDoc.dsv);
5668 return xmlSerialize(atomDoc.element);
5669 }
5670 }
5671 // Allow undefined to be returned.
5672 };
5673
5674 odata.atomHandler = handler(atomParser, atomSerializer, atomAcceptTypes.join(","), MAX_DATA_SERVICE_VERSION);
5675
5676
5677
5678 var schemaElement = function (attributes, elements, text, ns) {
5679 /// <summary>Creates an object that describes an element in an schema.</summary>
5680 /// <param name="attributes" type="Array">List containing the names of the attributes allowed for this element.</param>
5681 /// <param name="elements" type="Array">List containing the names of the child elements allowed for this element.</param>
5682 /// <param name="text" type="Boolean">Flag indicating if the element's text value is of interest or not.</param>
5683 /// <param name="ns" type="String">Namespace to which the element belongs to.</param>
5684 /// <remarks>
5685 /// If a child element name ends with * then it is understood by the schema that that child element can appear 0 or more times.
5686 /// </remarks>
5687 /// <returns type="Object">Object with attributes, elements, text, and ns fields.</returns>
5688
5689 return {
5690 attributes: attributes,
5691 elements: elements,
5692 text: text || false,
5693 ns: ns
5694 };
5695 };
5696
5697 // It's assumed that all elements may have Documentation children and Annotation elements.
5698 // See http://msdn.microsoft.com/en-us/library/bb399292.aspx for a CSDL reference.
5699 var schema = {
5700 elements: {
5701 Annotations: schemaElement(
5702 /*attributes*/["Target", "Qualifier"],
5703 /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
5704 ),
5705 Association: schemaElement(
5706 /*attributes*/["Name"],
5707 /*elements*/["End*", "ReferentialConstraint", "TypeAnnotation*", "ValueAnnotation*"]
5708 ),
5709 AssociationSet: schemaElement(
5710 /*attributes*/["Name", "Association"],
5711 /*elements*/["End*", "TypeAnnotation*", "ValueAnnotation*"]
5712 ),
5713 Binary: schemaElement(
5714 /*attributes*/null,
5715 /*elements*/null,
5716 /*text*/true
5717 ),
5718 Bool: schemaElement(
5719 /*attributes*/null,
5720 /*elements*/null,
5721 /*text*/true
5722 ),
5723 Collection: schemaElement(
5724 /*attributes*/null,
5725 /*elements*/["String*", "Int*", "Float*", "Decimal*", "Bool*", "DateTime*", "DateTimeOffset*", "Guid*", "Binary*", "Time*", "Collection*", "Record*"]
5726 ),
5727 CollectionType: schemaElement(
5728 /*attributes*/["ElementType", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "SRID"],
5729 /*elements*/["CollectionType", "ReferenceType", "RowType", "TypeRef"]
5730 ),
5731 ComplexType: schemaElement(
5732 /*attributes*/["Name", "BaseType", "Abstract"],
5733 /*elements*/["Property*", "TypeAnnotation*", "ValueAnnotation*"]
5734 ),
5735 DateTime: schemaElement(
5736 /*attributes*/null,
5737 /*elements*/null,
5738 /*text*/true
5739 ),
5740 DateTimeOffset: schemaElement(
5741 /*attributes*/null,
5742 /*elements*/null,
5743 /*text*/true
5744 ),
5745 Decimal: schemaElement(
5746 /*attributes*/null,
5747 /*elements*/null,
5748 /*text*/true
5749 ),
5750 DefiningExpression: schemaElement(
5751 /*attributes*/null,
5752 /*elements*/null,
5753 /*text*/true
5754 ),
5755 Dependent: schemaElement(
5756 /*attributes*/["Role"],
5757 /*elements*/["PropertyRef*"]
5758 ),
5759 Documentation: schemaElement(
5760 /*attributes*/null,
5761 /*elements*/null,
5762 /*text*/true
5763 ),
5764 End: schemaElement(
5765 /*attributes*/["Type", "Role", "Multiplicity", "EntitySet"],
5766 /*elements*/["OnDelete"]
5767 ),
5768 EntityContainer: schemaElement(
5769 /*attributes*/["Name", "Extends"],
5770 /*elements*/["EntitySet*", "AssociationSet*", "FunctionImport*", "TypeAnnotation*", "ValueAnnotation*"]
5771 ),
5772 EntitySet: schemaElement(
5773 /*attributes*/["Name", "EntityType"],
5774 /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
5775 ),
5776 EntityType: schemaElement(
5777 /*attributes*/["Name", "BaseType", "Abstract", "OpenType"],
5778 /*elements*/["Key", "Property*", "NavigationProperty*", "TypeAnnotation*", "ValueAnnotation*"]
5779 ),
5780 EnumType: schemaElement(
5781 /*attributes*/["Name", "UnderlyingType", "IsFlags"],
5782 /*elements*/["Member*"]
5783 ),
5784 Float: schemaElement(
5785 /*attributes*/null,
5786 /*elements*/null,
5787 /*text*/true
5788 ),
5789 Function: schemaElement(
5790 /*attributes*/["Name", "ReturnType"],
5791 /*elements*/["Parameter*", "DefiningExpression", "ReturnType", "TypeAnnotation*", "ValueAnnotation*"]
5792 ),
5793 FunctionImport: schemaElement(
5794 /*attributes*/["Name", "ReturnType", "EntitySet", "IsSideEffecting", "IsComposable", "IsBindable", "EntitySetPath"],
5795 /*elements*/["Parameter*", "ReturnType", "TypeAnnotation*", "ValueAnnotation*"]
5796 ),
5797 Guid: schemaElement(
5798 /*attributes*/null,
5799 /*elements*/null,
5800 /*text*/true
5801 ),
5802 Int: schemaElement(
5803 /*attributes*/null,
5804 /*elements*/null,
5805 /*text*/true
5806 ),
5807 Key: schemaElement(
5808 /*attributes*/null,
5809 /*elements*/["PropertyRef*"]
5810 ),
5811 LabeledElement: schemaElement(
5812 /*attributes*/["Name"],
5813 /*elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
5814 ),
5815 Member: schemaElement(
5816 /*attributes*/["Name", "Value"]
5817 ),
5818 NavigationProperty: schemaElement(
5819 /*attributes*/["Name", "Relationship", "ToRole", "FromRole", "ContainsTarget"],
5820 /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
5821 ),
5822 Null: schemaElement(
5823 /*attributes*/null,
5824 /*elements*/null
5825 ),
5826 OnDelete: schemaElement(
5827 /*attributes*/["Action"]
5828 ),
5829 Path: schemaElement(
5830 /*attributes*/null,
5831 /*elements*/null,
5832 /*text*/true
5833 ),
5834 Parameter: schemaElement(
5835 /*attributes*/["Name", "Type", "Mode", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "ConcurrencyMode", "SRID"],
5836 /*elements*/["CollectionType", "ReferenceType", "RowType", "TypeRef", "TypeAnnotation*", "ValueAnnotation*"]
5837 ),
5838 Principal: schemaElement(
5839 /*attributes*/["Role"],
5840 /*elements*/["PropertyRef*"]
5841 ),
5842 Property: schemaElement(
5843 /*attributes*/["Name", "Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "ConcurrencyMode", "CollectionKind", "SRID"],
5844 /*elements*/["CollectionType", "ReferenceType", "RowType", "TypeAnnotation*", "ValueAnnotation*"]
5845 ),
5846 PropertyRef: schemaElement(
5847 /*attributes*/["Name"]
5848 ),
5849 PropertyValue: schemaElement(
5850 /*attributes*/["Property", "Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time"],
5851 /*Elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
5852 ),
5853 ReferenceType: schemaElement(
5854 /*attributes*/["Type"]
5855 ),
5856 ReferentialConstraint: schemaElement(
5857 /*attributes*/null,
5858 /*elements*/["Principal", "Dependent"]
5859 ),
5860 ReturnType: schemaElement(
5861 /*attributes*/["ReturnType", "Type", "EntitySet"],
5862 /*elements*/["CollectionType", "ReferenceType", "RowType"]
5863 ),
5864 RowType: schemaElement(
5865 /*elements*/["Property*"]
5866 ),
5867 String: schemaElement(
5868 /*attributes*/null,
5869 /*elements*/null,
5870 /*text*/true
5871 ),
5872 Schema: schemaElement(
5873 /*attributes*/["Namespace", "Alias"],
5874 /*elements*/["Using*", "EntityContainer*", "EntityType*", "Association*", "ComplexType*", "Function*", "ValueTerm*", "Annotations*"]
5875 ),
5876 Time: schemaElement(
5877 /*attributes*/null,
5878 /*elements*/null,
5879 /*text*/true
5880 ),
5881 TypeAnnotation: schemaElement(
5882 /*attributes*/["Term", "Qualifier"],
5883 /*elements*/["PropertyValue*"]
5884 ),
5885 TypeRef: schemaElement(
5886 /*attributes*/["Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "SRID"]
5887 ),
5888 Using: schemaElement(
5889 /*attributes*/["Namespace", "Alias"]
5890 ),
5891 ValueAnnotation: schemaElement(
5892 /*attributes*/["Term", "Qualifier", "Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time"],
5893 /*Elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
5894 ),
5895 ValueTerm: schemaElement(
5896 /*attributes*/["Name", "Type"],
5897 /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
5898 ),
5899
5900 // See http://msdn.microsoft.com/en-us/library/dd541238(v=prot.10) for an EDMX reference.
5901 Edmx: schemaElement(
5902 /*attributes*/["Version"],
5903 /*elements*/["DataServices", "Reference*", "AnnotationsReference*"],
5904 /*text*/false,
5905 /*ns*/edmxNs
5906 ),
5907 DataServices: schemaElement(
5908 /*attributes*/null,
5909 /*elements*/["Schema*"],
5910 /*text*/false,
5911 /*ns*/edmxNs
5912 )
5913 }
5914 };
5915
5916 // See http://msdn.microsoft.com/en-us/library/ee373839.aspx for a feed customization reference.
5917 var customizationAttributes = ["m:FC_ContentKind", "m:FC_KeepInContent", "m:FC_NsPrefix", "m:FC_NsUri", "m:FC_SourcePath", "m:FC_TargetPath"];
5918 schema.elements.Property.attributes = schema.elements.Property.attributes.concat(customizationAttributes);
5919 schema.elements.EntityType.attributes = schema.elements.EntityType.attributes.concat(customizationAttributes);
5920
5921 // See http://msdn.microsoft.com/en-us/library/dd541284(PROT.10).aspx for an EDMX reference.
5922 schema.elements.Edmx = { attributes: ["Version"], elements: ["DataServices"], ns: edmxNs };
5923 schema.elements.DataServices = { elements: ["Schema*"], ns: edmxNs };
5924
5925 // See http://msdn.microsoft.com/en-us/library/dd541233(v=PROT.10) for Conceptual Schema Definition Language Document for Data Services.
5926 schema.elements.EntityContainer.attributes.push("m:IsDefaultEntityContainer");
5927 schema.elements.Property.attributes.push("m:MimeType");
5928 schema.elements.FunctionImport.attributes.push("m:HttpMethod");
5929 schema.elements.FunctionImport.attributes.push("m:IsAlwaysBindable");
5930 schema.elements.EntityType.attributes.push("m:HasStream");
5931 schema.elements.DataServices.attributes = ["m:DataServiceVersion", "m:MaxDataServiceVersion"];
5932
5933 var scriptCase = function (text) {
5934 /// <summary>Converts a Pascal-case identifier into a camel-case identifier.</summary>
5935 /// <param name="text" type="String">Text to convert.</param>
5936 /// <returns type="String">Converted text.</returns>
5937 /// <remarks>If the text starts with multiple uppercase characters, it is left as-is.</remarks>
5938
5939 if (!text) {
5940 return text;
5941 }
5942
5943 if (text.length > 1) {
5944 var firstTwo = text.substr(0, 2);
5945 if (firstTwo === firstTwo.toUpperCase()) {
5946 return text;
5947 }
5948
5949 return text.charAt(0).toLowerCase() + text.substr(1);
5950 }
5951
5952 return text.charAt(0).toLowerCase();
5953 };
5954
5955 var getChildSchema = function (parentSchema, candidateName) {
5956 /// <summary>Gets the schema node for the specified element.</summary>
5957 /// <param name="parentSchema" type="Object">Schema of the parent XML node of 'element'.</param>
5958 /// <param name="candidateName">XML element name to consider.</param>
5959 /// <returns type="Object">The schema that describes the specified element; null if not found.</returns>
5960
5961 if (candidateName === "Documentation") {
5962 return { isArray: true, propertyName: "documentation" };
5963 }
5964
5965 var elements = parentSchema.elements;
5966 if (!elements) {
5967 return null;
5968 }
5969
5970 var i, len;
5971 for (i = 0, len = elements.length; i < len; i++) {
5972 var elementName = elements[i];
5973 var multipleElements = false;
5974 if (elementName.charAt(elementName.length - 1) === "*") {
5975 multipleElements = true;
5976 elementName = elementName.substr(0, elementName.length - 1);
5977 }
5978
5979 if (candidateName === elementName) {
5980 var propertyName = scriptCase(elementName);
5981 return { isArray: multipleElements, propertyName: propertyName };
5982 }
5983 }
5984
5985 return null;
5986 };
5987
5988 // This regular expression is used to detect a feed customization element
5989 // after we've normalized it into the 'm' prefix. It starts with m:FC_,
5990 // followed by other characters, and ends with _ and a number.
5991 // The captures are 0 - whole string, 1 - name as it appears in internal table.
5992 var isFeedCustomizationNameRE = /^(m:FC_.*)_[0-9]+$/;
5993
5994 var isEdmNamespace = function (nsURI) {
5995 /// <summary>Checks whether the specifies namespace URI is one of the known CSDL namespace URIs.</summary>
5996 /// <param name="nsURI" type="String">Namespace URI to check.</param>
5997 /// <returns type="Boolean">true if nsURI is a known CSDL namespace; false otherwise.</returns>
5998
5999 return nsURI === edmNs1 ||
6000 nsURI === edmNs1_1 ||
6001 nsURI === edmNs1_2 ||
6002 nsURI === edmNs2a ||
6003 nsURI === edmNs2b ||
6004 nsURI === edmNs3;
6005 };
6006
6007 var parseConceptualModelElement = function (element) {
6008 /// <summary>Parses a CSDL document.</summary>
6009 /// <param name="element">DOM element to parse.</param>
6010 /// <returns type="Object">An object describing the parsed element.</returns>
6011
6012 var localName = xmlLocalName(element);
6013 var nsURI = xmlNamespaceURI(element);
6014 var elementSchema = schema.elements[localName];
6015 if (!elementSchema) {
6016 return null;
6017 }
6018
6019 if (elementSchema.ns) {
6020 if (nsURI !== elementSchema.ns) {
6021 return null;
6022 }
6023 } else if (!isEdmNamespace(nsURI)) {
6024 return null;
6025 }
6026
6027 var item = {};
6028 var extensions = [];
6029 var attributes = elementSchema.attributes || [];
6030 xmlAttributes(element, function (attribute) {
6031
6032 var localName = xmlLocalName(attribute);
6033 var nsURI = xmlNamespaceURI(attribute);
6034 var value = attribute.value;
6035
6036 // Don't do anything with xmlns attributes.
6037 if (nsURI === xmlnsNS) {
6038 return;
6039 }
6040
6041 // Currently, only m: for metadata is supported as a prefix in the internal schema table,
6042 // un-prefixed element names imply one a CSDL element.
6043 var schemaName = null;
6044 var handled = false;
6045 if (isEdmNamespace(nsURI) || nsURI === null) {
6046 schemaName = "";
6047 } else if (nsURI === odataMetaXmlNs) {
6048 schemaName = "m:";
6049 }
6050
6051 if (schemaName !== null) {
6052 schemaName += localName;
6053
6054 // Feed customizations for complex types have additional
6055 // attributes with a suffixed counter starting at '1', so
6056 // take that into account when doing the lookup.
6057 var match = isFeedCustomizationNameRE.exec(schemaName);
6058 if (match) {
6059 schemaName = match[1];
6060 }
6061
6062 if (contains(attributes, schemaName)) {
6063 handled = true;
6064 item[scriptCase(localName)] = value;
6065 }
6066 }
6067
6068 if (!handled) {
6069 extensions.push(createAttributeExtension(attribute));
6070 }
6071 });
6072
6073 xmlChildElements(element, function (child) {
6074 var localName = xmlLocalName(child);
6075 var childSchema = getChildSchema(elementSchema, localName);
6076 if (childSchema) {
6077 if (childSchema.isArray) {
6078 var arr = item[childSchema.propertyName];
6079 if (!arr) {
6080 arr = [];
6081 item[childSchema.propertyName] = arr;
6082 }
6083 arr.push(parseConceptualModelElement(child));
6084 } else {
6085 item[childSchema.propertyName] = parseConceptualModelElement(child);
6086 }
6087 } else {
6088 extensions.push(createElementExtension(child));
6089 }
6090 });
6091
6092 if (elementSchema.text) {
6093 item.text = xmlInnerText(element);
6094 }
6095
6096 if (extensions.length) {
6097 item.extensions = extensions;
6098 }
6099
6100 return item;
6101 };
6102
6103 var metadataParser = function (handler, text) {
6104 /// <summary>Parses a metadata document.</summary>
6105 /// <param name="handler">This handler.</param>
6106 /// <param name="text" type="String">Metadata text.</param>
6107 /// <returns>An object representation of the conceptual model.</returns>
6108
6109 var doc = xmlParse(text);
6110 var root = xmlFirstChildElement(doc);
6111 return parseConceptualModelElement(root) || undefined;
6112 };
6113
6114 odata.metadataHandler = handler(metadataParser, null, xmlMediaType, MAX_DATA_SERVICE_VERSION);
6115
6116
6117
6118 var PAYLOADTYPE_OBJECT = "o";
6119 var PAYLOADTYPE_FEED = "f";
6120 var PAYLOADTYPE_PRIMITIVE = "p";
6121 var PAYLOADTYPE_COLLECTION = "c";
6122 var PAYLOADTYPE_SVCDOC = "s";
6123 var PAYLOADTYPE_LINKS = "l";
6124
6125 var odataNs = "odata";
6126 var odataAnnotationPrefix = odataNs + ".";
6127
6128 var bindAnnotation = "@" + odataAnnotationPrefix + "bind";
6129 var metadataAnnotation = odataAnnotationPrefix + "metadata";
6130 var navUrlAnnotation = odataAnnotationPrefix + "navigationLinkUrl";
6131 var typeAnnotation = odataAnnotationPrefix + "type";
6132
6133 var jsonLightNameMap = {
6134 readLink: "self",
6135 editLink: "edit",
6136 nextLink: "__next",
6137 mediaReadLink: "media_src",
6138 mediaEditLink: "edit_media",
6139 mediaContentType: "content_type",
6140 mediaETag: "media_etag",
6141 count: "__count",
6142 media_src: "mediaReadLink",
6143 edit_media: "mediaEditLink",
6144 content_type: "mediaContentType",
6145 media_etag: "mediaETag",
6146 url: "uri"
6147 };
6148
6149 var jsonLightAnnotations = {
6150 metadata: "odata.metadata",
6151 count: "odata.count",
6152 next: "odata.nextLink",
6153 id: "odata.id",
6154 etag: "odata.etag",
6155 read: "odata.readLink",
6156 edit: "odata.editLink",
6157 mediaRead: "odata.mediaReadLink",
6158 mediaEdit: "odata.mediaEditLink",
6159 mediaEtag: "odata.mediaETag",
6160 mediaContentType: "odata.mediaContentType",
6161 actions: "odata.actions",
6162 functions: "odata.functions",
6163 navigationUrl: "odata.navigationLinkUrl",
6164 associationUrl: "odata.associationLinkUrl",
6165 type: "odata.type"
6166 };
6167
6168 var jsonLightAnnotationInfo = function (annotation) {
6169 /// <summary>Gets the name and target of an annotation in a JSON light payload.</summary>
6170 /// <param name="annotation" type="String">JSON light payload annotation.</param>
6171 /// <returns type="Object">Object containing the annotation name and the target property name.</param>
6172
6173 if (annotation.indexOf(".") > 0) {
6174 var targetEnd = annotation.indexOf("@");
6175 var target = targetEnd > -1 ? annotation.substring(0, targetEnd) : null;
6176 var name = annotation.substring(targetEnd + 1);
6177
6178 return {
6179 target: target,
6180 name: name,
6181 isOData: name.indexOf(odataAnnotationPrefix) === 0
6182 };
6183 }
6184 return null;
6185 };
6186
6187 var jsonLightDataItemType = function (name, value, container, dataItemModel, model) {
6188 /// <summary>Gets the type name of a JSON light data item that belongs to a feed, an entry, a complex type property, or a collection property.</summary>
6189 /// <param name="name" type="String">Name of the data item for which the type name is going to be retrieved.</param>
6190 /// <param name="value">Value of the data item.</param>
6191 /// <param name="container" type="Object">JSON light object that owns the data item.</param>
6192 /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
6193 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6194 /// <remarks>
6195 /// This function will first try to get the type name from the data item's value itself if it is a JSON light object; otherwise
6196 /// it will try to get it from the odata.type annotation applied to the data item in the container. Then, it will fallback to the data item model.
6197 /// If all attempts fail, it will return null.
6198 /// </remarks>
6199 /// <returns type="String">Data item type name; null if the type name cannot be found.</returns>
6200
6201 return (isComplex(value) && value[typeAnnotation]) ||
6202 (container && container[name + "@" + typeAnnotation]) ||
6203 (dataItemModel && dataItemModel.type) ||
6204 (lookupNavigationPropertyType(dataItemModel, model)) ||
6205 null;
6206 };
6207
6208 var jsonLightDataItemModel = function (name, containerModel) {
6209 /// <summary>Gets an object describing a data item in an OData conceptual schema.</summary>
6210 /// <param name="name" type="String">Name of the data item for which the model is going to be retrieved.</param>
6211 /// <param name="containerModel" type="Object">Object describing the owner of the data item in an OData conceptual schema.</param>
6212 /// <returns type="Object">Object describing the data item; null if it cannot be found.</returns>
6213
6214 if (containerModel) {
6215 return lookupProperty(containerModel.property, name) ||
6216 lookupProperty(containerModel.navigationProperty, name);
6217 }
6218 return null;
6219 };
6220
6221 var jsonLightIsEntry = function (data) {
6222 /// <summary>Determines whether data represents a JSON light entry object.</summary>
6223 /// <param name="data" type="Object">JSON light object to test.</param>
6224 /// <returns type="Boolean">True if the data is JSON light entry object; false otherwise.</returns>
6225
6226 return isComplex(data) && ((odataAnnotationPrefix + "id") in data);
6227 };
6228
6229 var jsonLightIsNavigationProperty = function (name, data, dataItemModel) {
6230 /// <summary>Determines whether a data item in a JSON light object is a navigation property.</summary>
6231 /// <param name="name" type="String">Name of the data item to test.</param>
6232 /// <param name="data" type="Object">JSON light object that owns the data item.</param>
6233 /// <param name="dataItemModel" type="Object">Object describing the data item in an OData conceptual schema.</param>
6234 /// <returns type="Boolean">True if the data item is a navigation property; false otherwise.</returns>
6235
6236 if (!!data[name + "@" + navUrlAnnotation] || (dataItemModel && dataItemModel.relationship)) {
6237 return true;
6238 }
6239
6240 // Sniff the property value.
6241 var value = isArray(data[name]) ? data[name][0] : data[name];
6242 return jsonLightIsEntry(value);
6243 };
6244
6245 var jsonLightIsPrimitiveType = function (typeName) {
6246 /// <summary>Determines whether a type name is a primitive type in a JSON light payload.</summary>
6247 /// <param name="typeName" type="String">Type name to test.</param>
6248 /// <returns type="Boolean">True if the type name an EDM primitive type or an OData spatial type; false otherwise.</returns>
6249
6250 return isPrimitiveEdmType(typeName) || isGeographyEdmType(typeName) || isGeometryEdmType(typeName);
6251 };
6252
6253 var jsonLightReadDataAnnotations = function (data, obj, baseURI, dataModel, model) {
6254 /// <summary>Converts annotations found in a JSON light payload object to either properties or metadata.</summary>
6255 /// <param name="data" type="Object">JSON light payload object containing the annotations to convert.</param>
6256 /// <param name="obj" type="Object">Object that will store the converted annotations.</param>
6257 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6258 /// <param name="dataModel" type="Object">Object describing the JSON light payload in an OData conceptual schema.</param>
6259 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6260 /// <returns>JSON light payload object with its annotations converted to either properties or metadata.</param>
6261
6262 for (var name in data) {
6263 if (name.indexOf(".") > 0 && name.charAt(0) !== "#") {
6264 var annotationInfo = jsonLightAnnotationInfo(name);
6265 if (annotationInfo) {
6266 var annotationName = annotationInfo.name;
6267 var target = annotationInfo.target;
6268 var targetModel = null;
6269 var targetType = null;
6270
6271 if (target) {
6272 targetModel = jsonLightDataItemModel(target, dataModel);
6273 targetType = jsonLightDataItemType(target, data[target], data, targetModel, model);
6274 }
6275
6276 if (annotationInfo.isOData) {
6277 jsonLightApplyPayloadODataAnnotation(annotationName, target, targetType, data[name], data, obj, baseURI);
6278 } else {
6279 obj[name] = data[name];
6280 }
6281 }
6282 }
6283 }
6284 return obj;
6285 };
6286
6287 var jsonLightApplyPayloadODataAnnotation = function (name, target, targetType, value, data, obj, baseURI) {
6288 /// <summary>
6289 /// Processes a JSON Light payload OData annotation producing either a property, payload metadata, or property metadata on its owner object.
6290 /// </summary>
6291 /// <param name="name" type="String">Annotation name.</param>
6292 /// <param name="target" type="String">Name of the property that is being targeted by the annotation.</param>
6293 /// <param name="targetType" type="String">Type name of the target property.</param>
6294 /// <param name="data" type="Object">JSON light object containing the annotation.</param>
6295 /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
6296 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6297
6298 var annotation = name.substring(odataAnnotationPrefix.length);
6299
6300 switch (annotation) {
6301 case "navigationLinkUrl":
6302 jsonLightApplyNavigationUrlAnnotation(annotation, target, targetType, value, data, obj, baseURI);
6303 return;
6304 case "nextLink":
6305 case "count":
6306 jsonLightApplyFeedAnnotation(annotation, target, value, obj, baseURI);
6307 return;
6308 case "mediaReadLink":
6309 case "mediaEditLink":
6310 case "mediaContentType":
6311 case "mediaETag":
6312 jsonLightApplyMediaAnnotation(annotation, target, targetType, value, obj, baseURI);
6313 return;
6314 default:
6315 jsonLightApplyMetadataAnnotation(annotation, target, value, obj, baseURI);
6316 return;
6317 }
6318 };
6319
6320 var jsonLightApplyMetadataAnnotation = function (name, target, value, obj, baseURI) {
6321 /// <summary>
6322 /// Converts a JSON light annotation that applies to entry metadata only (i.e. odata.editLink or odata.readLink) and its value
6323 /// into their library's internal representation and saves it back to data.
6324 /// </summary>
6325 /// <param name="name" type="String">Annotation name.</param>
6326 /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
6327 /// <param name="value" type="Object">Annotation value.</param>
6328 /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
6329 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6330
6331 var metadata = obj.__metadata = obj.__metadata || {};
6332 var mappedName = jsonLightNameMap[name] || name;
6333
6334 if (name === "editLink") {
6335 metadata.uri = normalizeURI(value, baseURI);
6336 metadata[mappedName] = metadata.uri;
6337 return;
6338 }
6339
6340 if (name === "readLink" || name === "associationLinkUrl") {
6341 value = normalizeURI(value, baseURI);
6342 }
6343
6344 if (target) {
6345 var propertiesMetadata = metadata.properties = metadata.properties || {};
6346 var propertyMetadata = propertiesMetadata[target] = propertiesMetadata[target] || {};
6347
6348 if (name === "type") {
6349 propertyMetadata[mappedName] = propertyMetadata[mappedName] || value;
6350 return;
6351 }
6352 propertyMetadata[mappedName] = value;
6353 return;
6354 }
6355 metadata[mappedName] = value;
6356 };
6357
6358 var jsonLightApplyFeedAnnotation = function (name, target, value, obj, baseURI) {
6359 /// <summary>
6360 /// Converts a JSON light annotation that applies to feeds only (i.e. odata.count or odata.nextlink) and its value
6361 /// into their library's internal representation and saves it back to data.
6362 /// </summary>
6363 /// <param name="name" type="String">Annotation name.</param>
6364 /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
6365 /// <param name="value" type="Object">Annotation value.</param>
6366 /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
6367 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6368
6369 var mappedName = jsonLightNameMap[name];
6370 var feed = target ? obj[target] : obj;
6371 feed[mappedName] = (name === "nextLink") ? normalizeURI(value, baseURI) : value;
6372 };
6373
6374 var jsonLightApplyMediaAnnotation = function (name, target, targetType, value, obj, baseURI) {
6375 /// <summary>
6376 /// Converts a JSON light media annotation in and its value into their library's internal representation
6377 /// and saves it back to data or metadata.
6378 /// </summary>
6379 /// <param name="name" type="String">Annotation name.</param>
6380 /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
6381 /// <param name="targetType" type="String">Type name of the target property.</param>
6382 /// <param name="value" type="Object">Annotation value.</param>
6383 /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
6384 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6385
6386 var metadata = obj.__metadata = obj.__metadata || {};
6387 var mappedName = jsonLightNameMap[name];
6388
6389 if (name === "mediaReadLink" || name === "mediaEditLink") {
6390 value = normalizeURI(value, baseURI);
6391 }
6392
6393 if (target) {
6394 var propertiesMetadata = metadata.properties = metadata.properties || {};
6395 var propertyMetadata = propertiesMetadata[target] = propertiesMetadata[target] || {};
6396 propertyMetadata.type = propertyMetadata.type || targetType;
6397
6398 obj.__metadata = metadata;
6399 obj[target] = obj[target] || { __mediaresource: {} };
6400 obj[target].__mediaresource[mappedName] = value;
6401 return;
6402 }
6403
6404 metadata[mappedName] = value;
6405 };
6406
6407 var jsonLightApplyNavigationUrlAnnotation = function (name, target, targetType, value, data, obj, baseURI) {
6408 /// <summary>
6409 /// Converts a JSON light navigation property annotation and its value into their library's internal representation
6410 /// and saves it back to data o metadata.
6411 /// </summary>
6412 /// <param name="name" type="String">Annotation name.</param>
6413 /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
6414 /// <param name="targetType" type="String">Type name of the target property.</param>
6415 /// <param name="value" type="Object">Annotation value.</param>
6416 /// <param name="data" type="Object">JSON light object containing the annotation.</param>
6417 /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
6418 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6419
6420 var metadata = obj.__metadata = obj.__metadata || {};
6421 var propertiesMetadata = metadata.properties = metadata.properties || {};
6422 var propertyMetadata = propertiesMetadata[target] = propertiesMetadata[target] || {};
6423 var uri = normalizeURI(value, baseURI);
6424
6425 if (data.hasOwnProperty(target)) {
6426 // The navigation property is inlined in the payload,
6427 // so the navigation link url should be pushed to the object's
6428 // property metadata instead.
6429 propertyMetadata.navigationLinkUrl = uri;
6430 return;
6431 }
6432 obj[target] = { __deferred: { uri: uri} };
6433 propertyMetadata.type = propertyMetadata.type || targetType;
6434 };
6435
6436
6437 var jsonLightReadDataItemValue = function (value, typeName, dataItemMetadata, baseURI, dataItemModel, model, recognizeDates) {
6438 /// <summary>Converts the value of a data item in a JSON light object to its library representation.</summary>
6439 /// <param name="value">Data item value to convert.</param>
6440 /// <param name="typeName" type="String">Type name of the data item.</param>
6441 /// <param name="dataItemMetadata" type="Object">Object containing metadata about the data item.</param>
6442 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6443 /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
6444 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6445 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
6446 /// <returns>Data item value in its library representation.</param>
6447
6448 if (typeof value === "string") {
6449 return jsonLightReadStringPropertyValue(value, typeName, recognizeDates);
6450 }
6451
6452 if (!jsonLightIsPrimitiveType(typeName)) {
6453 if (isArray(value)) {
6454 return jsonLightReadCollectionPropertyValue(value, typeName, dataItemMetadata, baseURI, model, recognizeDates);
6455 }
6456
6457 if (isComplex(value)) {
6458 return jsonLightReadComplexPropertyValue(value, typeName, dataItemMetadata, baseURI, model, recognizeDates);
6459 }
6460 }
6461 return value;
6462 };
6463
6464 var jsonLightReadStringPropertyValue = function (value, propertyType, recognizeDates) {
6465 /// <summary>Convertes the value of a string property in a JSON light object to its library representation.</summary>
6466 /// <param name="value" type="String">String value to convert.</param>
6467 /// <param name="propertyType" type="String">Type name of the property.</param>
6468 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
6469 /// <returns>String property value in its library representation.</returns>
6470
6471 switch (propertyType) {
6472 case EDM_TIME:
6473 return parseDuration(value);
6474 case EDM_DATETIME:
6475 return parseDateTime(value, /*nullOnError*/false);
6476 case EDM_DATETIMEOFFSET:
6477 return parseDateTimeOffset(value, /*nullOnError*/false);
6478 }
6479
6480 if (recognizeDates) {
6481 return parseDateTime(value, /*nullOnError*/true) ||
6482 parseDateTimeOffset(value, /*nullOnError*/true) ||
6483 value;
6484 }
6485 return value;
6486 };
6487
6488 var jsonLightReadCollectionPropertyValue = function (value, propertyType, propertyMetadata, baseURI, model, recognizeDates) {
6489 /// <summary>Converts the value of a collection property in a JSON light object into its library representation.</summary>
6490 /// <param name="value" type="Array">Collection property value to convert.</param>
6491 /// <param name="propertyType" type="String">Property type name.</param>
6492 /// <param name="propertyMetadata" type="Object">Object containing metadata about the collection property.</param>
6493 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6494 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6495 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
6496 /// <returns type="Object">Collection property value in its library representation.</returns>
6497
6498 var collectionType = getCollectionType(propertyType);
6499 var itemsMetadata = [];
6500 var items = [];
6501
6502 var i, len;
6503 for (i = 0, len = value.length; i < len; i++) {
6504 var itemType = jsonLightDataItemType(null, value[i]) || collectionType;
6505 var itemMetadata = { type: itemType };
6506 var item = jsonLightReadDataItemValue(value[i], itemType, itemMetadata, baseURI, null, model, recognizeDates);
6507
6508 if (!jsonLightIsPrimitiveType(itemType) && !isPrimitive(value[i])) {
6509 itemsMetadata.push(itemMetadata);
6510 }
6511 items.push(item);
6512 }
6513
6514 if (itemsMetadata.length > 0) {
6515 propertyMetadata.elements = itemsMetadata;
6516 }
6517
6518 return { __metadata: { type: propertyType }, results: items };
6519 };
6520
6521 var jsonLightReadComplexPropertyValue = function (value, propertyType, propertyMetadata, baseURI, model, recognizeDates) {
6522 /// <summary>Converts the value of a comples property in a JSON light object into its library representation.</summary>
6523 /// <param name="value" type="Object">Complex property value to convert.</param>
6524 /// <param name="propertyType" type="String">Property type name.</param>
6525 /// <param name="propertyMetadata" type="Object">Object containing metadata about the complx type property.</param>
6526 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6527 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6528 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
6529 /// <returns type="Object">Complex property value in its library representation.</returns>
6530
6531 var complexValue = jsonLightReadObject(value, { type: propertyType }, baseURI, model, recognizeDates);
6532 var complexMetadata = complexValue.__metadata;
6533 var complexPropertiesMetadata = complexMetadata.properties;
6534
6535 if (complexPropertiesMetadata) {
6536 propertyMetadata.properties = complexPropertiesMetadata;
6537 delete complexMetadata.properties;
6538 }
6539 return complexValue;
6540 };
6541
6542 var jsonLightReadNavigationPropertyValue = function (value, propertyInfo, baseURI, model, recognizeDates) {
6543 /// <summary>Converts the value of a navigation property in a JSON light object into its library representation.</summary>
6544 /// <param name="value">Navigation property property value to convert.</param>
6545 /// <param name="propertyInfo" type="String">Information about the property whether it's an entry, feed or complex type.</param>
6546 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6547 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6548 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
6549 /// <returns type="Object">Collection property value in its library representation.</returns>
6550
6551 if (isArray(value)) {
6552 return jsonLightReadFeed(value, propertyInfo, baseURI, model, recognizeDates);
6553 }
6554
6555 if (isComplex(value)) {
6556 return jsonLightReadObject(value, propertyInfo, baseURI, model, recognizeDates);
6557 }
6558 return null;
6559 };
6560
6561 var jsonLightReadObject = function (data, objectInfo, baseURI, model, recognizeDates) {
6562 /// <summary>Converts a JSON light entry or complex type object into its library representation.</summary>
6563 /// <param name="data" type="Object">JSON light entry or complex type object to convert.</param>
6564 /// <param name="objectInfo" type="Object">Information about the entry or complex.</param>
6565 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6566 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6567 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
6568 /// <returns type="Object">Entry or complex type object.</param>
6569
6570 objectInfo = objectInfo || {};
6571 var actualType = data[typeAnnotation] || objectInfo.type || null;
6572 var dataModel = lookupEntityType(actualType, model);
6573 var isEntry = true;
6574 if (!dataModel) {
6575 isEntry = false;
6576 dataModel = lookupComplexType(actualType, model);
6577 }
6578
6579 var metadata = { type: actualType };
6580 var obj = { __metadata: metadata };
6581 var propertiesMetadata = {};
6582 var baseTypeModel;
6583 if (isEntry && dataModel && objectInfo.entitySet && objectInfo.contentTypeOdata == "minimalmetadata") {
6584 var serviceURI = baseURI.substring(0, baseURI.lastIndexOf("$metadata"));
6585 baseTypeModel = null; // check if the key model is in a parent type.
6586 if (!dataModel.key) {
6587 baseTypeModel = dataModel;
6588 }
6589 while (!!baseTypeModel && !baseTypeModel.key && baseTypeModel.baseType) {
6590 baseTypeModel = lookupEntityType(baseTypeModel.baseType, model);
6591 }
6592
6593 if (dataModel.key || (!!baseTypeModel && baseTypeModel.key)) {
6594 var entryKey;
6595 if (dataModel.key) {
6596 entryKey = jsonLightGetEntryKey(data, dataModel);
6597 } else {
6598 entryKey = jsonLightGetEntryKey(data, baseTypeModel);
6599 }
6600 if (entryKey) {
6601 var entryInfo = {
6602 key: entryKey,
6603 entitySet: objectInfo.entitySet,
6604 functionImport: objectInfo.functionImport,
6605 containerName: objectInfo.containerName
6606 };
6607 jsonLightComputeUrisIfMissing(data, entryInfo, actualType, serviceURI, dataModel, baseTypeModel);
6608 }
6609 }
6610 }
6611
6612 for (var name in data) {
6613 if (name.indexOf("#") === 0) {
6614 // This is an advertised function or action.
6615 jsonLightReadAdvertisedFunctionOrAction(name.substring(1), data[name], obj, baseURI, model);
6616 } else {
6617 // Is name NOT an annotation?
6618 if (name.indexOf(".") === -1) {
6619 if (!metadata.properties) {
6620 metadata.properties = propertiesMetadata;
6621 }
6622
6623 var propertyValue = data[name];
6624 var propertyModel = propertyModel = jsonLightDataItemModel(name, dataModel);
6625 baseTypeModel = dataModel;
6626 while (!!dataModel && propertyModel === null && baseTypeModel.baseType) {
6627 baseTypeModel = lookupEntityType(baseTypeModel.baseType, model);
6628 propertyModel = propertyModel = jsonLightDataItemModel(name, baseTypeModel);
6629 }
6630 var isNavigationProperty = jsonLightIsNavigationProperty(name, data, propertyModel);
6631 var propertyType = jsonLightDataItemType(name, propertyValue, data, propertyModel, model);
6632 var propertyMetadata = propertiesMetadata[name] = propertiesMetadata[name] || { type: propertyType };
6633 if (isNavigationProperty) {
6634 var propertyInfo = {};
6635 if (objectInfo.entitySet !== undefined) {
6636 var navigationPropertyEntitySetName = lookupNavigationPropertyEntitySet(propertyModel, objectInfo.entitySet.name, model);
6637 propertyInfo = getEntitySetInfo(navigationPropertyEntitySetName, model);
6638 }
6639 propertyInfo.contentTypeOdata = objectInfo.contentTypeOdata;
6640 propertyInfo.kind = objectInfo.kind;
6641 propertyInfo.type = propertyType;
6642 obj[name] = jsonLightReadNavigationPropertyValue(propertyValue, propertyInfo, baseURI, model, recognizeDates);
6643 } else {
6644 obj[name] = jsonLightReadDataItemValue(propertyValue, propertyType, propertyMetadata, baseURI, propertyModel, model, recognizeDates);
6645 }
6646 }
6647 }
6648 }
6649
6650 return jsonLightReadDataAnnotations(data, obj, baseURI, dataModel, model);
6651 };
6652
6653 var jsonLightReadAdvertisedFunctionOrAction = function (name, value, obj, baseURI, model) {
6654 /// <summary>Converts a JSON light advertised action or function object into its library representation.</summary>
6655 /// <param name="name" type="String">Advertised action or function name.</param>
6656 /// <param name="value">Advertised action or function value.</param>
6657 /// <param name="obj" type="Object">Object that will the converted value of the advertised action or function.</param>
6658 /// <param name="baseURI" type="String">Base URI for normalizing the action's or function's relative URIs.</param>
6659 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6660 /// <remarks>
6661 /// Actions and functions have the same representation in json light, so to disambiguate them the function uses
6662 /// the model object. If available, the function will look for the functionImport object that describes the
6663 /// the action or the function. If for whatever reason the functionImport can't be retrieved from the model (like
6664 /// there is no model available or there is no functionImport within the model), then the value is going to be treated
6665 /// as an advertised action and stored under obj.__metadata.actions.
6666 /// </remarks>
6667
6668 if (!name || !isArray(value) && !isComplex(value)) {
6669 return;
6670 }
6671
6672 var isFunction = false;
6673 var nsEnd = name.lastIndexOf(".");
6674 var simpleName = name.substring(nsEnd + 1);
6675 var containerName = (nsEnd > -1) ? name.substring(0, nsEnd) : "";
6676
6677 var container = (simpleName === name || containerName.indexOf(".") === -1) ?
6678 lookupDefaultEntityContainer(model) :
6679 lookupEntityContainer(containerName, model);
6680
6681 if (container) {
6682 var functionImport = lookupFunctionImport(container.functionImport, simpleName);
6683 if (functionImport && !!functionImport.isSideEffecting) {
6684 isFunction = !parseBool(functionImport.isSideEffecting);
6685 }
6686 }
6687
6688 var metadata = obj.__metadata;
6689 var targetName = isFunction ? "functions" : "actions";
6690 var metadataURI = normalizeURI(name, baseURI);
6691 var items = (isArray(value)) ? value : [value];
6692
6693 var i, len;
6694 for (i = 0, len = items.length; i < len; i++) {
6695 var item = items[i];
6696 if (item) {
6697 var targetCollection = metadata[targetName] = metadata[targetName] || [];
6698 var actionOrFunction = { metadata: metadataURI, title: item.title, target: normalizeURI(item.target, baseURI) };
6699 targetCollection.push(actionOrFunction);
6700 }
6701 }
6702 };
6703
6704 var jsonLightReadFeed = function (data, feedInfo, baseURI, model, recognizeDates) {
6705 /// <summary>Converts a JSON light feed or top level collection property object into its library representation.</summary>
6706 /// <param name="data" type="Object">JSON light feed object to convert.</param>
6707 /// <param name="typeName" type="String">Type name of the feed or collection items.</param>
6708 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6709 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6710 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
6711 /// <returns type="Object">Feed or top level collection object.</param>
6712
6713 var items = isArray(data) ? data : data.value;
6714 var entries = [];
6715 var i, len, entry;
6716 for (i = 0, len = items.length; i < len; i++) {
6717 entry = jsonLightReadObject(items[i], feedInfo, baseURI, model, recognizeDates);
6718 entries.push(entry);
6719 }
6720
6721 var feed = { results: entries };
6722
6723 if (isComplex(data)) {
6724 for (var name in data) {
6725 if (name.indexOf("#") === 0) {
6726 // This is an advertised function or action.
6727 feed.__metadata = feed.__metadata || {};
6728 jsonLightReadAdvertisedFunctionOrAction(name.substring(1), data[name], feed, baseURI, model);
6729 }
6730 }
6731 feed = jsonLightReadDataAnnotations(data, feed, baseURI);
6732 }
6733 return feed;
6734 };
6735
6736 var jsonLightGetEntryKey = function (data, entityModel) {
6737 /// <summary>Gets the key of an entry.</summary>
6738 /// <param name="data" type="Object">JSON light entry.</param>
6739 /// <param name="entityModel" type="String">Object describing the entry Model</param>
6740 /// <returns type="string">Entry instance key.</returns>
6741
6742 var entityInstanceKey;
6743 var entityKeys = entityModel.key.propertyRef;
6744 var type;
6745 entityInstanceKey = "(";
6746 if (entityKeys.length == 1) {
6747 type = lookupProperty(entityModel.property, entityKeys[0].name).type;
6748 entityInstanceKey += formatLiteral(data[entityKeys[0].name], type);
6749 } else {
6750 var first = true;
6751 for (var i = 0; i < entityKeys.length; i++) {
6752 if (!first) {
6753 entityInstanceKey += ",";
6754 } else {
6755 first = false;
6756 }
6757 type = lookupProperty(entityModel.property, entityKeys[i].name).type;
6758 entityInstanceKey += entityKeys[i].name + "=" + formatLiteral(data[entityKeys[i].name], type);
6759 }
6760 }
6761 entityInstanceKey += ")";
6762 return entityInstanceKey;
6763 };
6764
6765
6766 var jsonLightComputeUrisIfMissing = function (data, entryInfo, actualType, serviceURI, entityModel, baseTypeModel) {
6767 /// <summary>Compute the URI according to OData conventions if it doesn't exist</summary>
6768 /// <param name="data" type="Object">JSON light entry.</param>
6769 /// <param name="entryInfo" type="Object">Information about the entry includes type, key, entitySet and entityContainerName.</param>
6770 /// <param name="actualType" type="String">Type of the entry</param>
6771 /// <param name="serviceURI" type="String">Base URI the service.</param>
6772 /// <param name="entityModel" type="Object">Object describing an OData conceptual schema of the entry.</param>
6773 /// <param name="baseTypeModel" type="Object" optional="true">Object escribing an OData conceptual schema of the baseType if it exists.</param>
6774
6775 var lastIdSegment = data[jsonLightAnnotations.id] || data[jsonLightAnnotations.read] || data[jsonLightAnnotations.edit] || entryInfo.entitySet.name + entryInfo.key;
6776 data[jsonLightAnnotations.id] = serviceURI + lastIdSegment;
6777 if (!data[jsonLightAnnotations.edit]) {
6778 data[jsonLightAnnotations.edit] = entryInfo.entitySet.name + entryInfo.key;
6779 if (entryInfo.entitySet.entityType != actualType) {
6780 data[jsonLightAnnotations.edit] += "/" + actualType;
6781 }
6782 }
6783 data[jsonLightAnnotations.read] = data[jsonLightAnnotations.read] || data[jsonLightAnnotations.edit];
6784 if (!data[jsonLightAnnotations.etag]) {
6785 var etag = jsonLightComputeETag(data, entityModel, baseTypeModel);
6786 if (!!etag) {
6787 data[jsonLightAnnotations.etag] = etag;
6788 }
6789 }
6790
6791 jsonLightComputeStreamLinks(data, entityModel, baseTypeModel);
6792 jsonLightComputeNavigationAndAssociationProperties(data, entityModel, baseTypeModel);
6793 jsonLightComputeFunctionImports(data, entryInfo);
6794 };
6795
6796 var jsonLightComputeETag = function (data, entityModel, baseTypeModel) {
6797 /// <summary>Computes the etag of an entry</summary>
6798 /// <param name="data" type="Object">JSON light entry.</param>
6799 /// <param name="entryInfo" type="Object">Object describing the entry model.</param>
6800 /// <param name="baseTypeModel" type="Object" optional="true">Object describing an OData conceptual schema of the baseType if it exists.</param>
6801 /// <returns type="string">Etag value</returns>
6802 var etag = "";
6803 var propertyModel;
6804 for (var i = 0; entityModel.property && i < entityModel.property.length; i++) {
6805 propertyModel = entityModel.property[i];
6806 etag = jsonLightAppendValueToEtag(data, etag, propertyModel);
6807
6808 }
6809 if (baseTypeModel) {
6810 for (i = 0; baseTypeModel.property && i < baseTypeModel.property.length; i++) {
6811 propertyModel = baseTypeModel.property[i];
6812 etag = jsonLightAppendValueToEtag(data, etag, propertyModel);
6813 }
6814 }
6815 if (etag.length > 0) {
6816 return etag + "\"";
6817 }
6818 return null;
6819 };
6820
6821 var jsonLightAppendValueToEtag = function (data, etag, propertyModel) {
6822 /// <summary>Adds a propery value to the etag after formatting.</summary>
6823 /// <param name="data" type="Object">JSON light entry.</param>
6824 /// <param name="etag" type="Object">value of the etag.</param>
6825 /// <param name="propertyModel" type="Object">Object describing an OData conceptual schema of the property.</param>
6826 /// <returns type="string">Etag value</returns>
6827
6828 if (propertyModel.concurrencyMode == "Fixed") {
6829 if (etag.length > 0) {
6830 etag += ",";
6831 } else {
6832 etag += "W/\"";
6833 }
6834 if (data[propertyModel.name] !== null) {
6835 etag += formatLiteral(data[propertyModel.name], propertyModel.type);
6836 } else {
6837 etag += "null";
6838 }
6839 }
6840 return etag;
6841 };
6842
6843 var jsonLightComputeNavigationAndAssociationProperties = function (data, entityModel, baseTypeModel) {
6844 /// <summary>Adds navigation links to the entry metadata</summary>
6845 /// <param name="data" type="Object">JSON light entry.</param>
6846 /// <param name="entityModel" type="Object">Object describing the entry model.</param>
6847 /// <param name="baseTypeModel" type="Object" optional="true">Object describing an OData conceptual schema of the baseType if it exists.</param>
6848
6849 var navigationLinkAnnotation = "@odata.navigationLinkUrl";
6850 var associationLinkAnnotation = "@odata.associationLinkUrl";
6851 var navigationPropertyName, navigationPropertyAnnotation, associationPropertyAnnotation;
6852 for (var i = 0; entityModel.navigationProperty && i < entityModel.navigationProperty.length; i++) {
6853 navigationPropertyName = entityModel.navigationProperty[i].name;
6854 navigationPropertyAnnotation = navigationPropertyName + navigationLinkAnnotation;
6855 if (data[navigationPropertyAnnotation] === undefined) {
6856 data[navigationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/" + encodeURIComponent(navigationPropertyName);
6857 }
6858 associationPropertyAnnotation = navigationPropertyName + associationLinkAnnotation;
6859 if (data[associationPropertyAnnotation] === undefined) {
6860 data[associationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/$links/" + encodeURIComponent(navigationPropertyName);
6861 }
6862 }
6863
6864 if (baseTypeModel && baseTypeModel.navigationProperty) {
6865 for (i = 0; i < baseTypeModel.navigationProperty.length; i++) {
6866 navigationPropertyName = baseTypeModel.navigationProperty[i].name;
6867 navigationPropertyAnnotation = navigationPropertyName + navigationLinkAnnotation;
6868 if (data[navigationPropertyAnnotation] === undefined) {
6869 data[navigationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/" + encodeURIComponent(navigationPropertyName);
6870 }
6871 associationPropertyAnnotation = navigationPropertyName + associationLinkAnnotation;
6872 if (data[associationPropertyAnnotation] === undefined) {
6873 data[associationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/$links/" + encodeURIComponent(navigationPropertyName);
6874 }
6875 }
6876 }
6877 };
6878
6879 var formatLiteral = function (value, type) {
6880 /// <summary>Formats a value according to Uri literal format</summary>
6881 /// <param name="value">Value to be formatted.</param>
6882 /// <param name="type">Edm type of the value</param>
6883 /// <returns type="string">Value after formatting</returns>
6884
6885 value = "" + formatRowLiteral(value, type);
6886 value = encodeURIComponent(value.replace("'", "''"));
6887 switch ((type)) {
6888 case "Edm.Binary":
6889 return "X'" + value + "'";
6890 case "Edm.DateTime":
6891 return "datetime" + "'" + value + "'";
6892 case "Edm.DateTimeOffset":
6893 return "datetimeoffset" + "'" + value + "'";
6894 case "Edm.Decimal":
6895 return value + "M";
6896 case "Edm.Guid":
6897 return "guid" + "'" + value + "'";
6898 case "Edm.Int64":
6899 return value + "L";
6900 case "Edm.Float":
6901 return value + "f";
6902 case "Edm.Double":
6903 return value + "D";
6904 case "Edm.Geography":
6905 return "geography" + "'" + value + "'";
6906 case "Edm.Geometry":
6907 return "geometry" + "'" + value + "'";
6908 case "Edm.Time":
6909 return "time" + "'" + value + "'";
6910 case "Edm.String":
6911 return "'" + value + "'";
6912 default:
6913 return value;
6914 }
6915 };
6916
6917
6918 var formatRowLiteral = function (value, type) {
6919 switch (type) {
6920 case "Edm.Binary":
6921 return convertByteArrayToHexString(value);
6922 default:
6923 return value;
6924 }
6925 };
6926
6927 var jsonLightComputeFunctionImports = function (data, entryInfo) {
6928 /// <summary>Adds functions and actions links to the entry metadata</summary>
6929 /// <param name="entry" type="Object">JSON light entry.</param>
6930 /// <param name="entityInfo" type="Object">Object describing the entry</param>
6931
6932 var functionImport = entryInfo.functionImport || [];
6933 for (var i = 0; i < functionImport.length; i++) {
6934 if (functionImport[i].isBindable && functionImport[i].parameter[0] && functionImport[i].parameter[0].type == entryInfo.entitySet.entityType) {
6935 var functionImportAnnotation = "#" + entryInfo.containerName + "." + functionImport[i].name;
6936 if (data[functionImportAnnotation] == undefined) {
6937 data[functionImportAnnotation] = {
6938 title: functionImport[i].name,
6939 target: data[jsonLightAnnotations.edit] + "/" + functionImport[i].name
6940 };
6941 }
6942 }
6943 }
6944 };
6945
6946 var jsonLightComputeStreamLinks = function (data, entityModel, baseTypeModel) {
6947 /// <summary>Adds stream links to the entry metadata</summary>
6948 /// <param name="data" type="Object">JSON light entry.</param>
6949 /// <param name="entityModel" type="Object">Object describing the entry model.</param>
6950 /// <param name="baseTypeModel" type="Object" optional="true">Object describing an OData conceptual schema of the baseType if it exists.</param>
6951
6952 if (entityModel.hasStream || (baseTypeModel && baseTypeModel.hasStream)) {
6953 data[jsonLightAnnotations.mediaEdit] = data[jsonLightAnnotations.mediaEdit] || data[jsonLightAnnotations.mediaEdit] + "/$value";
6954 data[jsonLightAnnotations.mediaRead] = data[jsonLightAnnotations.mediaRead] || data[jsonLightAnnotations.mediaEdit];
6955 }
6956 };
6957
6958 var jsonLightReadTopPrimitiveProperty = function (data, typeName, baseURI, recognizeDates) {
6959 /// <summary>Converts a JSON light top level primitive property object into its library representation.</summary>
6960 /// <param name="data" type="Object">JSON light feed object to convert.</param>
6961 /// <param name="typeName" type="String">Type name of the primitive property.</param>
6962 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6963 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
6964 /// <returns type="Object">Top level primitive property object.</param>
6965
6966 var metadata = { type: typeName };
6967 var value = jsonLightReadDataItemValue(data.value, typeName, metadata, baseURI, null, null, recognizeDates);
6968 return jsonLightReadDataAnnotations(data, { __metadata: metadata, value: value }, baseURI);
6969 };
6970
6971 var jsonLightReadTopCollectionProperty = function (data, typeName, baseURI, model, recognizeDates) {
6972 /// <summary>Converts a JSON light top level collection property object into its library representation.</summary>
6973 /// <param name="data" type="Object">JSON light feed object to convert.</param>
6974 /// <param name="typeName" type="String">Type name of the collection property.</param>
6975 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6976 /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
6977 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
6978 /// <returns type="Object">Top level collection property object.</param>
6979
6980 var propertyMetadata = {};
6981 var value = jsonLightReadCollectionPropertyValue(data.value, typeName, propertyMetadata, baseURI, model, recognizeDates);
6982 extend(value.__metadata, propertyMetadata);
6983 return jsonLightReadDataAnnotations(data, value, baseURI);
6984 };
6985
6986 var jsonLightReadLinksDocument = function (data, baseURI) {
6987 /// <summary>Converts a JSON light links collection object to its library representation.</summary>
6988 /// <param name="data" type="Object">JSON light link object to convert.</param>
6989 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
6990 /// <returns type="Object">Links collection object.</param>
6991
6992 var items = data.value;
6993 if (!isArray(items)) {
6994 return jsonLightReadLink(data, baseURI);
6995 }
6996
6997 var results = [];
6998 var i, len;
6999 for (i = 0, len = items.length; i < len; i++) {
7000 results.push(jsonLightReadLink(items[i], baseURI));
7001 }
7002
7003 var links = { results: results };
7004 return jsonLightReadDataAnnotations(data, links, baseURI);
7005 };
7006
7007 var jsonLightReadLink = function (data, baseURI) {
7008 /// <summary>Converts a JSON light link object to its library representation.</summary>
7009 /// <param name="data" type="Object">JSON light link object to convert.</param>
7010 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
7011 /// <returns type="Object">Link object.</param>
7012
7013 var link = { uri: normalizeURI(data.url, baseURI) };
7014
7015 link = jsonLightReadDataAnnotations(data, link, baseURI);
7016 var metadata = link.__metadata || {};
7017 var metadataProperties = metadata.properties || {};
7018
7019 jsonLightRemoveTypePropertyMetadata(metadataProperties.url);
7020 renameProperty(metadataProperties, "url", "uri");
7021
7022 return link;
7023 };
7024
7025 var jsonLightRemoveTypePropertyMetadata = function (propertyMetadata) {
7026 /// <summary>Removes the type property from a property metadata object.</summary>
7027 /// <param name="propertyMetadata" type="Object">Property metadata object.</param>
7028
7029 if (propertyMetadata) {
7030 delete propertyMetadata.type;
7031 }
7032 };
7033
7034 var jsonLightReadSvcDocument = function (data, baseURI) {
7035 /// <summary>Converts a JSON light service document object to its library representation.</summary>
7036 /// <param name="data" type="Object">JSON light service document object to convert.</param>
7037 /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
7038 /// <returns type="Object">Link object.</param>
7039
7040 var items = data.value;
7041 var collections = [];
7042 var workspace = jsonLightReadDataAnnotations(data, { collections: collections }, baseURI);
7043
7044 var metadata = workspace.__metadata || {};
7045 var metadataProperties = metadata.properties || {};
7046
7047 jsonLightRemoveTypePropertyMetadata(metadataProperties.value);
7048 renameProperty(metadataProperties, "value", "collections");
7049
7050 var i, len;
7051 for (i = 0, len = items.length; i < len; i++) {
7052 var item = items[i];
7053 var collection = { title: item.name, href: normalizeURI(item.url, baseURI) };
7054
7055 collection = jsonLightReadDataAnnotations(item, collection, baseURI);
7056 metadata = collection.__metadata || {};
7057 metadataProperties = metadata.properties || {};
7058
7059 jsonLightRemoveTypePropertyMetadata(metadataProperties.name);
7060 jsonLightRemoveTypePropertyMetadata(metadataProperties.url);
7061
7062 renameProperty(metadataProperties, "name", "title");
7063 renameProperty(metadataProperties, "url", "href");
7064
7065 collections.push(collection);
7066 }
7067
7068 return { workspaces: [workspace] };
7069 };
7070
7071 var jsonLightMakePayloadInfo = function (kind, type) {
7072 /// <summary>Creates an object containing information for the json light payload.</summary>
7073 /// <param name="kind" type="String">JSON light payload kind, one of the PAYLOADTYPE_XXX constant values.</param>
7074 /// <param name="typeName" type="String">Type name of the JSON light payload.</param>
7075 /// <returns type="Object">Object with kind and type fields.</returns>
7076
7077 /// <field name="kind" type="String">Kind of the JSON light payload. One of the PAYLOADTYPE_XXX constant values.</field>
7078 /// <field name="type" type="String">Data type of the JSON light payload.</field>
7079
7080 return { kind: kind, type: type || null };
7081 };
7082
7083 var jsonLightPayloadInfo = function (data, model, inferFeedAsComplexType) {
7084 /// <summary>Infers the information describing the JSON light payload from its metadata annotation, structure, and data model.</summary>
7085 /// <param name="data" type="Object">Json light response payload object.</param>
7086 /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
7087 /// <param name="inferFeedAsComplexType" type="Boolean">True if a JSON light payload that looks like a feed should be treated as a complex type property instead.</param>
7088 /// <remarks>
7089 /// If the arguments passed to the function don't convey enough information about the payload to determine without doubt that the payload is a feed then it
7090 /// will try to use the payload object structure instead. If the payload looks like a feed (has value property that is an array or non-primitive values) then
7091 /// the function will report its kind as PAYLOADTYPE_FEED unless the inferFeedAsComplexType flag is set to true. This flag comes from the user request
7092 /// and allows the user to control how the library behaves with an ambigous JSON light payload.
7093 /// </remarks>
7094 /// <returns type="Object">
7095 /// Object with kind and type fields. Null if there is no metadata annotation or the payload info cannot be obtained..
7096 /// </returns>
7097
7098 var metadataUri = data[metadataAnnotation];
7099 if (!metadataUri || typeof metadataUri !== "string") {
7100 return null;
7101 }
7102
7103 var fragmentStart = metadataUri.lastIndexOf("#");
7104 if (fragmentStart === -1) {
7105 return jsonLightMakePayloadInfo(PAYLOADTYPE_SVCDOC);
7106 }
7107
7108 var elementStart = metadataUri.indexOf("@Element", fragmentStart);
7109 var fragmentEnd = elementStart - 1;
7110
7111 if (fragmentEnd < 0) {
7112 fragmentEnd = metadataUri.indexOf("?", fragmentStart);
7113 if (fragmentEnd === -1) {
7114 fragmentEnd = metadataUri.length;
7115 }
7116 }
7117
7118 var fragment = metadataUri.substring(fragmentStart + 1, fragmentEnd);
7119 if (fragment.indexOf("/$links/") > 0) {
7120 return jsonLightMakePayloadInfo(PAYLOADTYPE_LINKS);
7121 }
7122
7123 var fragmentParts = fragment.split("/");
7124 if (fragmentParts.length >= 0) {
7125 var qualifiedName = fragmentParts[0];
7126 var typeCast = fragmentParts[1];
7127
7128 if (jsonLightIsPrimitiveType(qualifiedName)) {
7129 return jsonLightMakePayloadInfo(PAYLOADTYPE_PRIMITIVE, qualifiedName);
7130 }
7131
7132 if (isCollectionType(qualifiedName)) {
7133 return jsonLightMakePayloadInfo(PAYLOADTYPE_COLLECTION, qualifiedName);
7134 }
7135
7136 var entityType = typeCast;
7137 var entitySet, functionImport, containerName;
7138 if (!typeCast) {
7139 var nsEnd = qualifiedName.lastIndexOf(".");
7140 var simpleName = qualifiedName.substring(nsEnd + 1);
7141 var container = (simpleName === qualifiedName) ?
7142 lookupDefaultEntityContainer(model) :
7143 lookupEntityContainer(qualifiedName.substring(0, nsEnd), model);
7144
7145 if (container) {
7146 entitySet = lookupEntitySet(container.entitySet, simpleName);
7147 functionImport = container.functionImport;
7148 containerName = container.name;
7149 entityType = !!entitySet ? entitySet.entityType : null;
7150 }
7151 }
7152
7153 var info;
7154 if (elementStart > 0) {
7155 info = jsonLightMakePayloadInfo(PAYLOADTYPE_OBJECT, entityType);
7156 info.entitySet = entitySet;
7157 info.functionImport = functionImport;
7158 info.containerName = containerName;
7159 return info;
7160 }
7161
7162 if (entityType) {
7163 info = jsonLightMakePayloadInfo(PAYLOADTYPE_FEED, entityType);
7164 info.entitySet = entitySet;
7165 info.functionImport = functionImport;
7166 info.containerName = containerName;
7167 return info;
7168 }
7169
7170 if (isArray(data.value) && !lookupComplexType(qualifiedName, model)) {
7171 var item = data.value[0];
7172 if (!isPrimitive(item)) {
7173 if (jsonLightIsEntry(item) || !inferFeedAsComplexType) {
7174 return jsonLightMakePayloadInfo(PAYLOADTYPE_FEED, null);
7175 }
7176 }
7177 }
7178
7179 return jsonLightMakePayloadInfo(PAYLOADTYPE_OBJECT, qualifiedName);
7180 }
7181
7182 return null;
7183 };
7184
7185 var jsonLightReadPayload = function (data, model, recognizeDates, inferFeedAsComplexType, contentTypeOdata) {
7186 /// <summary>Converts a JSON light response payload object into its library's internal representation.</summary>
7187 /// <param name="data" type="Object">Json light response payload object.</param>
7188 /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
7189 /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
7190 /// <param name="inferFeedAsComplexType" type="Boolean">True if a JSON light payload that looks like a feed should be reported as a complex type property instead.</param>
7191 /// <param name="contentTypeOdata" type="string">Includes the type of json ( minimalmetadata, fullmetadata .. etc )</param>
7192 /// <returns type="Object">Object in the library's representation.</returns>
7193
7194 if (!isComplex(data)) {
7195 return data;
7196 }
7197
7198 contentTypeOdata = contentTypeOdata || "minimalmetadata";
7199 var baseURI = data[metadataAnnotation];
7200 var payloadInfo = jsonLightPayloadInfo(data, model, inferFeedAsComplexType);
7201 if (assigned(payloadInfo)) {
7202 payloadInfo.contentTypeOdata = contentTypeOdata;
7203 }
7204 var typeName = null;
7205 if (payloadInfo) {
7206 delete data[metadataAnnotation];
7207
7208 typeName = payloadInfo.type;
7209 switch (payloadInfo.kind) {
7210 case PAYLOADTYPE_FEED:
7211 return jsonLightReadFeed(data, payloadInfo, baseURI, model, recognizeDates);
7212 case PAYLOADTYPE_COLLECTION:
7213 return jsonLightReadTopCollectionProperty(data, typeName, baseURI, model, recognizeDates);
7214 case PAYLOADTYPE_PRIMITIVE:
7215 return jsonLightReadTopPrimitiveProperty(data, typeName, baseURI, recognizeDates);
7216 case PAYLOADTYPE_SVCDOC:
7217 return jsonLightReadSvcDocument(data, baseURI);
7218 case PAYLOADTYPE_LINKS:
7219 return jsonLightReadLinksDocument(data, baseURI);
7220 }
7221 }
7222 return jsonLightReadObject(data, payloadInfo, baseURI, model, recognizeDates);
7223 };
7224
7225 var jsonLightSerializableMetadata = ["type", "etag", "media_src", "edit_media", "content_type", "media_etag"];
7226
7227 var formatJsonLight = function (obj, context) {
7228 /// <summary>Converts an object in the library's internal representation to its json light representation.</summary>
7229 /// <param name="obj" type="Object">Object the library's internal representation.</param>
7230 /// <param name="context" type="Object">Object with the serialization context.</param>
7231 /// <returns type="Object">Object in its json light representation.</returns>
7232
7233 // Regular expression used to test that the uri is for a $links document.
7234 var linksUriRE = /\/\$links\//;
7235 var data = {};
7236 var metadata = obj.__metadata;
7237
7238 var islinks = context && linksUriRE.test(context.request.requestUri);
7239 formatJsonLightData(obj, (metadata && metadata.properties), data, islinks);
7240 return data;
7241 };
7242
7243 var formatJsonLightMetadata = function (metadata, data) {
7244 /// <summary>Formats an object's metadata into the appropriate json light annotations and saves them to data.</summary>
7245 /// <param name="obj" type="Object">Object whose metadata is going to be formatted as annotations.</param>
7246 /// <param name="data" type="Object">Object on which the annotations are going to be stored.</param>
7247
7248 if (metadata) {
7249 var i, len;
7250 for (i = 0, len = jsonLightSerializableMetadata.length; i < len; i++) {
7251 // There is only a subset of metadata values that are interesting during update requests.
7252 var name = jsonLightSerializableMetadata[i];
7253 var qName = odataAnnotationPrefix + (jsonLightNameMap[name] || name);
7254 formatJsonLightAnnotation(qName, null, metadata[name], data);
7255 }
7256 }
7257 };
7258
7259 var formatJsonLightData = function (obj, pMetadata, data, isLinks) {
7260 /// <summary>Formats an object's data into the appropriate json light values and saves them to data.</summary>
7261 /// <param name="obj" type="Object">Object whose data is going to be formatted.</param>
7262 /// <param name="pMetadata" type="Object">Object that contains metadata for the properties that are being formatted.</param>
7263 /// <param name="data" type="Object">Object on which the formatted values are going to be stored.</param>
7264 /// <param name="isLinks" type="Boolean">True if a links document is being formatted. False otherwise.</param>
7265
7266 for (var key in obj) {
7267 var value = obj[key];
7268 if (key === "__metadata") {
7269 // key is the object metadata.
7270 formatJsonLightMetadata(value, data);
7271 } else if (key.indexOf(".") === -1) {
7272 // key is an regular property or array element.
7273 if (isLinks && key === "uri") {
7274 formatJsonLightEntityLink(value, data);
7275 } else {
7276 formatJsonLightProperty(key, value, pMetadata, data, isLinks);
7277 }
7278 } else {
7279 data[key] = value;
7280 }
7281 }
7282 };
7283
7284 var formatJsonLightProperty = function (name, value, pMetadata, data) {
7285 /// <summary>Formats an object's value identified by name to its json light representation and saves it to data.</summary>
7286 /// <param name="name" type="String">Property name.</param>
7287 /// <param name="value">Property value.</param>
7288 /// <param name="pMetadata" type="Object">Object that contains metadata for the property that is being formatted.</param>
7289 /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
7290
7291 // Get property type from property metadata
7292 var propertyMetadata = pMetadata && pMetadata[name] || { properties: undefined, type: undefined };
7293 var typeName = dataItemTypeName(value, propertyMetadata);
7294
7295 if (isPrimitive(value) || !value) {
7296 // It is a primitive value then.
7297 formatJsonLightAnnotation(typeAnnotation, name, typeName, data);
7298 data[name] = value;
7299 return;
7300 }
7301
7302 if (isFeed(value, typeName) || isEntry(value)) {
7303 formatJsonLightInlineProperty(name, value, data);
7304 return;
7305 }
7306
7307 if (!typeName && isDeferred(value)) {
7308 // It is really a deferred property.
7309 formatJsonLightDeferredProperty(name, value, data);
7310 return;
7311 }
7312
7313 if (isCollection(value, typeName)) {
7314 // The thing is a collection, format it as one.
7315 if (getCollectionType(typeName)) {
7316 formatJsonLightAnnotation(typeAnnotation, name, typeName, data);
7317 }
7318 formatJsonLightCollectionProperty(name, value, data);
7319 return;
7320 }
7321
7322
7323 // Format the complex property value in a new object in data[name].
7324 data[name] = {};
7325 formatJsonLightAnnotation(typeAnnotation, null, typeName, data[name]);
7326 formatJsonLightData(value, propertyMetadata.properties, data[name]);
7327 };
7328
7329 var formatJsonLightEntityLink = function (value, data) {
7330 /// <summary>Formats an entity link in a $links document and saves it into data.</summary>
7331 /// <param name="value" type="String">Entity link value.</summary>
7332 /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
7333 data.url = value;
7334 };
7335
7336 var formatJsonLightDeferredProperty = function (name, value, data) {
7337 /// <summary>Formats the object value's identified by name as an odata.navigalinkurl annotation and saves it to data.</summary>
7338 /// <param name="name" type="String">Name of the deferred property to be formatted.</param>
7339 /// <param name="value" type="Object">Deferred property value to be formatted.</param>
7340 /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
7341
7342 formatJsonLightAnnotation(navUrlAnnotation, name, value.__deferred.uri, data);
7343 };
7344
7345 var formatJsonLightCollectionProperty = function (name, value, data) {
7346 /// <summary>Formats a collection property in obj identified by name as a json light collection property and saves it to data.</summary>
7347 /// <param name="name" type="String">Name of the collection property to be formatted.</param>
7348 /// <param name="value" type="Object">Collection property value to be formatted.</param>
7349 /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
7350
7351 data[name] = [];
7352 var items = isArray(value) ? value : value.results;
7353 formatJsonLightData(items, null, data[name]);
7354 };
7355
7356 var formatJsonLightInlineProperty = function (name, value, data) {
7357 /// <summary>Formats an inline feed or entry property in obj identified by name as a json light value and saves it to data.</summary>
7358 /// <param name="name" type="String">Name of the inline feed or entry property to be formatted.</param>
7359 /// <param name="value" type="Object or Array">Value of the inline feed or entry property.</param>
7360 /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
7361
7362 if (isFeed(value)) {
7363 data[name] = [];
7364 // Format each of the inline feed entries
7365 var entries = isArray(value) ? value : value.results;
7366 var i, len;
7367 for (i = 0, len = entries.length; i < len; i++) {
7368 formatJsonLightInlineEntry(name, entries[i], true, data);
7369 }
7370 return;
7371 }
7372 formatJsonLightInlineEntry(name, value, false, data);
7373 };
7374
7375 var formatJsonLightInlineEntry = function (name, value, inFeed, data) {
7376 /// <summary>Formats an inline entry value in the property identified by name as a json light value and saves it to data.</summary>
7377 /// <param name="name" type="String">Name of the inline feed or entry property that owns the entry formatted.</param>
7378 /// <param name="value" type="Object">Inline entry value to be formatted.</param>
7379 /// <param name="inFeed" type="Boolean">True if the entry is in an inline feed; false otherwise.
7380 /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
7381
7382 // This might be a bind instead of a deep insert.
7383 var uri = value.__metadata && value.__metadata.uri;
7384 if (uri) {
7385 formatJsonLightBinding(name, uri, inFeed, data);
7386 return;
7387 }
7388
7389 var entry = formatJsonLight(value);
7390 if (inFeed) {
7391 data[name].push(entry);
7392 return;
7393 }
7394 data[name] = entry;
7395 };
7396
7397 var formatJsonLightBinding = function (name, uri, inFeed, data) {
7398 /// <summary>Formats an entry binding in the inline property in obj identified by name as an odata.bind annotation and saves it to data.</summary>
7399 /// <param name="name" type="String">Name of the inline property that has the binding to be formated.</param>
7400 /// <param name="uri" type="String">Uri to the bound entry.</param>
7401 /// <param name="inFeed" type="Boolean">True if the binding is in an inline feed; false otherwise.
7402 /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
7403
7404 var bindingName = name + bindAnnotation;
7405 if (inFeed) {
7406 // The binding is inside an inline feed, so merge it with whatever other bindings already exist in data.
7407 data[bindingName] = data[bindingName] || [];
7408 data[bindingName].push(uri);
7409 return;
7410 }
7411 // The binding is on an inline entry; it can be safely overwritten.
7412 data[bindingName] = uri;
7413 };
7414
7415 var formatJsonLightAnnotation = function (qName, target, value, data) {
7416 /// <summary>Formats a value as a json light annotation and stores it in data</summary>
7417 /// <param name="qName" type="String">Qualified name of the annotation.</param>
7418 /// <param name="target" type="String">Name of the property that the metadata value targets.</param>
7419 /// <param name="value">Annotation value.</param>
7420 /// <param name="data" type="Object">Object on which the annotation is going to be stored.</param>
7421
7422 if (value !== undefined) {
7423 if(target) {
7424 data[target + "@" + qName] = value;
7425 }
7426 else {
7427 data[qName] = value;
7428 }
7429 }
7430 };
7431
7432
7433
7434 var jsonMediaType = "application/json";
7435 var jsonContentType = contentType(jsonMediaType);
7436
7437 var jsonReadAdvertisedActionsOrFunctions = function (value) {
7438 /// <summary>Reads and object containing action or function metadata and maps them into a single array of objects.</summary>
7439 /// <param name="value" type="Object">Object containing action or function metadata.</param>
7440 /// <returns type="Array">Array of objects containing metadata for the actions or functions specified in value.</returns>
7441
7442 var result = [];
7443 for (var name in value) {
7444 var i, len;
7445 for (i = 0, len = value[name].length; i < len; i++) {
7446 result.push(extend({ metadata: name }, value[name][i]));
7447 }
7448 }
7449 return result;
7450 };
7451
7452 var jsonApplyMetadata = function (value, metadata, dateParser, recognizeDates) {
7453 /// <summary>Applies metadata coming from both the payload and the metadata object to the value.</summary>
7454 /// <param name="value" type="Object">Data on which the metada is going to be applied.</param>
7455 /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
7456 /// <param name="dateParser" type="function">Function used for parsing datetime values.</param>
7457 /// <param name="recognizeDates" type="Boolean">
7458 /// True if strings formatted as datetime values should be treated as datetime values. False otherwise.
7459 /// </param>
7460 /// <returns type="Object">Transformed data.</returns>
7461
7462 if (value && typeof value === "object") {
7463 var dataTypeName;
7464 var valueMetadata = value.__metadata;
7465
7466 if (valueMetadata) {
7467 if (valueMetadata.actions) {
7468 valueMetadata.actions = jsonReadAdvertisedActionsOrFunctions(valueMetadata.actions);
7469 }
7470 if (valueMetadata.functions) {
7471 valueMetadata.functions = jsonReadAdvertisedActionsOrFunctions(valueMetadata.functions);
7472 }
7473 dataTypeName = valueMetadata && valueMetadata.type;
7474 }
7475
7476 var dataType = lookupEntityType(dataTypeName, metadata) || lookupComplexType(dataTypeName, metadata);
7477 var propertyValue;
7478 if (dataType) {
7479 var properties = dataType.property;
7480 if (properties) {
7481 var i, len;
7482 for (i = 0, len = properties.length; i < len; i++) {
7483 var property = properties[i];
7484 var propertyName = property.name;
7485 propertyValue = value[propertyName];
7486
7487 if (property.type === "Edm.DateTime" || property.type === "Edm.DateTimeOffset") {
7488 if (propertyValue) {
7489 propertyValue = dateParser(propertyValue);
7490 if (!propertyValue) {
7491 throw { message: "Invalid date/time value" };
7492 }
7493 value[propertyName] = propertyValue;
7494 }
7495 } else if (property.type === "Edm.Time") {
7496 value[propertyName] = parseDuration(propertyValue);
7497 }
7498 }
7499 }
7500 } else if (recognizeDates) {
7501 for (var name in value) {
7502 propertyValue = value[name];
7503 if (typeof propertyValue === "string") {
7504 value[name] = dateParser(propertyValue) || propertyValue;
7505 }
7506 }
7507 }
7508 }
7509 return value;
7510 };
7511
7512 var isJsonLight = function (contentType) {
7513 /// <summary>Tests where the content type indicates a json light payload.</summary>
7514 /// <param name="contentType">Object with media type and properties dictionary.</param>
7515 /// <returns type="Boolean">True is the content type indicates a json light payload. False otherwise.</returns>
7516
7517 if (contentType) {
7518 var odata = contentType.properties.odata;
7519 return odata === "nometadata" || odata === "minimalmetadata" || odata === "fullmetadata";
7520 }
7521 return false;
7522 };
7523
7524 var normalizeServiceDocument = function (data, baseURI) {
7525 /// <summary>Normalizes a JSON service document to look like an ATOM service document.</summary>
7526 /// <param name="data" type="Object">Object representation of service documents as deserialized.</param>
7527 /// <param name="baseURI" type="String">Base URI to resolve relative URIs.</param>
7528 /// <returns type="Object">An object representation of the service document.</returns>
7529 var workspace = { collections: [] };
7530
7531 var i, len;
7532 for (i = 0, len = data.EntitySets.length; i < len; i++) {
7533 var title = data.EntitySets[i];
7534 var collection = {
7535 title: title,
7536 href: normalizeURI(title, baseURI)
7537 };
7538
7539 workspace.collections.push(collection);
7540 }
7541
7542 return { workspaces: [workspace] };
7543 };
7544
7545 // The regular expression corresponds to something like this:
7546 // /Date(123+60)/
7547 //
7548 // This first number is date ticks, the + may be a - and is optional,
7549 // with the second number indicating a timezone offset in minutes.
7550 //
7551 // On the wire, the leading and trailing forward slashes are
7552 // escaped without being required to so the chance of collisions is reduced;
7553 // however, by the time we see the objects, the characters already
7554 // look like regular forward slashes.
7555 var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
7556
7557 var minutesToOffset = function (minutes) {
7558 /// <summary>Formats the given minutes into (+/-)hh:mm format.</summary>
7559 /// <param name="minutes" type="Number">Number of minutes to format.</param>
7560 /// <returns type="String">The minutes in (+/-)hh:mm format.</returns>
7561
7562 var sign;
7563 if (minutes < 0) {
7564 sign = "-";
7565 minutes = -minutes;
7566 } else {
7567 sign = "+";
7568 }
7569
7570 var hours = Math.floor(minutes / 60);
7571 minutes = minutes - (60 * hours);
7572
7573 return sign + formatNumberWidth(hours, 2) + ":" + formatNumberWidth(minutes, 2);
7574 };
7575
7576 var parseJsonDateString = function (value) {
7577 /// <summary>Parses the JSON Date representation into a Date object.</summary>
7578 /// <param name="value" type="String">String value.</param>
7579 /// <returns type="Date">A Date object if the value matches one; falsy otherwise.</returns>
7580
7581 var arr = value && jsonDateRE.exec(value);
7582 if (arr) {
7583 // 0 - complete results; 1 - ticks; 2 - sign; 3 - minutes
7584 var result = new Date(parseInt10(arr[1]));
7585 if (arr[2]) {
7586 var mins = parseInt10(arr[3]);
7587 if (arr[2] === "-") {
7588 mins = -mins;
7589 }
7590
7591 // The offset is reversed to get back the UTC date, which is
7592 // what the API will eventually have.
7593 var current = result.getUTCMinutes();
7594 result.setUTCMinutes(current - mins);
7595 result.__edmType = "Edm.DateTimeOffset";
7596 result.__offset = minutesToOffset(mins);
7597 }
7598 if (!isNaN(result.valueOf())) {
7599 return result;
7600 }
7601 }
7602
7603 // Allow undefined to be returned.
7604 };
7605
7606 // Some JSON implementations cannot produce the character sequence \/
7607 // which is needed to format DateTime and DateTimeOffset into the
7608 // JSON string representation defined by the OData protocol.
7609 // See the history of this file for a candidate implementation of
7610 // a 'formatJsonDateString' function.
7611
7612 var jsonParser = function (handler, text, context) {
7613 /// <summary>Parses a JSON OData payload.</summary>
7614 /// <param name="handler">This handler.</param>
7615 /// <param name="text">Payload text (this parser also handles pre-parsed objects).</param>
7616 /// <param name="context" type="Object">Object with parsing context.</param>
7617 /// <returns>An object representation of the OData payload.</returns>
7618
7619 var recognizeDates = defined(context.recognizeDates, handler.recognizeDates);
7620 var inferJsonLightFeedAsObject = defined(context.inferJsonLightFeedAsObject, handler.inferJsonLightFeedAsObject);
7621 var model = context.metadata;
7622 var dataServiceVersion = context.dataServiceVersion;
7623 var dateParser = parseJsonDateString;
7624 var json = (typeof text === "string") ? window.JSON.parse(text) : text;
7625
7626 if ((maxVersion("3.0", dataServiceVersion) === dataServiceVersion)) {
7627 if (isJsonLight(context.contentType)) {
7628 return jsonLightReadPayload(json, model, recognizeDates, inferJsonLightFeedAsObject, context.contentType.properties.odata);
7629 }
7630 dateParser = parseDateTime;
7631 }
7632
7633 json = traverse(json.d, function (key, value) {
7634 return jsonApplyMetadata(value, model, dateParser, recognizeDates);
7635 });
7636
7637 json = jsonUpdateDataFromVersion(json, context.dataServiceVersion);
7638 return jsonNormalizeData(json, context.response.requestUri);
7639 };
7640
7641 var jsonToString = function (data) {
7642 /// <summary>Converts the data into a JSON string.</summary>
7643 /// <param name="data">Data to serialize.</param>
7644 /// <returns type="String">The JSON string representation of data.</returns>
7645
7646 var result; // = undefined;
7647 // Save the current date.toJSON function
7648 var dateToJSON = Date.prototype.toJSON;
7649 try {
7650 // Set our own date.toJSON function
7651 Date.prototype.toJSON = function () {
7652 return formatDateTimeOffset(this);
7653 };
7654 result = window.JSON.stringify(data, jsonReplacer);
7655 } finally {
7656 // Restore the original toJSON function
7657 Date.prototype.toJSON = dateToJSON;
7658 }
7659 return result;
7660 };
7661
7662 var jsonSerializer = function (handler, data, context) {
7663 /// <summary>Serializes the data by returning its string representation.</summary>
7664 /// <param name="handler">This handler.</param>
7665 /// <param name="data">Data to serialize.</param>
7666 /// <param name="context" type="Object">Object with serialization context.</param>
7667 /// <returns type="String">The string representation of data.</returns>
7668
7669 var dataServiceVersion = context.dataServiceVersion || "1.0";
7670 var useJsonLight = defined(context.useJsonLight, handler.useJsonLight);
7671 var cType = context.contentType = context.contentType || jsonContentType;
7672
7673 if (cType && cType.mediaType === jsonContentType.mediaType) {
7674 var json = data;
7675 if (useJsonLight || isJsonLight(cType)) {
7676 context.dataServiceVersion = maxVersion(dataServiceVersion, "3.0");
7677 json = formatJsonLight(data, context);
7678 return jsonToString(json);
7679 }
7680 if (maxVersion("3.0", dataServiceVersion) === dataServiceVersion) {
7681 cType.properties.odata = "verbose";
7682 context.contentType = cType;
7683 }
7684 return jsonToString(json);
7685 }
7686 return undefined;
7687 };
7688
7689 var jsonReplacer = function (_, value) {
7690 /// <summary>JSON replacer function for converting a value to its JSON representation.</summary>
7691 /// <param value type="Object">Value to convert.</param>
7692 /// <returns type="String">JSON representation of the input value.</returns>
7693 /// <remarks>
7694 /// This method is used during JSON serialization and invoked only by the JSON.stringify function.
7695 /// It should never be called directly.
7696 /// </remarks>
7697
7698 if (value && value.__edmType === "Edm.Time") {
7699 return formatDuration(value);
7700 } else {
7701 return value;
7702 }
7703 };
7704
7705 var jsonNormalizeData = function (data, baseURI) {
7706 /// <summary>
7707 /// Normalizes the specified data into an intermediate representation.
7708 /// like the latest supported version.
7709 /// </summary>
7710 /// <param name="data" optional="false">Data to update.</param>
7711 /// <param name="baseURI" optional="false">URI to use as the base for normalizing references.</param>
7712
7713 var isSvcDoc = isComplex(data) && !data.__metadata && isArray(data.EntitySets);
7714 return isSvcDoc ? normalizeServiceDocument(data, baseURI) : data;
7715 };
7716
7717 var jsonUpdateDataFromVersion = function (data, dataVersion) {
7718 /// <summary>
7719 /// Updates the specified data in the specified version to look
7720 /// like the latest supported version.
7721 /// </summary>
7722 /// <param name="data" optional="false">Data to update.</param>
7723 /// <param name="dataVersion" optional="true" type="String">Version the data is in (possibly unknown).</param>
7724
7725 // Strip the trailing comma if there.
7726 if (dataVersion && dataVersion.lastIndexOf(";") === dataVersion.length - 1) {
7727 dataVersion = dataVersion.substr(0, dataVersion.length - 1);
7728 }
7729
7730 if (!dataVersion || dataVersion === "1.0") {
7731 if (isArray(data)) {
7732 data = { results: data };
7733 }
7734 }
7735
7736 return data;
7737 };
7738
7739 var jsonHandler = handler(jsonParser, jsonSerializer, jsonMediaType, MAX_DATA_SERVICE_VERSION);
7740 jsonHandler.recognizeDates = false;
7741 jsonHandler.useJsonLight = false;
7742 jsonHandler.inferJsonLightFeedAsObject = false;
7743
7744 odata.jsonHandler = jsonHandler;
7745
7746
7747
7748
7749 var batchMediaType = "multipart/mixed";
7750 var responseStatusRegex = /^HTTP\/1\.\d (\d{3}) (.*)$/i;
7751 var responseHeaderRegex = /^([^()<>@,;:\\"\/[\]?={} \t]+)\s?:\s?(.*)/;
7752
7753 var hex16 = function () {
7754 /// <summary>
7755 /// Calculates a random 16 bit number and returns it in hexadecimal format.
7756 /// </summary>
7757 /// <returns type="String">A 16-bit number in hex format.</returns>
7758
7759 return Math.floor((1 + Math.random()) * 0x10000).toString(16).substr(1);
7760 };
7761
7762 var createBoundary = function (prefix) {
7763 /// <summary>
7764 /// Creates a string that can be used as a multipart request boundary.
7765 /// </summary>
7766 /// <param name="prefix" type="String" optional="true">String to use as the start of the boundary string</param>
7767 /// <returns type="String">Boundary string of the format: <prefix><hex16>-<hex16>-<hex16></returns>
7768
7769 return prefix + hex16() + "-" + hex16() + "-" + hex16();
7770 };
7771
7772 var partHandler = function (context) {
7773 /// <summary>
7774 /// Gets the handler for data serialization of individual requests / responses in a batch.
7775 /// </summary>
7776 /// <param name="context">Context used for data serialization.</param>
7777 /// <returns>Handler object.</returns>
7778
7779 return context.handler.partHandler;
7780 };
7781
7782 var currentBoundary = function (context) {
7783 /// <summary>
7784 /// Gets the current boundary used for parsing the body of a multipart response.
7785 /// </summary>
7786 /// <param name="context">Context used for parsing a multipart response.</param>
7787 /// <returns type="String">Boundary string.</returns>
7788
7789 var boundaries = context.boundaries;
7790 return boundaries[boundaries.length - 1];
7791 };
7792
7793 var batchParser = function (handler, text, context) {
7794 /// <summary>Parses a batch response.</summary>
7795 /// <param name="handler">This handler.</param>
7796 /// <param name="text" type="String">Batch text.</param>
7797 /// <param name="context" type="Object">Object with parsing context.</param>
7798 /// <returns>An object representation of the batch.</returns>
7799
7800 var boundary = context.contentType.properties["boundary"];
7801 return { __batchResponses: readBatch(text, { boundaries: [boundary], handlerContext: context }) };
7802 };
7803
7804 var batchSerializer = function (handler, data, context) {
7805 /// <summary>Serializes a batch object representation into text.</summary>
7806 /// <param name="handler">This handler.</param>
7807 /// <param name="data" type="Object">Representation of a batch.</param>
7808 /// <param name="context" type="Object">Object with parsing context.</param>
7809 /// <returns>An text representation of the batch object; undefined if not applicable.</returns>
7810
7811 var cType = context.contentType = context.contentType || contentType(batchMediaType);
7812 if (cType.mediaType === batchMediaType) {
7813 return writeBatch(data, context);
7814 }
7815 };
7816
7817 var readBatch = function (text, context) {
7818 /// <summary>
7819 /// Parses a multipart/mixed response body from from the position defined by the context.
7820 /// </summary>
7821 /// <param name="text" type="String" optional="false">Body of the multipart/mixed response.</param>
7822 /// <param name="context">Context used for parsing.</param>
7823 /// <returns>Array of objects representing the individual responses.</returns>
7824
7825 var delimiter = "--" + currentBoundary(context);
7826
7827 // Move beyond the delimiter and read the complete batch
7828 readTo(text, context, delimiter);
7829
7830 // Ignore the incoming line
7831 readLine(text, context);
7832
7833 // Read the batch parts
7834 var responses = [];
7835 var partEnd;
7836
7837 while (partEnd !== "--" && context.position < text.length) {
7838 var partHeaders = readHeaders(text, context);
7839 var partContentType = contentType(partHeaders["Content-Type"]);
7840
7841 var changeResponses;
7842 if (partContentType && partContentType.mediaType === batchMediaType) {
7843 context.boundaries.push(partContentType.properties["boundary"]);
7844 try {
7845 changeResponses = readBatch(text, context);
7846 } catch (e) {
7847 e.response = readResponse(text, context, delimiter);
7848 changeResponses = [e];
7849 }
7850 responses.push({ __changeResponses: changeResponses });
7851 context.boundaries.pop();
7852 readTo(text, context, "--" + currentBoundary(context));
7853 } else {
7854 if (!partContentType || partContentType.mediaType !== "application/http") {
7855 throw { message: "invalid MIME part type " };
7856 }
7857 // Skip empty line
7858 readLine(text, context);
7859 // Read the response
7860 var response = readResponse(text, context, delimiter);
7861 try {
7862 if (response.statusCode >= 200 && response.statusCode <= 299) {
7863 partHandler(context.handlerContext).read(response, context.handlerContext);
7864 } else {
7865 // Keep track of failed responses and continue processing the batch.
7866 response = { message: "HTTP request failed", response: response };
7867 }
7868 } catch (e) {
7869 response = e;
7870 }
7871
7872 responses.push(response);
7873 }
7874
7875 partEnd = text.substr(context.position, 2);
7876
7877 // Ignore the incoming line.
7878 readLine(text, context);
7879 }
7880 return responses;
7881 };
7882
7883 var readHeaders = function (text, context) {
7884 /// <summary>
7885 /// Parses the http headers in the text from the position defined by the context.
7886 /// </summary>
7887 /// <param name="text" type="String" optional="false">Text containing an http response's headers</param>
7888 /// <param name="context">Context used for parsing.</param>
7889 /// <returns>Object containing the headers as key value pairs.</returns>
7890 /// <remarks>
7891 /// This function doesn't support split headers and it will stop reading when it hits two consecutive line breaks.
7892 /// </remarks>
7893
7894 var headers = {};
7895 var parts;
7896 var line;
7897 var pos;
7898
7899 do {
7900 pos = context.position;
7901 line = readLine(text, context);
7902 parts = responseHeaderRegex.exec(line);
7903 if (parts !== null) {
7904 headers[parts[1]] = parts[2];
7905 } else {
7906 // Whatever was found is not a header, so reset the context position.
7907 context.position = pos;
7908 }
7909 } while (line && parts);
7910
7911 normalizeHeaders(headers);
7912
7913 return headers;
7914 };
7915
7916 var readResponse = function (text, context, delimiter) {
7917 /// <summary>
7918 /// Parses an HTTP response.
7919 /// </summary>
7920 /// <param name="text" type="String" optional="false">Text representing the http response.</param>
7921 /// <param name="context" optional="false">Context used for parsing.</param>
7922 /// <param name="delimiter" type="String" optional="false">String used as delimiter of the multipart response parts.</param>
7923 /// <returns>Object representing the http response.</returns>
7924
7925 // Read the status line.
7926 var pos = context.position;
7927 var match = responseStatusRegex.exec(readLine(text, context));
7928
7929 var statusCode;
7930 var statusText;
7931 var headers;
7932
7933 if (match) {
7934 statusCode = match[1];
7935 statusText = match[2];
7936 headers = readHeaders(text, context);
7937 readLine(text, context);
7938 } else {
7939 context.position = pos;
7940 }
7941
7942 return {
7943 statusCode: statusCode,
7944 statusText: statusText,
7945 headers: headers,
7946 body: readTo(text, context, "\r\n" + delimiter)
7947 };
7948 };
7949
7950 var readLine = function (text, context) {
7951 /// <summary>
7952 /// Returns a substring from the position defined by the context up to the next line break (CRLF).
7953 /// </summary>
7954 /// <param name="text" type="String" optional="false">Input string.</param>
7955 /// <param name="context" optional="false">Context used for reading the input string.</param>
7956 /// <returns type="String">Substring to the first ocurrence of a line break or null if none can be found. </returns>
7957
7958 return readTo(text, context, "\r\n");
7959 };
7960
7961 var readTo = function (text, context, str) {
7962 /// <summary>
7963 /// 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.
7964 /// </summary>
7965 /// <param name="text" type="String" optional="false">Input string.</param>
7966 /// <param name="context" type="Object" optional="false">Context used for reading the input string.</param>
7967 /// <param name="str" type="String" optional="true">Substring to read up to.</param>
7968 /// <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>
7969
7970 var start = context.position || 0;
7971 var end = text.length;
7972 if (str) {
7973 end = text.indexOf(str, start);
7974 if (end === -1) {
7975 return null;
7976 }
7977 context.position = end + str.length;
7978 } else {
7979 context.position = end;
7980 }
7981
7982 return text.substring(start, end);
7983 };
7984
7985 var writeBatch = function (data, context) {
7986 /// <summary>
7987 /// Serializes a batch request object to a string.
7988 /// </summary>
7989 /// <param name="data" optional="false">Batch request object in payload representation format</param>
7990 /// <param name="context" optional="false">Context used for the serialization</param>
7991 /// <returns type="String">String representing the batch request</returns>
7992
7993 if (!isBatch(data)) {
7994 throw { message: "Data is not a batch object." };
7995 }
7996
7997 var batchBoundary = createBoundary("batch_");
7998 var batchParts = data.__batchRequests;
7999 var batch = "";
8000 var i, len;
8001 for (i = 0, len = batchParts.length; i < len; i++) {
8002 batch += writeBatchPartDelimiter(batchBoundary, false) +
8003 writeBatchPart(batchParts[i], context);
8004 }
8005 batch += writeBatchPartDelimiter(batchBoundary, true);
8006
8007 // Register the boundary with the request content type.
8008 var contentTypeProperties = context.contentType.properties;
8009 contentTypeProperties.boundary = batchBoundary;
8010
8011 return batch;
8012 };
8013
8014 var writeBatchPartDelimiter = function (boundary, close) {
8015 /// <summary>
8016 /// Creates the delimiter that indicates that start or end of an individual request.
8017 /// </summary>
8018 /// <param name="boundary" type="String" optional="false">Boundary string used to indicate the start of the request</param>
8019 /// <param name="close" type="Boolean">Flag indicating that a close delimiter string should be generated</param>
8020 /// <returns type="String">Delimiter string</returns>
8021
8022 var result = "\r\n--" + boundary;
8023 if (close) {
8024 result += "--";
8025 }
8026
8027 return result + "\r\n";
8028 };
8029
8030 var writeBatchPart = function (part, context, nested) {
8031 /// <summary>
8032 /// Serializes a part of a batch request to a string. A part can be either a GET request or
8033 /// a change set grouping several CUD (create, update, delete) requests.
8034 /// </summary>
8035 /// <param name="part" optional="false">Request or change set object in payload representation format</param>
8036 /// <param name="context" optional="false">Object containing context information used for the serialization</param>
8037 /// <param name="nested" type="boolean" optional="true">Flag indicating that the part is nested inside a change set</param>
8038 /// <returns type="String">String representing the serialized part</returns>
8039 /// <remarks>
8040 /// A change set is an array of request objects and they cannot be nested inside other change sets.
8041 /// </remarks>
8042
8043 var changeSet = part.__changeRequests;
8044 var result;
8045 if (isArray(changeSet)) {
8046 if (nested) {
8047 throw { message: "Not Supported: change set nested in other change set" };
8048 }
8049
8050 var changeSetBoundary = createBoundary("changeset_");
8051 result = "Content-Type: " + batchMediaType + "; boundary=" + changeSetBoundary + "\r\n";
8052 var i, len;
8053 for (i = 0, len = changeSet.length; i < len; i++) {
8054 result += writeBatchPartDelimiter(changeSetBoundary, false) +
8055 writeBatchPart(changeSet[i], context, true);
8056 }
8057
8058 result += writeBatchPartDelimiter(changeSetBoundary, true);
8059 } else {
8060 result = "Content-Type: application/http\r\nContent-Transfer-Encoding: binary\r\n\r\n";
8061 var partContext = extend({}, context);
8062 partContext.handler = handler;
8063 partContext.request = part;
8064 partContext.contentType = null;
8065
8066 prepareRequest(part, partHandler(context), partContext);
8067 result += writeRequest(part);
8068 }
8069
8070 return result;
8071 };
8072
8073 var writeRequest = function (request) {
8074 /// <summary>
8075 /// Serializes a request object to a string.
8076 /// </summary>
8077 /// <param name="request" optional="false">Request object to serialize</param>
8078 /// <returns type="String">String representing the serialized request</returns>
8079
8080 var result = (request.method ? request.method : "GET") + " " + request.requestUri + " HTTP/1.1\r\n";
8081 for (var name in request.headers) {
8082 if (request.headers[name]) {
8083 result = result + name + ": " + request.headers[name] + "\r\n";
8084 }
8085 }
8086
8087 result += "\r\n";
8088
8089 if (request.body) {
8090 result += request.body;
8091 }
8092
8093 return result;
8094 };
8095
8096 odata.batchHandler = handler(batchParser, batchSerializer, batchMediaType, MAX_DATA_SERVICE_VERSION);
8097
8098
8099
8100 var handlers = [odata.jsonHandler, odata.atomHandler, odata.xmlHandler, odata.textHandler];
8101
8102 var dispatchHandler = function (handlerMethod, requestOrResponse, context) {
8103 /// <summary>Dispatches an operation to handlers.</summary>
8104 /// <param name="handlerMethod" type="String">Name of handler method to invoke.</param>
8105 /// <param name="requestOrResponse" type="Object">request/response argument for delegated call.</param>
8106 /// <param name="context" type="Object">context argument for delegated call.</param>
8107
8108 var i, len;
8109 for (i = 0, len = handlers.length; i < len && !handlers[i][handlerMethod](requestOrResponse, context); i++) {
8110 }
8111
8112 if (i === len) {
8113 throw { message: "no handler for data" };
8114 }
8115 };
8116
8117 odata.defaultSuccess = function (data) {
8118 /// <summary>Default success handler for OData.</summary>
8119 /// <param name="data">Data to process.</param>
8120
8121 window.alert(window.JSON.stringify(data));
8122 };
8123
8124 odata.defaultError = throwErrorCallback;
8125
8126 odata.defaultHandler = {
8127 read: function (response, context) {
8128 /// <summary>Reads the body of the specified response by delegating to JSON and ATOM handlers.</summary>
8129 /// <param name="response">Response object.</param>
8130 /// <param name="context">Operation context.</param>
8131
8132 if (response && assigned(response.body) && response.headers["Content-Type"]) {
8133 dispatchHandler("read", response, context);
8134 }
8135 },
8136
8137 write: function (request, context) {
8138 /// <summary>Write the body of the specified request by delegating to JSON and ATOM handlers.</summary>
8139 /// <param name="request">Reques tobject.</param>
8140 /// <param name="context">Operation context.</param>
8141
8142 dispatchHandler("write", request, context);
8143 },
8144
8145 maxDataServiceVersion: MAX_DATA_SERVICE_VERSION,
8146 accept: "application/atomsvc+xml;q=0.8, application/json;odata=fullmetadata;q=0.7, application/json;q=0.5, */*;q=0.1"
8147 };
8148
8149 odata.defaultMetadata = [];
8150
8151 odata.read = function (urlOrRequest, success, error, handler, httpClient, metadata) {
8152 /// <summary>Reads data from the specified URL.</summary>
8153 /// <param name="urlOrRequest">URL to read data from.</param>
8154 /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param>
8155 /// <param name="error" type="Function" optional="true">Callback for handling errors.</param>
8156 /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param>
8157 /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param>
8158 /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param>
8159
8160 var request;
8161 if (urlOrRequest instanceof String || typeof urlOrRequest === "string") {
8162 request = { requestUri: urlOrRequest };
8163 } else {
8164 request = urlOrRequest;
8165 }
8166
8167 return odata.request(request, success, error, handler, httpClient, metadata);
8168 };
8169
8170 odata.request = function (request, success, error, handler, httpClient, metadata) {
8171 /// <summary>Sends a request containing OData payload to a server.</summary>
8172 /// <param name="request" type="Object">Object that represents the request to be sent.</param>
8173 /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param>
8174 /// <param name="error" type="Function" optional="true">Callback for handling errors.</param>
8175 /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param>
8176 /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param>
8177 /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param>
8178
8179 success = success || odata.defaultSuccess;
8180 error = error || odata.defaultError;
8181 handler = handler || odata.defaultHandler;
8182 httpClient = httpClient || odata.defaultHttpClient;
8183 metadata = metadata || odata.defaultMetadata;
8184
8185 // Augment the request with additional defaults.
8186 request.recognizeDates = defined(request.recognizeDates, odata.jsonHandler.recognizeDates);
8187 request.callbackParameterName = defined(request.callbackParameterName, odata.defaultHttpClient.callbackParameterName);
8188 request.formatQueryString = defined(request.formatQueryString, odata.defaultHttpClient.formatQueryString);
8189 request.enableJsonpCallback = defined(request.enableJsonpCallback, odata.defaultHttpClient.enableJsonpCallback);
8190 request.useJsonLight = defined(request.useJsonLight, odata.jsonHandler.enableJsonpCallback);
8191 request.inferJsonLightFeedAsObject = defined(request.inferJsonLightFeedAsObject, odata.jsonHandler.inferJsonLightFeedAsObject);
8192
8193 // Create the base context for read/write operations, also specifying complete settings.
8194 var context = {
8195 metadata: metadata,
8196 recognizeDates: request.recognizeDates,
8197 callbackParameterName: request.callbackParameterName,
8198 formatQueryString: request.formatQueryString,
8199 enableJsonpCallback: request.enableJsonpCallback,
8200 useJsonLight: request.useJsonLight,
8201 inferJsonLightFeedAsObject: request.inferJsonLightFeedAsObject
8202 };
8203
8204 try {
8205 prepareRequest(request, handler, context);
8206 return invokeRequest(request, success, error, handler, httpClient, context);
8207 } catch (err) {
8208 error(err);
8209 }
8210 };
8211
8212 odata.parseMetadata = function (csdlMetadataDocument) {
8213 /// <summary>Parses the csdl metadata to DataJS metatdata format. This method can be used when the metadata is retrieved using something other than DataJS</summary>
8214 /// <param name="atomMetadata" type="string">A string that represents the entire csdl metadata.</param>
8215 /// <returns type="Object">An object that has the representation of the metadata in Datajs format.</returns>
8216
8217 return metadataParser(null, csdlMetadataDocument);
8218 };
8219
8220 // Configure the batch handler to use the default handler for the batch parts.
8221 odata.batchHandler.partHandler = odata.defaultHandler;
8222
8223
8224
8225 var localStorage = null;
8226
8227 var domStoreDateToJSON = function () {
8228 /// <summary>Converts a Date object into an object representation friendly to JSON serialization.</summary>
8229 /// <returns type="Object">Object that represents the Date.</returns>
8230 /// <remarks>
8231 /// This method is used to override the Date.toJSON method and is called only by
8232 /// JSON.stringify. It should never be called directly.
8233 /// </remarks>
8234
8235 var newValue = { v: this.valueOf(), t: "[object Date]" };
8236 // Date objects might have extra properties on them so we save them.
8237 for (var name in this) {
8238 newValue[name] = this[name];
8239 }
8240 return newValue;
8241 };
8242
8243 var domStoreJSONToDate = function (_, value) {
8244 /// <summary>JSON reviver function for converting an object representing a Date in a JSON stream to a Date object</summary>
8245 /// <param value="Object">Object to convert.</param>
8246 /// <returns type="Date">Date object.</returns>
8247 /// <remarks>
8248 /// This method is used during JSON parsing and invoked only by the reviver function.
8249 /// It should never be called directly.
8250 /// </remarks>
8251
8252 if (value && value.t === "[object Date]") {
8253 var newValue = new Date(value.v);
8254 for (var name in value) {
8255 if (name !== "t" && name !== "v") {
8256 newValue[name] = value[name];
8257 }
8258 }
8259 value = newValue;
8260 }
8261 return value;
8262 };
8263
8264 var qualifyDomStoreKey = function (store, key) {
8265 /// <summary>Qualifies the key with the name of the store.</summary>
8266 /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param>
8267 /// <param name="key" type="String">Key string.</param>
8268 /// <returns type="String">Fully qualified key string.</returns>
8269
8270 return store.name + "#!#" + key;
8271 };
8272
8273 var unqualifyDomStoreKey = function (store, key) {
8274 /// <summary>Gets the key part of a fully qualified key string.</summary>
8275 /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param>
8276 /// <param name="key" type="String">Fully qualified key string.</param>
8277 /// <returns type="String">Key part string</returns>
8278
8279 return key.replace(store.name + "#!#", "");
8280 };
8281
8282 var DomStore = function (name) {
8283 /// <summary>Constructor for store objects that use DOM storage as the underlying mechanism.</summary>
8284 /// <param name="name" type="String">Store name.</param>
8285 this.name = name;
8286 };
8287
8288 DomStore.create = function (name) {
8289 /// <summary>Creates a store object that uses DOM Storage as its underlying mechanism.</summary>
8290 /// <param name="name" type="String">Store name.</param>
8291 /// <returns type="Object">Store object.</returns>
8292
8293 if (DomStore.isSupported()) {
8294 localStorage = localStorage || window.localStorage;
8295 return new DomStore(name);
8296 }
8297
8298 throw { message: "Web Storage not supported by the browser" };
8299 };
8300
8301 DomStore.isSupported = function () {
8302 /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary>
8303 /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</summary>
8304 return !!window.localStorage;
8305 };
8306
8307 DomStore.prototype.add = function (key, value, success, error) {
8308 /// <summary>Adds a new value identified by a key to the store.</summary>
8309 /// <param name="key" type="String">Key string.</param>
8310 /// <param name="value">Value that is going to be added to the store.</param>
8311 /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param>
8312 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8313 /// <remarks>
8314 /// This method errors out if the store already contains the specified key.
8315 /// </remarks>
8316
8317 error = error || this.defaultError;
8318 var store = this;
8319 this.contains(key, function (contained) {
8320 if (!contained) {
8321 store.addOrUpdate(key, value, success, error);
8322 } else {
8323 delay(error, { message: "key already exists", key: key });
8324 }
8325 }, error);
8326 };
8327
8328 DomStore.prototype.addOrUpdate = function (key, value, success, error) {
8329 /// <summary>Adds or updates a value identified by a key to the store.</summary>
8330 /// <param name="key" type="String">Key string.</param>
8331 /// <param name="value">Value that is going to be added or updated to the store.</param>
8332 /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param>
8333 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8334 /// <remarks>
8335 /// 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.
8336 /// </remarks>
8337
8338 error = error || this.defaultError;
8339
8340 if (key instanceof Array) {
8341 error({ message: "Array of keys not supported" });
8342 } else {
8343 var fullKey = qualifyDomStoreKey(this, key);
8344 var oldDateToJSON = Date.prototype.toJSON;
8345 try {
8346 var storedValue = value;
8347 if (storedValue !== undefined) {
8348 // Dehydrate using json
8349 Date.prototype.toJSON = domStoreDateToJSON;
8350 storedValue = window.JSON.stringify(value);
8351 }
8352 // Save the json string.
8353 localStorage.setItem(fullKey, storedValue);
8354 delay(success, key, value);
8355 }
8356 catch (e) {
8357 if (e.code === 22 || e.number === 0x8007000E) {
8358 delay(error, { name: "QUOTA_EXCEEDED_ERR", error: e });
8359 } else {
8360 delay(error, e);
8361 }
8362 }
8363 finally {
8364 Date.prototype.toJSON = oldDateToJSON;
8365 }
8366 }
8367 };
8368
8369 DomStore.prototype.clear = function (success, error) {
8370 /// <summary>Removes all the data associated with this store object.</summary>
8371 /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param>
8372 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8373 /// <remarks>
8374 /// In case of an error, this method will not restore any keys that might have been deleted at that point.
8375 /// </remarks>
8376
8377 error = error || this.defaultError;
8378 try {
8379 var i = 0, len = localStorage.length;
8380 while (len > 0 && i < len) {
8381 var fullKey = localStorage.key(i);
8382 var key = unqualifyDomStoreKey(this, fullKey);
8383 if (fullKey !== key) {
8384 localStorage.removeItem(fullKey);
8385 len = localStorage.length;
8386 } else {
8387 i++;
8388 }
8389 }
8390 delay(success);
8391 }
8392 catch (e) {
8393 delay(error, e);
8394 }
8395 };
8396
8397 DomStore.prototype.close = function () {
8398 /// <summary>This function does nothing in DomStore as it does not have a connection model</summary>
8399 };
8400
8401 DomStore.prototype.contains = function (key, success, error) {
8402 /// <summary>Checks whether a key exists in the store.</summary>
8403 /// <param name="key" type="String">Key string.</param>
8404 /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param>
8405 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8406 error = error || this.defaultError;
8407 try {
8408 var fullKey = qualifyDomStoreKey(this, key);
8409 var value = localStorage.getItem(fullKey);
8410 delay(success, value !== null);
8411 } catch (e) {
8412 delay(error, e);
8413 }
8414 };
8415
8416 DomStore.prototype.defaultError = throwErrorCallback;
8417
8418 DomStore.prototype.getAllKeys = function (success, error) {
8419 /// <summary>Gets all the keys that exist in the store.</summary>
8420 /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param>
8421 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8422
8423 error = error || this.defaultError;
8424
8425 var results = [];
8426 var i, len;
8427
8428 try {
8429 for (i = 0, len = localStorage.length; i < len; i++) {
8430 var fullKey = localStorage.key(i);
8431 var key = unqualifyDomStoreKey(this, fullKey);
8432 if (fullKey !== key) {
8433 results.push(key);
8434 }
8435 }
8436 delay(success, results);
8437 }
8438 catch (e) {
8439 delay(error, e);
8440 }
8441 };
8442
8443 /// <summary>Identifies the underlying mechanism used by the store.</summary>
8444 DomStore.prototype.mechanism = "dom";
8445
8446 DomStore.prototype.read = function (key, success, error) {
8447 /// <summary>Reads the value associated to a key in the store.</summary>
8448 /// <param name="key" type="String">Key string.</param>
8449 /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param>
8450 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8451 error = error || this.defaultError;
8452
8453 if (key instanceof Array) {
8454 error({ message: "Array of keys not supported" });
8455 } else {
8456 try {
8457 var fullKey = qualifyDomStoreKey(this, key);
8458 var value = localStorage.getItem(fullKey);
8459 if (value !== null && value !== "undefined") {
8460 // Hydrate using json
8461 value = window.JSON.parse(value, domStoreJSONToDate);
8462 }
8463 else {
8464 value = undefined;
8465 }
8466 delay(success, key, value);
8467 } catch (e) {
8468 delay(error, e);
8469 }
8470 }
8471 };
8472
8473 DomStore.prototype.remove = function (key, success, error) {
8474 /// <summary>Removes a key and its value from the store.</summary>
8475 /// <param name="key" type="String">Key string.</param>
8476 /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param>
8477 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8478 error = error || this.defaultError;
8479
8480 if (key instanceof Array) {
8481 error({ message: "Batches not supported" });
8482 } else {
8483 try {
8484 var fullKey = qualifyDomStoreKey(this, key);
8485 localStorage.removeItem(fullKey);
8486 delay(success);
8487 } catch (e) {
8488 delay(error, e);
8489 }
8490 }
8491 };
8492
8493 DomStore.prototype.update = function (key, value, success, error) {
8494 /// <summary>Updates the value associated to a key in the store.</summary>
8495 /// <param name="key" type="String">Key string.</param>
8496 /// <param name="value">New value.</param>
8497 /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param>
8498 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8499 /// <remarks>
8500 /// This method errors out if the specified key is not found in the store.
8501 /// </remarks>
8502
8503 error = error || this.defaultError;
8504 var store = this;
8505 this.contains(key, function (contained) {
8506 if (contained) {
8507 store.addOrUpdate(key, value, success, error);
8508 } else {
8509 delay(error, { message: "key not found", key: key });
8510 }
8511 }, error);
8512 };
8513
8514
8515
8516 var indexedDB = window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB;
8517 var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
8518 var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || {};
8519
8520 var IDBT_READ_ONLY = IDBTransaction.READ_ONLY || "readonly";
8521 var IDBT_READ_WRITE = IDBTransaction.READ_WRITE || "readwrite";
8522
8523 var getError = function (error, defaultError) {
8524 /// <summary>Returns either a specific error handler or the default error handler</summary>
8525 /// <param name="error" type="Function">The specific error handler</param>
8526 /// <param name="defaultError" type="Function">The default error handler</param>
8527 /// <returns type="Function">The error callback</returns>
8528
8529 return function (e) {
8530 var errorFunc = error || defaultError;
8531 if (!errorFunc) {
8532 return;
8533 }
8534
8535 // Old api quota exceeded error support.
8536 if (Object.prototype.toString.call(e) === "[object IDBDatabaseException]") {
8537 if (e.code === 11 /* IndexedDb disk quota exceeded */) {
8538 errorFunc({ name: "QuotaExceededError", error: e });
8539 return;
8540 }
8541 errorFunc(e);
8542 return;
8543 }
8544
8545 var errName;
8546 try {
8547 var errObj = e.target.error || e;
8548 errName = errObj.name;
8549 } catch (ex) {
8550 errName = (e.type === "blocked") ? "IndexedDBBlocked" : "UnknownError";
8551 }
8552 errorFunc({ name: errName, error: e });
8553 };
8554 };
8555
8556 var openStoreDb = function (store, success, error) {
8557 /// <summary>Opens the store object's indexed db database.</summary>
8558 /// <param name="store" type="IndexedDBStore">The store object</param>
8559 /// <param name="success" type="Function">The success callback</param>
8560 /// <param name="error" type="Function">The error callback</param>
8561
8562 var storeName = store.name;
8563 var dbName = "_datajs_" + storeName;
8564
8565 var request = indexedDB.open(dbName);
8566 request.onblocked = error;
8567 request.onerror = error;
8568
8569 request.onupgradeneeded = function () {
8570 var db = request.result;
8571 if (!db.objectStoreNames.contains(storeName)) {
8572 db.createObjectStore(storeName);
8573 }
8574 };
8575
8576 request.onsuccess = function (event) {
8577 var db = request.result;
8578 if (!db.objectStoreNames.contains(storeName)) {
8579 // Should we use the old style api to define the database schema?
8580 if ("setVersion" in db) {
8581 var versionRequest = db.setVersion("1.0");
8582 versionRequest.onsuccess = function () {
8583 var transaction = versionRequest.transaction;
8584 transaction.oncomplete = function () {
8585 success(db);
8586 };
8587 db.createObjectStore(storeName, null, false);
8588 };
8589 versionRequest.onerror = error;
8590 versionRequest.onblocked = error;
8591 return;
8592 }
8593
8594 // The database doesn't have the expected store.
8595 // Fabricate an error object for the event for the schema mismatch
8596 // and error out.
8597 event.target.error = { name: "DBSchemaMismatch" };
8598 error(event);
8599 return;
8600 }
8601
8602 db.onversionchange = function(event) {
8603 event.target.close();
8604 };
8605 success(db);
8606 };
8607 };
8608
8609 var openTransaction = function (store, mode, success, error) {
8610 /// <summary>Opens a new transaction to the store</summary>
8611 /// <param name="store" type="IndexedDBStore">The store object</param>
8612 /// <param name="mode" type="Short">The read/write mode of the transaction (constants from IDBTransaction)</param>
8613 /// <param name="success" type="Function">The success callback</param>
8614 /// <param name="error" type="Function">The error callback</param>
8615
8616 var storeName = store.name;
8617 var storeDb = store.db;
8618 var errorCallback = getError(error, store.defaultError);
8619
8620 if (storeDb) {
8621 success(storeDb.transaction(storeName, mode));
8622 return;
8623 }
8624
8625 openStoreDb(store, function (db) {
8626 store.db = db;
8627 success(db.transaction(storeName, mode));
8628 }, errorCallback);
8629 };
8630
8631 var IndexedDBStore = function (name) {
8632 /// <summary>Creates a new IndexedDBStore.</summary>
8633 /// <param name="name" type="String">The name of the store.</param>
8634 /// <returns type="Object">The new IndexedDBStore.</returns>
8635 this.name = name;
8636 };
8637
8638 IndexedDBStore.create = function (name) {
8639 /// <summary>Creates a new IndexedDBStore.</summary>
8640 /// <param name="name" type="String">The name of the store.</param>
8641 /// <returns type="Object">The new IndexedDBStore.</returns>
8642 if (IndexedDBStore.isSupported()) {
8643 return new IndexedDBStore(name);
8644 }
8645
8646 throw { message: "IndexedDB is not supported on this browser" };
8647 };
8648
8649 IndexedDBStore.isSupported = function () {
8650 /// <summary>Returns whether IndexedDB is supported.</summary>
8651 /// <returns type="Boolean">True if IndexedDB is supported, false otherwise.</returns>
8652 return !!indexedDB;
8653 };
8654
8655 IndexedDBStore.prototype.add = function (key, value, success, error) {
8656 /// <summary>Adds a key/value pair to the store</summary>
8657 /// <param name="key" type="String">The key</param>
8658 /// <param name="value" type="Object">The value</param>
8659 /// <param name="success" type="Function">The success callback</param>
8660 /// <param name="error" type="Function">The error callback</param>
8661 var name = this.name;
8662 var defaultError = this.defaultError;
8663 var keys = [];
8664 var values = [];
8665
8666 if (key instanceof Array) {
8667 keys = key;
8668 values = value;
8669 } else {
8670 keys = [key];
8671 values = [value];
8672 }
8673
8674 openTransaction(this, IDBT_READ_WRITE, function (transaction) {
8675 transaction.onabort = getError(error, defaultError, key, "add");
8676 transaction.oncomplete = function () {
8677 if (key instanceof Array) {
8678 success(keys, values);
8679 } else {
8680 success(key, value);
8681 }
8682 };
8683
8684 for (var i = 0; i < keys.length && i < values.length; i++) {
8685 transaction.objectStore(name).add({ v: values[i] }, keys[i]);
8686 }
8687 }, error);
8688 };
8689
8690 IndexedDBStore.prototype.addOrUpdate = function (key, value, success, error) {
8691 /// <summary>Adds or updates a key/value pair in the store</summary>
8692 /// <param name="key" type="String">The key</param>
8693 /// <param name="value" type="Object">The value</param>
8694 /// <param name="success" type="Function">The success callback</param>
8695 /// <param name="error" type="Function">The error callback</param>
8696 var name = this.name;
8697 var defaultError = this.defaultError;
8698 var keys = [];
8699 var values = [];
8700
8701 if (key instanceof Array) {
8702 keys = key;
8703 values = value;
8704 } else {
8705 keys = [key];
8706 values = [value];
8707 }
8708
8709 openTransaction(this, IDBT_READ_WRITE, function (transaction) {
8710 transaction.onabort = getError(error, defaultError);
8711 transaction.oncomplete = function () {
8712 if (key instanceof Array) {
8713 success(keys, values);
8714 } else {
8715 success(key, value);
8716 }
8717 };
8718
8719 for (var i = 0; i < keys.length && i < values.length; i++) {
8720 var record = { v: values[i] };
8721 transaction.objectStore(name).put(record, keys[i]);
8722 }
8723 }, error);
8724 };
8725
8726 IndexedDBStore.prototype.clear = function (success, error) {
8727 /// <summary>Clears the store</summary>
8728 /// <param name="success" type="Function">The success callback</param>
8729 /// <param name="error" type="Function">The error callback</param>
8730 var name = this.name;
8731 var defaultError = this.defaultError;
8732 openTransaction(this, IDBT_READ_WRITE, function (transaction) {
8733 transaction.onerror = getError(error, defaultError);
8734 transaction.oncomplete = function () {
8735 success();
8736 };
8737
8738 transaction.objectStore(name).clear();
8739 }, error);
8740 };
8741
8742 IndexedDBStore.prototype.close = function () {
8743 /// <summary>Closes the connection to the database</summary>
8744 if (this.db) {
8745 this.db.close();
8746 this.db = null;
8747 }
8748 };
8749
8750 IndexedDBStore.prototype.contains = function (key, success, error) {
8751 /// <summary>Returns whether the store contains a key</summary>
8752 /// <param name="key" type="String">The key</param>
8753 /// <param name="success" type="Function">The success callback</param>
8754 /// <param name="error" type="Function">The error callback</param>
8755 var name = this.name;
8756 var defaultError = this.defaultError;
8757 openTransaction(this, IDBT_READ_ONLY, function (transaction) {
8758 var objectStore = transaction.objectStore(name);
8759 var request = objectStore["get"](key);
8760
8761 transaction.oncomplete = function () {
8762 success(!!request.result);
8763 };
8764 transaction.onerror = getError(error, defaultError);
8765 }, error);
8766 };
8767
8768 IndexedDBStore.prototype.defaultError = throwErrorCallback;
8769
8770 IndexedDBStore.prototype.getAllKeys = function (success, error) {
8771 /// <summary>Gets all the keys from the store</summary>
8772 /// <param name="success" type="Function">The success callback</param>
8773 /// <param name="error" type="Function">The error callback</param>
8774 var name = this.name;
8775 var defaultError = this.defaultError;
8776 openTransaction(this, IDBT_READ_WRITE, function (transaction) {
8777 var results = [];
8778
8779 transaction.oncomplete = function () {
8780 success(results);
8781 };
8782
8783 var request = transaction.objectStore(name).openCursor();
8784
8785 request.onerror = getError(error, defaultError);
8786 request.onsuccess = function (event) {
8787 var cursor = event.target.result;
8788 if (cursor) {
8789 results.push(cursor.key);
8790 // Some tools have issues because continue is a javascript reserved word.
8791 cursor["continue"].call(cursor);
8792 }
8793 };
8794 }, error);
8795 };
8796
8797 /// <summary>Identifies the underlying mechanism used by the store.</summary>
8798 IndexedDBStore.prototype.mechanism = "indexeddb";
8799
8800 IndexedDBStore.prototype.read = function (key, success, error) {
8801 /// <summary>Reads the value for the specified key</summary>
8802 /// <param name="key" type="String">The key</param>
8803 /// <param name="success" type="Function">The success callback</param>
8804 /// <param name="error" type="Function">The error callback</param>
8805 /// <remarks>If the key does not exist, the success handler will be called with value = undefined</remarks>
8806 var name = this.name;
8807 var defaultError = this.defaultError;
8808 var keys = (key instanceof Array) ? key : [key];
8809
8810 openTransaction(this, IDBT_READ_ONLY, function (transaction) {
8811 var values = [];
8812
8813 transaction.onerror = getError(error, defaultError, key, "read");
8814 transaction.oncomplete = function () {
8815 if (key instanceof Array) {
8816 success(keys, values);
8817 } else {
8818 success(keys[0], values[0]);
8819 }
8820 };
8821
8822 for (var i = 0; i < keys.length; i++) {
8823 // Some tools have issues because get is a javascript reserved word.
8824 var objectStore = transaction.objectStore(name);
8825 var request = objectStore["get"].call(objectStore, keys[i]);
8826 request.onsuccess = function (event) {
8827 var record = event.target.result;
8828 values.push(record ? record.v : undefined);
8829 };
8830 }
8831 }, error);
8832 };
8833
8834 IndexedDBStore.prototype.remove = function (key, success, error) {
8835 /// <summary>Removes the specified key from the store</summary>
8836 /// <param name="key" type="String">The key</param>
8837 /// <param name="success" type="Function">The success callback</param>
8838 /// <param name="error" type="Function">The error callback</param>
8839 var name = this.name;
8840 var defaultError = this.defaultError;
8841 var keys = (key instanceof Array) ? key : [key];
8842
8843 openTransaction(this, IDBT_READ_WRITE, function (transaction) {
8844 transaction.onerror = getError(error, defaultError);
8845 transaction.oncomplete = function () {
8846 success();
8847 };
8848
8849 for (var i = 0; i < keys.length; i++) {
8850 // Some tools have issues because continue is a javascript reserved word.
8851 var objectStore = transaction.objectStore(name);
8852 objectStore["delete"].call(objectStore, keys[i]);
8853 }
8854 }, error);
8855 };
8856
8857 IndexedDBStore.prototype.update = function (key, value, success, error) {
8858 /// <summary>Updates a key/value pair in the store</summary>
8859 /// <param name="key" type="String">The key</param>
8860 /// <param name="value" type="Object">The value</param>
8861 /// <param name="success" type="Function">The success callback</param>
8862 /// <param name="error" type="Function">The error callback</param>
8863 var name = this.name;
8864 var defaultError = this.defaultError;
8865 var keys = [];
8866 var values = [];
8867
8868 if (key instanceof Array) {
8869 keys = key;
8870 values = value;
8871 } else {
8872 keys = [key];
8873 values = [value];
8874 }
8875
8876 openTransaction(this, IDBT_READ_WRITE, function (transaction) {
8877 transaction.onabort = getError(error, defaultError);
8878 transaction.oncomplete = function () {
8879 if (key instanceof Array) {
8880 success(keys, values);
8881 } else {
8882 success(key, value);
8883 }
8884 };
8885
8886 for (var i = 0; i < keys.length && i < values.length; i++) {
8887 var request = transaction.objectStore(name).openCursor(IDBKeyRange.only(keys[i]));
8888 var record = { v: values[i] };
8889 request.pair = { key: keys[i], value: record };
8890 request.onsuccess = function (event) {
8891 var cursor = event.target.result;
8892 if (cursor) {
8893 cursor.update(event.target.pair.value);
8894 } else {
8895 transaction.abort();
8896 }
8897 };
8898 }
8899 }, error);
8900 };
8901
8902
8903
8904 var MemoryStore = function (name) {
8905 /// <summary>Constructor for store objects that use a sorted array as the underlying mechanism.</summary>
8906 /// <param name="name" type="String">Store name.</param>
8907
8908 var holes = [];
8909 var items = [];
8910 var keys = {};
8911
8912 this.name = name;
8913
8914 var getErrorCallback = function (error) {
8915 return error || this.defaultError;
8916 };
8917
8918 var validateKeyInput = function (key, error) {
8919 /// <summary>Validates that the specified key is not undefined, not null, and not an array</summary>
8920 /// <param name="key">Key value.</param>
8921 /// <param name="error" type="Function">Error callback.</param>
8922 /// <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>
8923
8924 var messageString;
8925
8926 if (key instanceof Array) {
8927 messageString = "Array of keys not supported";
8928 }
8929
8930 if (key === undefined || key === null) {
8931 messageString = "Invalid key";
8932 }
8933
8934 if (messageString) {
8935 delay(error, { message: messageString });
8936 return false;
8937 }
8938 return true;
8939 };
8940
8941 this.add = function (key, value, success, error) {
8942 /// <summary>Adds a new value identified by a key to the store.</summary>
8943 /// <param name="key" type="String">Key string.</param>
8944 /// <param name="value">Value that is going to be added to the store.</param>
8945 /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param>
8946 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8947 /// <remarks>
8948 /// This method errors out if the store already contains the specified key.
8949 /// </remarks>
8950
8951 error = getErrorCallback(error);
8952
8953 if (validateKeyInput(key, error)) {
8954 if (!keys.hasOwnProperty(key)) {
8955 this.addOrUpdate(key, value, success, error);
8956 } else {
8957 error({ message: "key already exists", key: key });
8958 }
8959 }
8960 };
8961
8962 this.addOrUpdate = function (key, value, success, error) {
8963 /// <summary>Adds or updates a value identified by a key to the store.</summary>
8964 /// <param name="key" type="String">Key string.</param>
8965 /// <param name="value">Value that is going to be added or updated to the store.</param>
8966 /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param>
8967 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
8968 /// <remarks>
8969 /// 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.
8970 /// </remarks>
8971
8972 error = getErrorCallback(error);
8973
8974 if (validateKeyInput(key, error)) {
8975 var index = keys[key];
8976 if (index === undefined) {
8977 if (holes.length > 0) {
8978 index = holes.splice(0, 1);
8979 } else {
8980 index = items.length;
8981 }
8982 }
8983 items[index] = value;
8984 keys[key] = index;
8985 delay(success, key, value);
8986 }
8987 };
8988
8989 this.clear = function (success) {
8990 /// <summary>Removes all the data associated with this store object.</summary>
8991 /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param>
8992
8993 items = [];
8994 keys = {};
8995 holes = [];
8996
8997 delay(success);
8998 };
8999
9000 this.contains = function (key, success) {
9001 /// <summary>Checks whether a key exists in the store.</summary>
9002 /// <param name="key" type="String">Key string.</param>
9003 /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param>
9004
9005 var contained = keys.hasOwnProperty(key);
9006 delay(success, contained);
9007 };
9008
9009 this.getAllKeys = function (success) {
9010 /// <summary>Gets all the keys that exist in the store.</summary>
9011 /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param>
9012
9013 var results = [];
9014 for (var name in keys) {
9015 results.push(name);
9016 }
9017 delay(success, results);
9018 };
9019
9020 this.read = function (key, success, error) {
9021 /// <summary>Reads the value associated to a key in the store.</summary>
9022 /// <param name="key" type="String">Key string.</param>
9023 /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param>
9024 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
9025 error = getErrorCallback(error);
9026
9027 if (validateKeyInput(key, error)) {
9028 var index = keys[key];
9029 delay(success, key, items[index]);
9030 }
9031 };
9032
9033 this.remove = function (key, success, error) {
9034 /// <summary>Removes a key and its value from the store.</summary>
9035 /// <param name="key" type="String">Key string.</param>
9036 /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param>
9037 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
9038 error = getErrorCallback(error);
9039
9040 if (validateKeyInput(key, error)) {
9041 var index = keys[key];
9042 if (index !== undefined) {
9043 if (index === items.length - 1) {
9044 items.pop();
9045 } else {
9046 items[index] = undefined;
9047 holes.push(index);
9048 }
9049 delete keys[key];
9050
9051 // The last item was removed, no need to keep track of any holes in the array.
9052 if (items.length === 0) {
9053 holes = [];
9054 }
9055 }
9056
9057 delay(success);
9058 }
9059 };
9060
9061 this.update = function (key, value, success, error) {
9062 /// <summary>Updates the value associated to a key in the store.</summary>
9063 /// <param name="key" type="String">Key string.</param>
9064 /// <param name="value">New value.</param>
9065 /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param>
9066 /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
9067 /// <remarks>
9068 /// This method errors out if the specified key is not found in the store.
9069 /// </remarks>
9070
9071 error = getErrorCallback(error);
9072 if (validateKeyInput(key, error)) {
9073 if (keys.hasOwnProperty(key)) {
9074 this.addOrUpdate(key, value, success, error);
9075 } else {
9076 error({ message: "key not found", key: key });
9077 }
9078 }
9079 };
9080 };
9081
9082 MemoryStore.create = function (name) {
9083 /// <summary>Creates a store object that uses memory storage as its underlying mechanism.</summary>
9084 /// <param name="name" type="String">Store name.</param>
9085 /// <returns type="Object">Store object.</returns>
9086 return new MemoryStore(name);
9087 };
9088
9089 MemoryStore.isSupported = function () {
9090 /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary>
9091 /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</returns>
9092 return true;
9093 };
9094
9095 MemoryStore.prototype.close = function () {
9096 /// <summary>This function does nothing in MemoryStore as it does not have a connection model.</summary>
9097 };
9098
9099 MemoryStore.prototype.defaultError = throwErrorCallback;
9100
9101 /// <summary>Identifies the underlying mechanism used by the store.</summary>
9102 MemoryStore.prototype.mechanism = "memory";
9103
9104
9105
9106 var mechanisms = {
9107 indexeddb: IndexedDBStore,
9108 dom: DomStore,
9109 memory: MemoryStore
9110 };
9111
9112 datajs.defaultStoreMechanism = "best";
9113
9114 datajs.createStore = function (name, mechanism) {
9115 /// <summary>Creates a new store object.</summary>
9116 /// <param name="name" type="String">Store name.</param>
9117 /// <param name="mechanism" type="String" optional="true">A specific mechanism to use (defaults to best, can be "best", "dom", "indexeddb", "webdb").</param>
9118 /// <returns type="Object">Store object.</returns>
9119
9120 if (!mechanism) {
9121 mechanism = datajs.defaultStoreMechanism;
9122 }
9123
9124 if (mechanism === "best") {
9125 mechanism = (DomStore.isSupported()) ? "dom" : "memory";
9126 }
9127
9128 var factory = mechanisms[mechanism];
9129 if (factory) {
9130 return factory.create(name);
9131 }
9132
9133 throw { message: "Failed to create store", name: name, mechanism: mechanism };
9134 };
9135
9136
9137
9138
9139 var appendQueryOption = function (uri, queryOption) {
9140 /// <summary>Appends the specified escaped query option to the specified URI.</summary>
9141 /// <param name="uri" type="String">URI to append option to.</param>
9142 /// <param name="queryOption" type="String">Escaped query option to append.</param>
9143 var separator = (uri.indexOf("?") >= 0) ? "&" : "?";
9144 return uri + separator + queryOption;
9145 };
9146
9147 var appendSegment = function (uri, segment) {
9148 /// <summary>Appends the specified segment to the given URI.</summary>
9149 /// <param name="uri" type="String">URI to append a segment to.</param>
9150 /// <param name="segment" type="String">Segment to append.</param>
9151 /// <returns type="String">The original URI with a new segment appended.</returns>
9152
9153 var index = uri.indexOf("?");
9154 var queryPortion = "";
9155 if (index >= 0) {
9156 queryPortion = uri.substr(index);
9157 uri = uri.substr(0, index);
9158 }
9159
9160 if (uri[uri.length - 1] !== "/") {
9161 uri += "/";
9162 }
9163 return uri + segment + queryPortion;
9164 };
9165
9166 var buildODataRequest = function (uri, options) {
9167 /// <summary>Builds a request object to GET the specified URI.</summary>
9168 /// <param name="uri" type="String">URI for request.</param>
9169 /// <param name="options" type="Object">Additional options.</param>
9170
9171 return {
9172 method: "GET",
9173 requestUri: uri,
9174 user: options.user,
9175 password: options.password,
9176 enableJsonpCallback: options.enableJsonpCallback,
9177 callbackParameterName: options.callbackParameterName,
9178 formatQueryString: options.formatQueryString
9179 };
9180 };
9181
9182 var findQueryOptionStart = function (uri, name) {
9183 /// <summary>Finds the index where the value of a query option starts.</summary>
9184 /// <param name="uri" type="String">URI to search in.</param>
9185 /// <param name="name" type="String">Name to look for.</param>
9186 /// <returns type="Number">The index where the query option starts.</returns>
9187
9188 var result = -1;
9189 var queryIndex = uri.indexOf("?");
9190 if (queryIndex !== -1) {
9191 var start = uri.indexOf("?" + name + "=", queryIndex);
9192 if (start === -1) {
9193 start = uri.indexOf("&" + name + "=", queryIndex);
9194 }
9195 if (start !== -1) {
9196 result = start + name.length + 2;
9197 }
9198 }
9199 return result;
9200 };
9201
9202 var queryForData = function (uri, options, success, error) {
9203 /// <summary>Gets data from an OData service.</summary>
9204 /// <param name="uri" type="String">URI to the OData service.</param>
9205 /// <param name="options" type="Object">Object with additional well-known request options.</param>
9206 /// <param name="success" type="Function">Success callback.</param>
9207 /// <param name="error" type="Function">Error callback.</param>
9208 /// <returns type="Object">Object with an abort method.</returns>
9209
9210 var request = queryForDataInternal(uri, options, [], success, error);
9211 return request;
9212 };
9213
9214 var queryForDataInternal = function (uri, options, data, success, error) {
9215 /// <summary>Gets data from an OData service taking into consideration server side paging.</summary>
9216 /// <param name="uri" type="String">URI to the OData service.</param>
9217 /// <param name="options" type="Object">Object with additional well-known request options.</param>
9218 /// <param name="data" type="Array">Array that stores the data provided by the OData service.</param>
9219 /// <param name="success" type="Function">Success callback.</param>
9220 /// <param name="error" type="Function">Error callback.</param>
9221 /// <returns type="Object">Object with an abort method.</returns>
9222
9223 var request = buildODataRequest(uri, options);
9224 var currentRequest = odata.request(request, function (newData) {
9225 var next = newData.__next;
9226 var results = newData.results;
9227
9228 data = data.concat(results);
9229
9230 if (next) {
9231 currentRequest = queryForDataInternal(next, options, data, success, error);
9232 } else {
9233 success(data);
9234 }
9235 }, error, undefined, options.httpClient, options.metadata);
9236
9237 return {
9238 abort: function () {
9239 currentRequest.abort();
9240 }
9241 };
9242 };
9243
9244 var ODataCacheSource = function (options) {
9245 /// <summary>Creates a data cache source object for requesting data from an OData service.</summary>
9246 /// <param name="options">Options for the cache data source.</param>
9247 /// <returns type="ODataCacheSource">A new data cache source instance.</returns>
9248
9249 var that = this;
9250 var uri = options.source;
9251
9252 that.identifier = normalizeURICase(encodeURI(decodeURI(uri)));
9253 that.options = options;
9254
9255 that.count = function (success, error) {
9256 /// <summary>Gets the number of items in the collection.</summary>
9257 /// <param name="success" type="Function">Success callback with the item count.</param>
9258 /// <param name="error" type="Function">Error callback.</param>
9259 /// <returns type="Object">Request object with an abort method./<param>
9260
9261 var options = that.options;
9262 return odata.request(
9263 buildODataRequest(appendSegment(uri, "$count"), options),
9264 function (data) {
9265 var count = parseInt10(data.toString());
9266 if (isNaN(count)) {
9267 error({ message: "Count is NaN", count: count });
9268 } else {
9269 success(count);
9270 }
9271 }, error, undefined, options.httpClient, options.metadata);
9272 };
9273
9274 that.read = function (index, count, success, error) {
9275 /// <summary>Gets a number of consecutive items from the collection.</summary>
9276 /// <param name="index" type="Number">Zero-based index of the items to retrieve.</param>
9277 /// <param name="count" type="Number">Number of items to retrieve.</param>
9278 /// <param name="success" type="Function">Success callback with the requested items.</param>
9279 /// <param name="error" type="Function">Error callback.</param>
9280 /// <returns type="Object">Request object with an abort method./<param>
9281
9282 var queryOptions = "$skip=" + index + "&$top=" + count;
9283 return queryForData(appendQueryOption(uri, queryOptions), that.options, success, error);
9284 };
9285
9286 return that;
9287 };
9288
9289
9290
9291 var appendPage = function (operation, page) {
9292 /// <summary>Appends a page's data to the operation data.</summary>
9293 /// <param name="operation" type="Object">Operation with (i)ndex, (c)ount and (d)ata.</param>
9294 /// <param name="page" type="Object">Page with (i)ndex, (c)ount and (d)ata.</param>
9295
9296 var intersection = intersectRanges(operation, page);
9297 if (intersection) {
9298 var start = intersection.i - page.i;
9299 var end = start + (operation.c - operation.d.length);
9300 operation.d = operation.d.concat(page.d.slice(start, end));
9301 }
9302 };
9303
9304 var intersectRanges = function (x, y) {
9305 /// <summary>Returns the {(i)ndex, (c)ount} range for the intersection of x and y.</summary>
9306 /// <param name="x" type="Object">Range with (i)ndex and (c)ount members.</param>
9307 /// <param name="y" type="Object">Range with (i)ndex and (c)ount members.</param>
9308 /// <returns type="Object">The intersection (i)ndex and (c)ount; undefined if there is no intersection.</returns>
9309
9310 var xLast = x.i + x.c;
9311 var yLast = y.i + y.c;
9312 var resultIndex = (x.i > y.i) ? x.i : y.i;
9313 var resultLast = (xLast < yLast) ? xLast : yLast;
9314 var result;
9315 if (resultLast >= resultIndex) {
9316 result = { i: resultIndex, c: resultLast - resultIndex };
9317 }
9318
9319 return result;
9320 };
9321
9322 var checkZeroGreater = function (val, name) {
9323 /// <summary>Checks whether val is a defined number with value zero or greater.</summary>
9324 /// <param name="val" type="Number">Value to check.</param>
9325 /// <param name="name" type="String">Parameter name to use in exception.</param>
9326
9327 if (val === undefined || typeof val !== "number") {
9328 throw { message: "'" + name + "' must be a number." };
9329 }
9330
9331 if (isNaN(val) || val < 0 || !isFinite(val)) {
9332 throw { message: "'" + name + "' must be greater than or equal to zero." };
9333 }
9334 };
9335
9336 var checkUndefinedGreaterThanZero = function (val, name) {
9337 /// <summary>Checks whether val is undefined or a number with value greater than zero.</summary>
9338 /// <param name="val" type="Number">Value to check.</param>
9339 /// <param name="name" type="String">Parameter name to use in exception.</param>
9340
9341 if (val !== undefined) {
9342 if (typeof val !== "number") {
9343 throw { message: "'" + name + "' must be a number." };
9344 }
9345
9346 if (isNaN(val) || val <= 0 || !isFinite(val)) {
9347 throw { message: "'" + name + "' must be greater than zero." };
9348 }
9349 }
9350 };
9351
9352 var checkUndefinedOrNumber = function (val, name) {
9353 /// <summary>Checks whether val is undefined or a number</summary>
9354 /// <param name="val" type="Number">Value to check.</param>
9355 /// <param name="name" type="String">Parameter name to use in exception.</param>
9356 if (val !== undefined && (typeof val !== "number" || isNaN(val) || !isFinite(val))) {
9357 throw { message: "'" + name + "' must be a number." };
9358 }
9359 };
9360
9361 var removeFromArray = function (arr, item) {
9362 /// <summary>Performs a linear search on the specified array and removes the first instance of 'item'.</summary>
9363 /// <param name="arr" type="Array">Array to search.</param>
9364 /// <param name="item">Item being sought.</param>
9365 /// <returns type="Boolean">Whether the item was removed.</returns>
9366
9367 var i, len;
9368 for (i = 0, len = arr.length; i < len; i++) {
9369 if (arr[i] === item) {
9370 arr.splice(i, 1);
9371 return true;
9372 }
9373 }
9374
9375 return false;
9376 };
9377
9378 var estimateSize = function (obj) {
9379 /// <summary>Estimates the size of an object in bytes.</summary>
9380 /// <param name="obj" type="Object">Object to determine the size of.</param>
9381 /// <returns type="Integer">Estimated size of the object in bytes.</returns>
9382 var size = 0;
9383 var type = typeof obj;
9384
9385 if (type === "object" && obj) {
9386 for (var name in obj) {
9387 size += name.length * 2 + estimateSize(obj[name]);
9388 }
9389 } else if (type === "string") {
9390 size = obj.length * 2;
9391 } else {
9392 size = 8;
9393 }
9394 return size;
9395 };
9396
9397 var snapToPageBoundaries = function (lowIndex, highIndex, pageSize) {
9398 /// <summary>Snaps low and high indices into page sizes and returns a range.</summary>
9399 /// <param name="lowIndex" type="Number">Low index to snap to a lower value.</param>
9400 /// <param name="highIndex" type="Number">High index to snap to a higher value.</param>
9401 /// <param name="pageSize" type="Number">Page size to snap to.</param>
9402 /// <returns type="Object">A range with (i)ndex and (c)ount of elements.</returns>
9403
9404 lowIndex = Math.floor(lowIndex / pageSize) * pageSize;
9405 highIndex = Math.ceil((highIndex + 1) / pageSize) * pageSize;
9406 return { i: lowIndex, c: highIndex - lowIndex };
9407 };
9408
9409 // The DataCache is implemented using state machines. The following constants are used to properly
9410 // identify and label the states that these machines transition to.
9411
9412 // DataCache state constants
9413
9414 var CACHE_STATE_DESTROY = "destroy";
9415 var CACHE_STATE_IDLE = "idle";
9416 var CACHE_STATE_INIT = "init";
9417 var CACHE_STATE_READ = "read";
9418 var CACHE_STATE_PREFETCH = "prefetch";
9419 var CACHE_STATE_WRITE = "write";
9420
9421 // DataCacheOperation state machine states.
9422 // Transitions on operations also depend on the cache current of the cache.
9423
9424 var OPERATION_STATE_CANCEL = "cancel";
9425 var OPERATION_STATE_END = "end";
9426 var OPERATION_STATE_ERROR = "error";
9427 var OPERATION_STATE_START = "start";
9428 var OPERATION_STATE_WAIT = "wait";
9429
9430 // Destroy state machine states
9431
9432 var DESTROY_STATE_CLEAR = "clear";
9433
9434 // Read / Prefetch state machine states
9435
9436 var READ_STATE_DONE = "done";
9437 var READ_STATE_LOCAL = "local";
9438 var READ_STATE_SAVE = "save";
9439 var READ_STATE_SOURCE = "source";
9440
9441 var DataCacheOperation = function (stateMachine, promise, isCancelable, index, count, data, pending) {
9442 /// <summary>Creates a new operation object.</summary>
9443 /// <param name="stateMachine" type="Function">State machine that describes the specific behavior of the operation.</param>
9444 /// <param name="promise" type ="DjsDeferred">Promise for requested values.</param>
9445 /// <param name="isCancelable" type ="Boolean">Whether this operation can be canceled or not.</param>
9446 /// <param name="index" type="Number">Index of first item requested.</param>
9447 /// <param name="count" type="Number">Count of items requested.</param>
9448 /// <param name="data" type="Array">Array with the items requested by the operation.</param>
9449 /// <param name="pending" type="Number">Total number of pending prefetch records.</param>
9450 /// <returns type="DataCacheOperation">A new data cache operation instance.</returns>
9451
9452 /// <field name="p" type="DjsDeferred">Promise for requested values.</field>
9453 /// <field name="i" type="Number">Index of first item requested.</field>
9454 /// <field name="c" type="Number">Count of items requested.</field>
9455 /// <field name="d" type="Array">Array with the items requested by the operation.</field>
9456 /// <field name="s" type="Array">Current state of the operation.</field>
9457 /// <field name="canceled" type="Boolean">Whether the operation has been canceled.</field>
9458 /// <field name="pending" type="Number">Total number of pending prefetch records.</field>
9459 /// <field name="oncomplete" type="Function">Callback executed when the operation reaches the end state.</field>
9460
9461 var stateData;
9462 var cacheState;
9463 var that = this;
9464
9465 that.p = promise;
9466 that.i = index;
9467 that.c = count;
9468 that.d = data;
9469 that.s = OPERATION_STATE_START;
9470
9471 that.canceled = false;
9472 that.pending = pending;
9473 that.oncomplete = null;
9474
9475 that.cancel = function () {
9476 /// <summary>Transitions this operation to the cancel state and sets the canceled flag to true.</summary>
9477 /// <remarks>The function is a no-op if the operation is non-cancelable.</summary>
9478
9479 if (!isCancelable) {
9480 return;
9481 }
9482
9483 var state = that.s;
9484 if (state !== OPERATION_STATE_ERROR && state !== OPERATION_STATE_END && state !== OPERATION_STATE_CANCEL) {
9485 that.canceled = true;
9486 transition(OPERATION_STATE_CANCEL, stateData);
9487 }
9488 };
9489
9490 that.complete = function () {
9491 /// <summary>Transitions this operation to the end state.</summary>
9492
9493 transition(OPERATION_STATE_END, stateData);
9494 };
9495
9496 that.error = function (err) {
9497 /// <summary>Transitions this operation to the error state.</summary>
9498 if (!that.canceled) {
9499 transition(OPERATION_STATE_ERROR, err);
9500 }
9501 };
9502
9503 that.run = function (state) {
9504 /// <summary>Executes the operation's current state in the context of a new cache state.</summary>
9505 /// <param name="state" type="Object">New cache state.</param>
9506
9507 cacheState = state;
9508 that.transition(that.s, stateData);
9509 };
9510
9511 that.wait = function (data) {
9512 /// <summary>Transitions this operation to the wait state.</summary>
9513
9514 transition(OPERATION_STATE_WAIT, data);
9515 };
9516
9517 var operationStateMachine = function (opTargetState, cacheState, data) {
9518 /// <summary>State machine that describes all operations common behavior.</summary>
9519 /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
9520 /// <param name="cacheState" type="Object">Current cache state.</param>
9521 /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
9522
9523 switch (opTargetState) {
9524 case OPERATION_STATE_START:
9525 // Initial state of the operation. The operation will remain in this state until the cache has been fully initialized.
9526 if (cacheState !== CACHE_STATE_INIT) {
9527 stateMachine(that, opTargetState, cacheState, data);
9528 }
9529 break;
9530
9531 case OPERATION_STATE_WAIT:
9532 // Wait state indicating that the operation is active but waiting for an asynchronous operation to complete.
9533 stateMachine(that, opTargetState, cacheState, data);
9534 break;
9535
9536 case OPERATION_STATE_CANCEL:
9537 // Cancel state.
9538 stateMachine(that, opTargetState, cacheState, data);
9539 that.fireCanceled();
9540 transition(OPERATION_STATE_END);
9541 break;
9542
9543 case OPERATION_STATE_ERROR:
9544 // Error state. Data is expected to be an object detailing the error condition.
9545 stateMachine(that, opTargetState, cacheState, data);
9546 that.canceled = true;
9547 that.fireRejected(data);
9548 transition(OPERATION_STATE_END);
9549 break;
9550
9551 case OPERATION_STATE_END:
9552 // Final state of the operation.
9553 if (that.oncomplete) {
9554 that.oncomplete(that);
9555 }
9556 if (!that.canceled) {
9557 that.fireResolved();
9558 }
9559 stateMachine(that, opTargetState, cacheState, data);
9560 break;
9561
9562 default:
9563 // Any other state is passed down to the state machine describing the operation's specific behavior.
9564 stateMachine(that, opTargetState, cacheState, data);
9565 break;
9566 }
9567 };
9568
9569 var transition = function (state, data) {
9570 /// <summary>Transitions this operation to a new state.</summary>
9571 /// <param name="state" type="Object">State to transition the operation to.</param>
9572 /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
9573
9574 that.s = state;
9575 stateData = data;
9576 operationStateMachine(state, cacheState, data);
9577 };
9578
9579 that.transition = transition;
9580
9581 return that;
9582 };
9583
9584 DataCacheOperation.prototype.fireResolved = function () {
9585 /// <summary>Fires a resolved notification as necessary.</summary>
9586
9587 // Fire the resolve just once.
9588 var p = this.p;
9589 if (p) {
9590 this.p = null;
9591 p.resolve(this.d);
9592 }
9593 };
9594
9595 DataCacheOperation.prototype.fireRejected = function (reason) {
9596 /// <summary>Fires a rejected notification as necessary.</summary>
9597
9598 // Fire the rejection just once.
9599 var p = this.p;
9600 if (p) {
9601 this.p = null;
9602 p.reject(reason);
9603 }
9604 };
9605
9606 DataCacheOperation.prototype.fireCanceled = function () {
9607 /// <summary>Fires a canceled notification as necessary.</summary>
9608
9609 this.fireRejected({ canceled: true, message: "Operation canceled" });
9610 };
9611
9612
9613 var DataCache = function (options) {
9614 /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary>
9615 /// <param name="options">
9616 /// Options for the data cache, including name, source, pageSize,
9617 /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
9618 /// </param>
9619 /// <returns type="DataCache">A new data cache instance.</returns>
9620
9621 var state = CACHE_STATE_INIT;
9622 var stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
9623
9624 var clearOperations = [];
9625 var readOperations = [];
9626 var prefetchOperations = [];
9627
9628 var actualCacheSize = 0; // Actual cache size in bytes.
9629 var allDataLocal = false; // Whether all data is local.
9630 var cacheSize = undefinedDefault(options.cacheSize, 1048576); // Requested cache size in bytes, default 1 MB.
9631 var collectionCount = 0; // Number of elements in the server collection.
9632 var highestSavedPage = 0; // Highest index of all the saved pages.
9633 var highestSavedPageSize = 0; // Item count of the saved page with the highest index.
9634 var overflowed = cacheSize === 0; // If the cache has overflowed (actualCacheSize > cacheSize or cacheSize == 0);
9635 var pageSize = undefinedDefault(options.pageSize, 50); // Number of elements to store per page.
9636 var prefetchSize = undefinedDefault(options.prefetchSize, pageSize); // Number of elements to prefetch from the source when the cache is idling.
9637 var version = "1.0";
9638 var cacheFailure;
9639
9640 var pendingOperations = 0;
9641
9642 var source = options.source;
9643 if (typeof source === "string") {
9644 // Create a new cache source.
9645 source = new ODataCacheSource(options);
9646 }
9647 source.options = options;
9648
9649 // Create a cache local store.
9650 var store = datajs.createStore(options.name, options.mechanism);
9651
9652 var that = this;
9653
9654 that.onidle = options.idle;
9655 that.stats = stats;
9656
9657 that.count = function () {
9658 /// <summary>Counts the number of items in the collection.</summary>
9659 /// <returns type="Object">A promise with the number of items.</returns>
9660
9661 if (cacheFailure) {
9662 throw cacheFailure;
9663 }
9664
9665 var deferred = createDeferred();
9666 var canceled = false;
9667
9668 if (allDataLocal) {
9669 delay(function () {
9670 deferred.resolve(collectionCount);
9671 });
9672
9673 return deferred.promise();
9674 }
9675
9676 // TODO: Consider returning the local data count instead once allDataLocal flag is set to true.
9677 var request = source.count(function (count) {
9678 request = null;
9679 stats.counts++;
9680 deferred.resolve(count);
9681 }, function (err) {
9682 request = null;
9683 deferred.reject(extend(err, { canceled: canceled }));
9684 });
9685
9686 return extend(deferred.promise(), {
9687 cancel: function () {
9688 /// <summary>Aborts the count operation.</summary>
9689 if (request) {
9690 canceled = true;
9691 request.abort();
9692 request = null;
9693 }
9694 }
9695 });
9696 };
9697
9698 that.clear = function () {
9699 /// <summary>Cancels all running operations and clears all local data associated with this cache.</summary>
9700 /// <remarks>
9701 /// New read requests made while a clear operation is in progress will not be canceled.
9702 /// Instead they will be queued for execution once the operation is completed.
9703 /// </remarks>
9704 /// <returns type="Object">A promise that has no value and can't be canceled.</returns>
9705
9706 if (cacheFailure) {
9707 throw cacheFailure;
9708 }
9709
9710 if (clearOperations.length === 0) {
9711 var deferred = createDeferred();
9712 var op = new DataCacheOperation(destroyStateMachine, deferred, false);
9713 queueAndStart(op, clearOperations);
9714 return deferred.promise();
9715 }
9716 return clearOperations[0].p;
9717 };
9718
9719 that.filterForward = function (index, count, predicate) {
9720 /// <summary>Filters the cache data based a predicate.</summary>
9721 /// <param name="index" type="Number">The index of the item to start filtering forward from.</param>
9722 /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
9723 /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
9724 /// <remarks>
9725 /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
9726 /// </remarks>
9727 /// <returns type="DjsDeferred">A promise for an array of results.</returns>
9728 return filter(index, count, predicate, false);
9729 };
9730
9731 that.filterBack = function (index, count, predicate) {
9732 /// <summary>Filters the cache data based a predicate.</summary>
9733 /// <param name="index" type="Number">The index of the item to start filtering backward from.</param>
9734 /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
9735 /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
9736 /// <remarks>
9737 /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
9738 /// </remarks>
9739 /// <returns type="DjsDeferred">A promise for an array of results.</returns>
9740 return filter(index, count, predicate, true);
9741 };
9742
9743 that.readRange = function (index, count) {
9744 /// <summary>Reads a range of adjacent records.</summary>
9745 /// <param name="index" type="Number">Zero-based index of record range to read.</param>
9746 /// <param name="count" type="Number">Number of records in the range.</param>
9747 /// <remarks>
9748 /// New read requests made while a clear operation is in progress will not be canceled.
9749 /// Instead they will be queued for execution once the operation is completed.
9750 /// </remarks>
9751 /// <returns type="DjsDeferred">
9752 /// A promise for an array of records; less records may be returned if the
9753 /// end of the collection is found.
9754 /// </returns>
9755
9756 checkZeroGreater(index, "index");
9757 checkZeroGreater(count, "count");
9758
9759 if (cacheFailure) {
9760 throw cacheFailure;
9761 }
9762
9763 var deferred = createDeferred();
9764
9765 // Merging read operations would be a nice optimization here.
9766 var op = new DataCacheOperation(readStateMachine, deferred, true, index, count, [], 0);
9767 queueAndStart(op, readOperations);
9768
9769 return extend(deferred.promise(), {
9770 cancel: function () {
9771 /// <summary>Aborts the readRange operation.</summary>
9772 op.cancel();
9773 }
9774 });
9775 };
9776
9777 that.ToObservable = that.toObservable = function () {
9778 /// <summary>Creates an Observable object that enumerates all the cache contents.</summary>
9779 /// <returns>A new Observable object that enumerates all the cache contents.</returns>
9780 if (!window.Rx || !window.Rx.Observable) {
9781 throw { message: "Rx library not available - include rx.js" };
9782 }
9783
9784 if (cacheFailure) {
9785 throw cacheFailure;
9786 }
9787
9788 return window.Rx.Observable.CreateWithDisposable(function (obs) {
9789 var disposed = false;
9790 var index = 0;
9791
9792 var errorCallback = function (error) {
9793 if (!disposed) {
9794 obs.OnError(error);
9795 }
9796 };
9797
9798 var successCallback = function (data) {
9799 if (!disposed) {
9800 var i, len;
9801 for (i = 0, len = data.length; i < len; i++) {
9802 // The wrapper automatically checks for Dispose
9803 // on the observer, so we don't need to check it here.
9804 obs.OnNext(data[i]);
9805 }
9806
9807 if (data.length < pageSize) {
9808 obs.OnCompleted();
9809 } else {
9810 index += pageSize;
9811 that.readRange(index, pageSize).then(successCallback, errorCallback);
9812 }
9813 }
9814 };
9815
9816 that.readRange(index, pageSize).then(successCallback, errorCallback);
9817
9818 return { Dispose: function () { disposed = true; } };
9819 });
9820 };
9821
9822 var cacheFailureCallback = function (message) {
9823 /// <summary>Creates a function that handles a callback by setting the cache into failure mode.</summary>
9824 /// <param name="message" type="String">Message text.</param>
9825 /// <returns type="Function">Function to use as error callback.</returns>
9826 /// <remarks>
9827 /// This function will specifically handle problems with critical store resources
9828 /// during cache initialization.
9829 /// </remarks>
9830
9831 return function (error) {
9832 cacheFailure = { message: message, error: error };
9833
9834 // Destroy any pending clear or read operations.
9835 // At this point there should be no prefetch operations.
9836 // Count operations will go through but are benign because they
9837 // won't interact with the store.
9838 var i, len;
9839 for (i = 0, len = readOperations.length; i < len; i++) {
9840 readOperations[i].fireRejected(cacheFailure);
9841 }
9842 for (i = 0, len = clearOperations.length; i < len; i++) {
9843 clearOperations[i].fireRejected(cacheFailure);
9844 }
9845
9846 // Null out the operation arrays.
9847 readOperations = clearOperations = null;
9848 };
9849 };
9850
9851 var changeState = function (newState) {
9852 /// <summary>Updates the cache's state and signals all pending operations of the change.</summary>
9853 /// <param name="newState" type="Object">New cache state.</param>
9854 /// <remarks>This method is a no-op if the cache's current state and the new state are the same.</remarks>
9855
9856 if (newState !== state) {
9857 state = newState;
9858 var operations = clearOperations.concat(readOperations, prefetchOperations);
9859 var i, len;
9860 for (i = 0, len = operations.length; i < len; i++) {
9861 operations[i].run(state);
9862 }
9863 }
9864 };
9865
9866 var clearStore = function () {
9867 /// <summary>Removes all the data stored in the cache.</summary>
9868 /// <returns type="DjsDeferred">A promise with no value.</returns>
9869
9870 var deferred = new DjsDeferred();
9871 store.clear(function () {
9872
9873 // Reset the cache settings.
9874 actualCacheSize = 0;
9875 allDataLocal = false;
9876 collectionCount = 0;
9877 highestSavedPage = 0;
9878 highestSavedPageSize = 0;
9879 overflowed = cacheSize === 0;
9880
9881 // version is not reset, in case there is other state in eg V1.1 that is still around.
9882
9883 // Reset the cache stats.
9884 stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
9885 that.stats = stats;
9886
9887 store.close();
9888 deferred.resolve();
9889 }, function (err) {
9890 deferred.reject(err);
9891 });
9892 return deferred;
9893 };
9894
9895 var dequeueOperation = function (operation) {
9896 /// <summary>Removes an operation from the caches queues and changes the cache state to idle.</summary>
9897 /// <param name="operation" type="DataCacheOperation">Operation to dequeue.</param>
9898 /// <remarks>This method is used as a handler for the operation's oncomplete event.</remarks>
9899
9900 var removed = removeFromArray(clearOperations, operation);
9901 if (!removed) {
9902 removed = removeFromArray(readOperations, operation);
9903 if (!removed) {
9904 removeFromArray(prefetchOperations, operation);
9905 }
9906 }
9907
9908 pendingOperations--;
9909 changeState(CACHE_STATE_IDLE);
9910 };
9911
9912 var fetchPage = function (start) {
9913 /// <summary>Requests data from the cache source.</summary>
9914 /// <param name="start" type="Number">Zero-based index of items to request.</param>
9915 /// <returns type="DjsDeferred">A promise for a page object with (i)ndex, (c)ount, (d)ata.</returns>
9916
9917
9918 var deferred = new DjsDeferred();
9919 var canceled = false;
9920
9921 var request = source.read(start, pageSize, function (data) {
9922 var page = { i: start, c: data.length, d: data };
9923 deferred.resolve(page);
9924 }, function (err) {
9925 deferred.reject(err);
9926 });
9927
9928 return extend(deferred, {
9929 cancel: function () {
9930 if (request) {
9931 request.abort();
9932 canceled = true;
9933 request = null;
9934 }
9935 }
9936 });
9937 };
9938
9939 var filter = function (index, count, predicate, backwards) {
9940 /// <summary>Filters the cache data based a predicate.</summary>
9941 /// <param name="index" type="Number">The index of the item to start filtering from.</param>
9942 /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
9943 /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
9944 /// <param name="backwards" type="Boolean">True if the filtering should move backward from the specified index, falsey otherwise.</param>
9945 /// <remarks>
9946 /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
9947 /// </remarks>
9948 /// <returns type="DjsDeferred">A promise for an array of results.</returns>
9949 index = parseInt10(index);
9950 count = parseInt10(count);
9951
9952 if (isNaN(index)) {
9953 throw { message: "'index' must be a valid number.", index: index };
9954 }
9955 if (isNaN(count)) {
9956 throw { message: "'count' must be a valid number.", count: count };
9957 }
9958
9959 if (cacheFailure) {
9960 throw cacheFailure;
9961 }
9962
9963 index = Math.max(index, 0);
9964
9965 var deferred = createDeferred();
9966 var arr = [];
9967 var canceled = false;
9968 var pendingReadRange = null;
9969
9970 var readMore = function (readIndex, readCount) {
9971 if (!canceled) {
9972 if (count >= 0 && arr.length >= count) {
9973 deferred.resolve(arr);
9974 } else {
9975 pendingReadRange = that.readRange(readIndex, readCount).then(function (data) {
9976 for (var i = 0, length = data.length; i < length && (count < 0 || arr.length < count); i++) {
9977 var dataIndex = backwards ? length - i - 1 : i;
9978 var item = data[dataIndex];
9979 if (predicate(item)) {
9980 var element = {
9981 index: readIndex + dataIndex,
9982 item: item
9983 };
9984
9985 backwards ? arr.unshift(element) : arr.push(element);
9986 }
9987 }
9988
9989 // Have we reached the end of the collection?
9990 if ((!backwards && data.length < readCount) || (backwards && readIndex <= 0)) {
9991 deferred.resolve(arr);
9992 } else {
9993 var nextIndex = backwards ? Math.max(readIndex - pageSize, 0) : readIndex + readCount;
9994 readMore(nextIndex, pageSize);
9995 }
9996 }, function (err) {
9997 deferred.reject(err);
9998 });
9999 }
10000 }
10001 };
10002
10003 // Initially, we read from the given starting index to the next/previous page boundary
10004 var initialPage = snapToPageBoundaries(index, index, pageSize);
10005 var initialIndex = backwards ? initialPage.i : index;
10006 var initialCount = backwards ? index - initialPage.i + 1 : initialPage.i + initialPage.c - index;
10007 readMore(initialIndex, initialCount);
10008
10009 return extend(deferred.promise(), {
10010 cancel: function () {
10011 /// <summary>Aborts the filter operation</summary>
10012 if (pendingReadRange) {
10013 pendingReadRange.cancel();
10014 }
10015 canceled = true;
10016 }
10017 });
10018 };
10019
10020 var fireOnIdle = function () {
10021 /// <summary>Fires an onidle event if any functions are assigned.</summary>
10022
10023 if (that.onidle && pendingOperations === 0) {
10024 that.onidle();
10025 }
10026 };
10027
10028 var prefetch = function (start) {
10029 /// <summary>Creates and starts a new prefetch operation.</summary>
10030 /// <param name="start" type="Number">Zero-based index of the items to prefetch.</param>
10031 /// <remarks>
10032 /// This method is a no-op if any of the following conditions is true:
10033 /// 1.- prefetchSize is 0
10034 /// 2.- All data has been read and stored locally in the cache.
10035 /// 3.- There is already an all data prefetch operation queued.
10036 /// 4.- The cache has run out of available space (overflowed).
10037 /// <remarks>
10038
10039 if (allDataLocal || prefetchSize === 0 || overflowed) {
10040 return;
10041 }
10042
10043
10044 if (prefetchOperations.length === 0 || (prefetchOperations[0] && prefetchOperations[0].c !== -1)) {
10045 // Merging prefetch operations would be a nice optimization here.
10046 var op = new DataCacheOperation(prefetchStateMachine, null, true, start, prefetchSize, null, prefetchSize);
10047 queueAndStart(op, prefetchOperations);
10048 }
10049 };
10050
10051 var queueAndStart = function (op, queue) {
10052 /// <summary>Queues an operation and runs it.</summary>
10053 /// <param name="op" type="DataCacheOperation">Operation to queue.</param>
10054 /// <param name="queue" type="Array">Array that will store the operation.</param>
10055
10056 op.oncomplete = dequeueOperation;
10057 queue.push(op);
10058 pendingOperations++;
10059 op.run(state);
10060 };
10061
10062 var readPage = function (key) {
10063 /// <summary>Requests a page from the cache local store.</summary>
10064 /// <param name="key" type="Number">Zero-based index of the reuqested page.</param>
10065 /// <returns type="DjsDeferred">A promise for a found flag and page object with (i)ndex, (c)ount, (d)ata, and (t)icks.</returns>
10066
10067
10068 var canceled = false;
10069 var deferred = extend(new DjsDeferred(), {
10070 cancel: function () {
10071 /// <summary>Aborts the readPage operation.</summary>
10072 canceled = true;
10073 }
10074 });
10075
10076 var error = storeFailureCallback(deferred, "Read page from store failure");
10077
10078 store.contains(key, function (contained) {
10079 if (canceled) {
10080 return;
10081 }
10082 if (contained) {
10083 store.read(key, function (_, data) {
10084 if (!canceled) {
10085 deferred.resolve(data !== undefined, data);
10086 }
10087 }, error);
10088 return;
10089 }
10090 deferred.resolve(false);
10091 }, error);
10092 return deferred;
10093 };
10094
10095 var savePage = function (key, page) {
10096 /// <summary>Saves a page to the cache local store.</summary>
10097 /// <param name="key" type="Number">Zero-based index of the requested page.</param>
10098 /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata, and (t)icks.</param>
10099 /// <returns type="DjsDeferred">A promise with no value.</returns>
10100
10101
10102 var canceled = false;
10103
10104 var deferred = extend(new DjsDeferred(), {
10105 cancel: function () {
10106 /// <summary>Aborts the readPage operation.</summary>
10107 canceled = true;
10108 }
10109 });
10110
10111 var error = storeFailureCallback(deferred, "Save page to store failure");
10112
10113 var resolve = function () {
10114 deferred.resolve(true);
10115 };
10116
10117 if (page.c > 0) {
10118 var pageBytes = estimateSize(page);
10119 overflowed = cacheSize >= 0 && cacheSize < actualCacheSize + pageBytes;
10120
10121 if (!overflowed) {
10122 store.addOrUpdate(key, page, function () {
10123 updateSettings(page, pageBytes);
10124 saveSettings(resolve, error);
10125 }, error);
10126 } else {
10127 resolve();
10128 }
10129 } else {
10130 updateSettings(page, 0);
10131 saveSettings(resolve, error);
10132 }
10133 return deferred;
10134 };
10135
10136 var saveSettings = function (success, error) {
10137 /// <summary>Saves the cache's current settings to the local store.</summary>
10138 /// <param name="success" type="Function">Success callback.</param>
10139 /// <param name="error" type="Function">Errror callback.</param>
10140
10141 var settings = {
10142 actualCacheSize: actualCacheSize,
10143 allDataLocal: allDataLocal,
10144 cacheSize: cacheSize,
10145 collectionCount: collectionCount,
10146 highestSavedPage: highestSavedPage,
10147 highestSavedPageSize: highestSavedPageSize,
10148 pageSize: pageSize,
10149 sourceId: source.identifier,
10150 version: version
10151 };
10152
10153 store.addOrUpdate("__settings", settings, success, error);
10154 };
10155
10156 var storeFailureCallback = function (deferred/*, message*/) {
10157 /// <summary>Creates a function that handles a store error.</summary>
10158 /// <param name="deferred" type="DjsDeferred">Deferred object to resolve.</param>
10159 /// <param name="message" type="String">Message text.</param>
10160 /// <returns type="Function">Function to use as error callback.</returns>
10161 /// <remarks>
10162 /// This function will specifically handle problems when interacting with the store.
10163 /// </remarks>
10164
10165 return function (/*error*/) {
10166 // var console = window.console;
10167 // if (console && console.log) {
10168 // console.log(message);
10169 // console.dir(error);
10170 // }
10171 deferred.resolve(false);
10172 };
10173 };
10174
10175 var updateSettings = function (page, pageBytes) {
10176 /// <summary>Updates the cache's settings based on a page object.</summary>
10177 /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata.</param>
10178 /// <param name="pageBytes" type="Number">Size of the page in bytes.</param>
10179
10180 var pageCount = page.c;
10181 var pageIndex = page.i;
10182
10183 // Detect the collection size.
10184 if (pageCount === 0) {
10185 if (highestSavedPage === pageIndex - pageSize) {
10186 collectionCount = highestSavedPage + highestSavedPageSize;
10187 }
10188 } else {
10189 highestSavedPage = Math.max(highestSavedPage, pageIndex);
10190 if (highestSavedPage === pageIndex) {
10191 highestSavedPageSize = pageCount;
10192 }
10193 actualCacheSize += pageBytes;
10194 if (pageCount < pageSize && !collectionCount) {
10195 collectionCount = pageIndex + pageCount;
10196 }
10197 }
10198
10199 // Detect the end of the collection.
10200 if (!allDataLocal && collectionCount === highestSavedPage + highestSavedPageSize) {
10201 allDataLocal = true;
10202 }
10203 };
10204
10205 var cancelStateMachine = function (operation, opTargetState, cacheState, data) {
10206 /// <summary>State machine describing the behavior for cancelling a read or prefetch operation.</summary>
10207 /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
10208 /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
10209 /// <param name="cacheState" type="Object">Current cache state.</param>
10210 /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
10211 /// <remarks>
10212 /// This state machine contains behavior common to read and prefetch operations.
10213 /// </remarks>
10214
10215 var canceled = operation.canceled && opTargetState !== OPERATION_STATE_END;
10216 if (canceled) {
10217 if (opTargetState === OPERATION_STATE_CANCEL) {
10218 // Cancel state.
10219 // Data is expected to be any pending request made to the cache.
10220 if (data && data.cancel) {
10221 data.cancel();
10222 }
10223 }
10224 }
10225 return canceled;
10226 };
10227
10228 var destroyStateMachine = function (operation, opTargetState, cacheState) {
10229 /// <summary>State machine describing the behavior of a clear operation.</summary>
10230 /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
10231 /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
10232 /// <param name="cacheState" type="Object">Current cache state.</param>
10233 /// <remarks>
10234 /// Clear operations have the highest priority and can't be interrupted by other operations; however,
10235 /// they will preempt any other operation currently executing.
10236 /// </remarks>
10237
10238 var transition = operation.transition;
10239
10240 // Signal the cache that a clear operation is running.
10241 if (cacheState !== CACHE_STATE_DESTROY) {
10242 changeState(CACHE_STATE_DESTROY);
10243 return true;
10244 }
10245
10246 switch (opTargetState) {
10247 case OPERATION_STATE_START:
10248 // Initial state of the operation.
10249 transition(DESTROY_STATE_CLEAR);
10250 break;
10251
10252 case OPERATION_STATE_END:
10253 // State that signals the operation is done.
10254 fireOnIdle();
10255 break;
10256
10257 case DESTROY_STATE_CLEAR:
10258 // State that clears all the local data of the cache.
10259 clearStore().then(function () {
10260 // Terminate the operation once the local store has been cleared.
10261 operation.complete();
10262 });
10263 // Wait until the clear request completes.
10264 operation.wait();
10265 break;
10266
10267 default:
10268 return false;
10269 }
10270 return true;
10271 };
10272
10273 var prefetchStateMachine = function (operation, opTargetState, cacheState, data) {
10274 /// <summary>State machine describing the behavior of a prefetch operation.</summary>
10275 /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
10276 /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
10277 /// <param name="cacheState" type="Object">Current cache state.</param>
10278 /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
10279 /// <remarks>
10280 /// Prefetch operations have the lowest priority and will be interrupted by operations of
10281 /// other kinds. A preempted prefetch operation will resume its execution only when the state
10282 /// of the cache returns to idle.
10283 ///
10284 /// If a clear operation starts executing then all the prefetch operations are canceled,
10285 /// even if they haven't started executing yet.
10286 /// </remarks>
10287
10288 // Handle cancelation
10289 if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
10290
10291 var transition = operation.transition;
10292
10293 // Handle preemption
10294 if (cacheState !== CACHE_STATE_PREFETCH) {
10295 if (cacheState === CACHE_STATE_DESTROY) {
10296 if (opTargetState !== OPERATION_STATE_CANCEL) {
10297 operation.cancel();
10298 }
10299 } else if (cacheState === CACHE_STATE_IDLE) {
10300 // Signal the cache that a prefetch operation is running.
10301 changeState(CACHE_STATE_PREFETCH);
10302 }
10303 return true;
10304 }
10305
10306 switch (opTargetState) {
10307 case OPERATION_STATE_START:
10308 // Initial state of the operation.
10309 if (prefetchOperations[0] === operation) {
10310 transition(READ_STATE_LOCAL, operation.i);
10311 }
10312 break;
10313
10314 case READ_STATE_DONE:
10315 // State that determines if the operation can be resolved or has to
10316 // continue processing.
10317 // Data is expected to be the read page.
10318 var pending = operation.pending;
10319
10320 if (pending > 0) {
10321 pending -= Math.min(pending, data.c);
10322 }
10323
10324 // Are we done, or has all the data been stored?
10325 if (allDataLocal || pending === 0 || data.c < pageSize || overflowed) {
10326 operation.complete();
10327 } else {
10328 // Continue processing the operation.
10329 operation.pending = pending;
10330 transition(READ_STATE_LOCAL, data.i + pageSize);
10331 }
10332 break;
10333
10334 default:
10335 return readSaveStateMachine(operation, opTargetState, cacheState, data, true);
10336 }
10337 }
10338 return true;
10339 };
10340
10341 var readStateMachine = function (operation, opTargetState, cacheState, data) {
10342 /// <summary>State machine describing the behavior of a read operation.</summary>
10343 /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
10344 /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
10345 /// <param name="cacheState" type="Object">Current cache state.</param>
10346 /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
10347 /// <remarks>
10348 /// Read operations have a higher priority than prefetch operations, but lower than
10349 /// clear operations. They will preempt any prefetch operation currently running
10350 /// but will be interrupted by a clear operation.
10351 ///
10352 /// If a clear operation starts executing then all the currently running
10353 /// read operations are canceled. Read operations that haven't started yet will
10354 /// wait in the start state until the destory operation finishes.
10355 /// </remarks>
10356
10357 // Handle cancelation
10358 if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
10359
10360 var transition = operation.transition;
10361
10362 // Handle preemption
10363 if (cacheState !== CACHE_STATE_READ && opTargetState !== OPERATION_STATE_START) {
10364 if (cacheState === CACHE_STATE_DESTROY) {
10365 if (opTargetState !== OPERATION_STATE_START) {
10366 operation.cancel();
10367 }
10368 } else if (cacheState !== CACHE_STATE_WRITE) {
10369 // Signal the cache that a read operation is running.
10370 changeState(CACHE_STATE_READ);
10371 }
10372
10373 return true;
10374 }
10375
10376 switch (opTargetState) {
10377 case OPERATION_STATE_START:
10378 // Initial state of the operation.
10379 // Wait until the cache is idle or prefetching.
10380 if (cacheState === CACHE_STATE_IDLE || cacheState === CACHE_STATE_PREFETCH) {
10381 // Signal the cache that a read operation is running.
10382 changeState(CACHE_STATE_READ);
10383 if (operation.c > 0) {
10384 // Snap the requested range to a page boundary.
10385 var range = snapToPageBoundaries(operation.i, operation.c, pageSize);
10386 transition(READ_STATE_LOCAL, range.i);
10387 } else {
10388 transition(READ_STATE_DONE, operation);
10389 }
10390 }
10391 break;
10392
10393 case READ_STATE_DONE:
10394 // State that determines if the operation can be resolved or has to
10395 // continue processing.
10396 // Data is expected to be the read page.
10397 appendPage(operation, data);
10398 var len = operation.d.length;
10399 // Are we done?
10400 if (operation.c === len || data.c < pageSize) {
10401 // Update the stats, request for a prefetch operation.
10402 stats.cacheReads++;
10403 prefetch(data.i + data.c);
10404 // Terminate the operation.
10405 operation.complete();
10406 } else {
10407 // Continue processing the operation.
10408 transition(READ_STATE_LOCAL, data.i + pageSize);
10409 }
10410 break;
10411
10412 default:
10413 return readSaveStateMachine(operation, opTargetState, cacheState, data, false);
10414 }
10415 }
10416
10417 return true;
10418 };
10419
10420 var readSaveStateMachine = function (operation, opTargetState, cacheState, data, isPrefetch) {
10421 /// <summary>State machine describing the behavior for reading and saving data into the cache.</summary>
10422 /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
10423 /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
10424 /// <param name="cacheState" type="Object">Current cache state.</param>
10425 /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
10426 /// <param name="isPrefetch" type="Boolean">Flag indicating whether a read (false) or prefetch (true) operation is running.
10427 /// <remarks>
10428 /// This state machine contains behavior common to read and prefetch operations.
10429 /// </remarks>
10430
10431 var error = operation.error;
10432 var transition = operation.transition;
10433 var wait = operation.wait;
10434 var request;
10435
10436 switch (opTargetState) {
10437 case OPERATION_STATE_END:
10438 // State that signals the operation is done.
10439 fireOnIdle();
10440 break;
10441
10442 case READ_STATE_LOCAL:
10443 // State that requests for a page from the local store.
10444 // Data is expected to be the index of the page to request.
10445 request = readPage(data).then(function (found, page) {
10446 // Signal the cache that a read operation is running.
10447 if (!operation.canceled) {
10448 if (found) {
10449 // The page is in the local store, check if the operation can be resolved.
10450 transition(READ_STATE_DONE, page);
10451 } else {
10452 // The page is not in the local store, request it from the source.
10453 transition(READ_STATE_SOURCE, data);
10454 }
10455 }
10456 });
10457 break;
10458
10459 case READ_STATE_SOURCE:
10460 // State that requests for a page from the cache source.
10461 // Data is expected to be the index of the page to request.
10462 request = fetchPage(data).then(function (page) {
10463 // Signal the cache that a read operation is running.
10464 if (!operation.canceled) {
10465 // Update the stats and save the page to the local store.
10466 if (isPrefetch) {
10467 stats.prefetches++;
10468 } else {
10469 stats.netReads++;
10470 }
10471 transition(READ_STATE_SAVE, page);
10472 }
10473 }, error);
10474 break;
10475
10476 case READ_STATE_SAVE:
10477 // State that saves a page to the local store.
10478 // Data is expected to be the page to save.
10479 // Write access to the store is exclusive.
10480 if (cacheState !== CACHE_STATE_WRITE) {
10481 changeState(CACHE_STATE_WRITE);
10482 request = savePage(data.i, data).then(function (saved) {
10483 if (!operation.canceled) {
10484 if (!saved && isPrefetch) {
10485 operation.pending = 0;
10486 }
10487 // Check if the operation can be resolved.
10488 transition(READ_STATE_DONE, data);
10489 }
10490 changeState(CACHE_STATE_IDLE);
10491 });
10492 }
10493 break;
10494
10495 default:
10496 // Unknown state that can't be handled by this state machine.
10497 return false;
10498 }
10499
10500 if (request) {
10501 // The operation might have been canceled between stack frames do to the async calls.
10502 if (operation.canceled) {
10503 request.cancel();
10504 } else if (operation.s === opTargetState) {
10505 // Wait for the request to complete.
10506 wait(request);
10507 }
10508 }
10509
10510 return true;
10511 };
10512
10513 // Initialize the cache.
10514 store.read("__settings", function (_, settings) {
10515 if (assigned(settings)) {
10516 var settingsVersion = settings.version;
10517 if (!settingsVersion || settingsVersion.indexOf("1.") !== 0) {
10518 cacheFailureCallback("Unsupported cache store version " + settingsVersion)();
10519 return;
10520 }
10521
10522 if (pageSize !== settings.pageSize || source.identifier !== settings.sourceId) {
10523 // The shape or the source of the data was changed so invalidate the store.
10524 clearStore().then(function () {
10525 // Signal the cache is fully initialized.
10526 changeState(CACHE_STATE_IDLE);
10527 }, cacheFailureCallback("Unable to clear store during initialization"));
10528 } else {
10529 // Restore the saved settings.
10530 actualCacheSize = settings.actualCacheSize;
10531 allDataLocal = settings.allDataLocal;
10532 cacheSize = settings.cacheSize;
10533 collectionCount = settings.collectionCount;
10534 highestSavedPage = settings.highestSavedPage;
10535 highestSavedPageSize = settings.highestSavedPageSize;
10536 version = settingsVersion;
10537
10538 // Signal the cache is fully initialized.
10539 changeState(CACHE_STATE_IDLE);
10540 }
10541 } else {
10542 // This is a brand new cache.
10543 saveSettings(function () {
10544 // Signal the cache is fully initialized.
10545 changeState(CACHE_STATE_IDLE);
10546 }, cacheFailureCallback("Unable to write settings during initialization."));
10547 }
10548 }, cacheFailureCallback("Unable to read settings from store."));
10549
10550 return that;
10551 };
10552
10553 datajs.createDataCache = function (options) {
10554 /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary>
10555 /// <param name="options">
10556 /// Options for the data cache, including name, source, pageSize,
10557 /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
10558 /// </param>
10559 /// <returns type="DataCache">A new data cache instance.</returns>
10560 checkUndefinedGreaterThanZero(options.pageSize, "pageSize");
10561 checkUndefinedOrNumber(options.cacheSize, "cacheSize");
10562 checkUndefinedOrNumber(options.prefetchSize, "prefetchSize");
10563
10564 if (!assigned(options.name)) {
10565 throw { message: "Undefined or null name", options: options };
10566 }
10567
10568 if (!assigned(options.source)) {
10569 throw { message: "Undefined source", options: options };
10570 }
10571
10572 return new DataCache(options);
10573 };
10574
10575
10576
10577})(this);