ts-odatajs-serbay
Version:
The Olingo OData Client for JavaScript (and TypeScript) is a new cross-browser library that enables data-centric web applications by leveraging modern protocols such as JSON and OData and HTML5-enabled browser features. It's designed to be small, fast and
8,141 lines • 292 kB
JavaScript
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function () {
var init = function (exports, module, require) {
// version information
exports.version = { major: 4, minor: 0, build: 6 };
// core stuff, always needed
exports.deferred = require('./lib/deferred.js');
exports.utils = require('./lib/utils.js');
// only needed for xml metadata
exports.xml = require('./lib/xml.js');
// only need in browser case
exports.oData = require('./lib/odata.js');
exports.store = require('./lib/store.js');
exports.cache = require('./lib/cache.js');
};
var datas = {"cache" : function(exports, module, require) {
'use strict';
/** @module cache */
//var odatajs = require('./odatajs/utils.js');
var utils = require('./utils.js');
var deferred = require('./deferred.js');
var storeReq = require('./store.js');
var cacheSource = require('./cache/source.js');
var assigned = utils.assigned;
var delay = utils.delay;
var extend = utils.extend;
var djsassert = utils.djsassert;
var isArray = utils.isArray;
var normalizeURI = utils.normalizeURI;
var parseInt10 = utils.parseInt10;
var undefinedDefault = utils.undefinedDefault;
var createDeferred = deferred.createDeferred;
var DjsDeferred = deferred.DjsDeferred;
var getJsonValueArraryLength = utils.getJsonValueArraryLength;
var sliceJsonValueArray = utils.sliceJsonValueArray;
var concatJsonValueArray = utils.concatJsonValueArray;
/** Appends a page's data to the operation data.
* @param {Object} operation - Operation with (i)ndex, (c)ount and (d)ata.
* @param {Object} page - Page with (i)ndex, (c)ount and (d)ata.
*/
function appendPage(operation, page) {
var intersection = intersectRanges(operation, page);
var start = 0;
var end = 0;
if (intersection) {
start = intersection.i - page.i;
end = start + (operation.c - getJsonValueArraryLength(operation.d));
}
operation.d = concatJsonValueArray(operation.d, sliceJsonValueArray(page.d, start, end));
}
/** Returns the {(i)ndex, (c)ount} range for the intersection of x and y.
* @param {Object} x - Range with (i)ndex and (c)ount members.
* @param {Object} y - Range with (i)ndex and (c)ount members.
* @returns {Object} The intersection (i)ndex and (c)ount; undefined if there is no intersection.
*/
function intersectRanges(x, y) {
var xLast = x.i + x.c;
var yLast = y.i + y.c;
var resultIndex = (x.i > y.i) ? x.i : y.i;
var resultLast = (xLast < yLast) ? xLast : yLast;
var result;
if (resultLast >= resultIndex) {
result = { i: resultIndex, c: resultLast - resultIndex };
}
return result;
}
/** Checks whether val is a defined number with value zero or greater.
* @param {Number} val - Value to check.
* @param {String} name - Parameter name to use in exception.
* @throws Throws an exception if the check fails
*/
function checkZeroGreater(val, name) {
if (val === undefined || typeof val !== "number") {
throw { message: "'" + name + "' must be a number." };
}
if (isNaN(val) || val < 0 || !isFinite(val)) {
throw { message: "'" + name + "' must be greater than or equal to zero." };
}
}
/** Checks whether val is undefined or a number with value greater than zero.
* @param {Number} val - Value to check.
* @param {String} name - Parameter name to use in exception.
* @throws Throws an exception if the check fails
*/
function checkUndefinedGreaterThanZero(val, name) {
if (val !== undefined) {
if (typeof val !== "number") {
throw { message: "'" + name + "' must be a number." };
}
if (isNaN(val) || val <= 0 || !isFinite(val)) {
throw { message: "'" + name + "' must be greater than zero." };
}
}
}
/** Checks whether val is undefined or a number
* @param {Number} val - Value to check.
* @param {String} name - Parameter name to use in exception.
* @throws Throws an exception if the check fails
*/
function checkUndefinedOrNumber(val, name) {
if (val !== undefined && (typeof val !== "number" || isNaN(val) || !isFinite(val))) {
throw { message: "'" + name + "' must be a number." };
}
}
/** Performs a linear search on the specified array and removes the first instance of 'item'.
* @param {Array} arr - Array to search.
* @param {*} item - Item being sought.
* @returns {Boolean} true if the item was removed otherwise false
*/
function removeFromArray(arr, item) {
var i, len;
for (i = 0, len = arr.length; i < len; i++) {
if (arr[i] === item) {
arr.splice(i, 1);
return true;
}
}
return false;
}
/** Estimates the size of an object in bytes.
* Object trees are traversed recursively
* @param {Object} object - Object to determine the size of.
* @returns {Number} Estimated size of the object in bytes.
*/
function estimateSize(object) {
var size = 0;
var type = typeof object;
if (type === "object" && object) {
for (var name in object) {
size += name.length * 2 + estimateSize(object[name]);
}
} else if (type === "string") {
size = object.length * 2;
} else {
size = 8;
}
return size;
}
/** Snaps low and high indices into page sizes and returns a range.
* @param {Number} lowIndex - Low index to snap to a lower value.
* @param {Number} highIndex - High index to snap to a higher value.
* @param {Number} pageSize - Page size to snap to.
* @returns {Object} A range with (i)ndex and (c)ount of elements.
*/
function snapToPageBoundaries(lowIndex, highIndex, pageSize) {
lowIndex = Math.floor(lowIndex / pageSize) * pageSize;
highIndex = Math.ceil((highIndex + 1) / pageSize) * pageSize;
return { i: lowIndex, c: highIndex - lowIndex };
}
// The DataCache is implemented using state machines. The following constants are used to properly
// identify and label the states that these machines transition to.
var CACHE_STATE_DESTROY = "destroy";
var CACHE_STATE_IDLE = "idle";
var CACHE_STATE_INIT = "init";
var CACHE_STATE_READ = "read";
var CACHE_STATE_PREFETCH = "prefetch";
var CACHE_STATE_WRITE = "write";
// DataCacheOperation state machine states.
// Transitions on operations also depend on the cache current of the cache.
var OPERATION_STATE_CANCEL = "cancel";
var OPERATION_STATE_END = "end";
var OPERATION_STATE_ERROR = "error";
var OPERATION_STATE_START = "start";
var OPERATION_STATE_WAIT = "wait";
// Destroy state machine states
var DESTROY_STATE_CLEAR = "clear";
// Read / Prefetch state machine states
var READ_STATE_DONE = "done";
var READ_STATE_LOCAL = "local";
var READ_STATE_SAVE = "save";
var READ_STATE_SOURCE = "source";
/** Creates a new operation object.
* @class DataCacheOperation
* @param {Function} stateMachine - State machine that describes the specific behavior of the operation.
* @param {DjsDeferred} promise - Promise for requested values.
* @param {Boolean} isCancelable - Whether this operation can be canceled or not.
* @param {Number} index - Index of first item requested.
* @param {Number} count - Count of items requested.
* @param {Array} data - Array with the items requested by the operation.
* @param {Number} pending - Total number of pending prefetch records.
* @returns {DataCacheOperation} A new data cache operation instance.
*/
function DataCacheOperation(stateMachine, promise, isCancelable, index, count, data, pending) {
var stateData;
var cacheState;
var that = this;
that.p = promise;
that.i = index;
that.c = count;
that.d = data;
that.s = OPERATION_STATE_START;
that.canceled = false;
that.pending = pending;
that.oncomplete = null;
/** Transitions this operation to the cancel state and sets the canceled flag to true.
* The function is a no-op if the operation is non-cancelable.
* @method DataCacheOperation#cancel
*/
that.cancel = function cancel() {
if (!isCancelable) {
return;
}
var state = that.s;
if (state !== OPERATION_STATE_ERROR && state !== OPERATION_STATE_END && state !== OPERATION_STATE_CANCEL) {
that.canceled = true;
that.transition(OPERATION_STATE_CANCEL, stateData);
}
};
/** Transitions this operation to the end state.
* @method DataCacheOperation#complete
*/
that.complete = function () {
djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.complete() - operation is in the end state", that);
that.transition(OPERATION_STATE_END, stateData);
};
/** Transitions this operation to the error state.
* @method DataCacheOperation#error
*/
that.error = function (err) {
if (!that.canceled) {
djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.error() - operation is in the end state", that);
djsassert(that.s !== OPERATION_STATE_ERROR, "DataCacheOperation.error() - operation is in the error state", that);
that.transition(OPERATION_STATE_ERROR, err);
}
};
/** Executes the operation's current state in the context of a new cache state.
* @method DataCacheOperation#run
* @param {Object} state - New cache state.
*/
that.run = function (state) {
cacheState = state;
that.transition(that.s, stateData);
};
/** Transitions this operation to the wait state.
* @method DataCacheOperation#wait
*/
that.wait = function (data) {
djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.wait() - operation is in the end state", that);
that.transition(OPERATION_STATE_WAIT, data);
};
/** State machine that describes all operations common behavior.
* @method DataCacheOperation#operationStateMachine
* @param {Object} opTargetState - Operation state to transition to.
* @param {Object} cacheState - Current cache state.
* @param {Object} [data] - Additional data passed to the state.
*/
var operationStateMachine = function (opTargetState, cacheState, data) {
switch (opTargetState) {
case OPERATION_STATE_START:
// Initial state of the operation. The operation will remain in this state until the cache has been fully initialized.
if (cacheState !== CACHE_STATE_INIT) {
stateMachine(that, opTargetState, cacheState, data);
}
break;
case OPERATION_STATE_WAIT:
// Wait state indicating that the operation is active but waiting for an asynchronous operation to complete.
stateMachine(that, opTargetState, cacheState, data);
break;
case OPERATION_STATE_CANCEL:
// Cancel state.
stateMachine(that, opTargetState, cacheState, data);
that.fireCanceled();
that.transition(OPERATION_STATE_END);
break;
case OPERATION_STATE_ERROR:
// Error state. Data is expected to be an object detailing the error condition.
stateMachine(that, opTargetState, cacheState, data);
that.canceled = true;
that.fireRejected(data);
that.transition(OPERATION_STATE_END);
break;
case OPERATION_STATE_END:
// Final state of the operation.
if (that.oncomplete) {
that.oncomplete(that);
}
if (!that.canceled) {
that.fireResolved();
}
stateMachine(that, opTargetState, cacheState, data);
break;
default:
// Any other state is passed down to the state machine describing the operation's specific behavior.
if (true) {
// Check that the state machine actually handled the sate.
var handled = stateMachine(that, opTargetState, cacheState, data);
djsassert(handled, "Bad operation state: " + opTargetState + " cacheState: " + cacheState, this);
} else {
stateMachine(that, opTargetState, cacheState, data);
}
break;
}
};
/** Transitions this operation to a new state.
* @method DataCacheOperation#transition
* @param {Object} state - State to transition the operation to.
* @param {Object} [data] -
*/
that.transition = function (state, data) {
that.s = state;
stateData = data;
operationStateMachine(state, cacheState, data);
};
return that;
}
/** Fires a resolved notification as necessary.
* @method DataCacheOperation#fireResolved
*/
DataCacheOperation.prototype.fireResolved = function () {
// Fire the resolve just once.
var p = this.p;
if (p) {
this.p = null;
p.resolve(this.d);
}
};
/** Fires a rejected notification as necessary.
* @method DataCacheOperation#fireRejected
*/
DataCacheOperation.prototype.fireRejected = function (reason) {
// Fire the rejection just once.
var p = this.p;
if (p) {
this.p = null;
p.reject(reason);
}
};
/** Fires a canceled notification as necessary.
* @method DataCacheOperation#fireCanceled
*/
DataCacheOperation.prototype.fireCanceled = function () {
this.fireRejected({ canceled: true, message: "Operation canceled" });
};
/** Creates a data cache for a collection that is efficiently loaded on-demand.
* @class DataCache
* @param options - Options for the data cache, including name, source, pageSize,
* prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
* @returns {DataCache} A new data cache instance.
*/
function DataCache(options) {
var state = CACHE_STATE_INIT;
var stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
var clearOperations = [];
var readOperations = [];
var prefetchOperations = [];
var actualCacheSize = 0; // Actual cache size in bytes.
var allDataLocal = false; // Whether all data is local.
var cacheSize = undefinedDefault(options.cacheSize, 1048576); // Requested cache size in bytes, default 1 MB.
var collectionCount = 0; // Number of elements in the server collection.
var highestSavedPage = 0; // Highest index of all the saved pages.
var highestSavedPageSize = 0; // Item count of the saved page with the highest index.
var overflowed = cacheSize === 0; // If the cache has overflowed (actualCacheSize > cacheSize or cacheSize == 0);
var pageSize = undefinedDefault(options.pageSize, 50); // Number of elements to store per page.
var prefetchSize = undefinedDefault(options.prefetchSize, pageSize); // Number of elements to prefetch from the source when the cache is idling.
var version = "1.0";
var cacheFailure;
var pendingOperations = 0;
var source = options.source;
if (typeof source === "string") {
// Create a new cache source.
source = new cacheSource.ODataCacheSource(options);
}
source.options = options;
// Create a cache local store.
var store = storeReq.createStore(options.name, options.mechanism);
var that = this;
that.onidle = options.idle;
that.stats = stats;
/** Counts the number of items in the collection.
* @method DataCache#count
* @returns {Object} A promise with the number of items.
*/
that.count = function () {
if (cacheFailure) {
throw cacheFailure;
}
var deferred = createDeferred();
var canceled = false;
if (allDataLocal) {
delay(function () {
deferred.resolve(collectionCount);
});
return deferred.promise();
}
// TODO: Consider returning the local data count instead once allDataLocal flag is set to true.
var request = source.count(function (count) {
request = null;
stats.counts++;
deferred.resolve(count);
}, function (err) {
request = null;
deferred.reject(extend(err, { canceled: canceled }));
});
return extend(deferred.promise(), {
/** Aborts the count operation (used within promise callback)
* @method DataCache#cancelCount
*/
cancel: function () {
if (request) {
canceled = true;
request.abort();
request = null;
}
}
});
};
/** Cancels all running operations and clears all local data associated with this cache.
* New read requests made while a clear operation is in progress will not be canceled.
* Instead they will be queued for execution once the operation is completed.
* @method DataCache#clear
* @returns {Object} A promise that has no value and can't be canceled.
*/
that.clear = function () {
if (cacheFailure) {
throw cacheFailure;
}
if (clearOperations.length === 0) {
var deferred = createDeferred();
var op = new DataCacheOperation(destroyStateMachine, deferred, false);
queueAndStart(op, clearOperations);
return deferred.promise();
}
return clearOperations[0].p;
};
/** Filters the cache data based a predicate.
* Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
* @method DataCache#filterForward
* @param {Number} index - The index of the item to start filtering forward from.
* @param {Number} count - Maximum number of items to include in the result.
* @param {Function} predicate - Callback function returning a boolean that determines whether an item should be included in the result or not.
* @returns {DjsDeferred} A promise for an array of results.
*/
that.filterForward = function (index, count, predicate) {
return filter(index, count, predicate, false);
};
/** Filters the cache data based a predicate.
* Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
* @method DataCache#filterBack
* @param {Number} index - The index of the item to start filtering backward from.
* @param {Number} count - Maximum number of items to include in the result.
* @param {Function} predicate - Callback function returning a boolean that determines whether an item should be included in the result or not.
* @returns {DjsDeferred} A promise for an array of results.
*/
that.filterBack = function (index, count, predicate) {
return filter(index, count, predicate, true);
};
/** Reads a range of adjacent records.
* New read requests made while a clear operation is in progress will not be canceled.
* Instead they will be queued for execution once the operation is completed.
* @method DataCache#readRange
* @param {Number} index - Zero-based index of record range to read.
* @param {Number} count - Number of records in the range.
* @returns {DjsDeferred} A promise for an array of records; less records may be returned if the
* end of the collection is found.
*/
that.readRange = function (index, count) {
checkZeroGreater(index, "index");
checkZeroGreater(count, "count");
if (cacheFailure) {
throw cacheFailure;
}
var deferred = createDeferred();
// Merging read operations would be a nice optimization here.
var op = new DataCacheOperation(readStateMachine, deferred, true, index, count, {}, 0);
queueAndStart(op, readOperations);
return extend(deferred.promise(), {
cancel: function () {
/** Aborts the readRange operation (used within promise callback)
* @method DataCache#cancelReadRange
*/
op.cancel();
}
});
};
/** Creates an Observable object that enumerates all the cache contents.
* @method DataCache#toObservable
* @returns A new Observable object that enumerates all the cache contents.
*/
that.ToObservable = that.toObservable = function () {
if ( !utils.inBrowser()) {
throw { message: "Only in broser supported" };
}
if (!window.Rx || !window.Rx.Observable) {
throw { message: "Rx library not available - include rx.js" };
}
if (cacheFailure) {
throw cacheFailure;
}
//return window.Rx.Observable.create(function (obs) {
return new window.Rx.Observable(function (obs) {
var disposed = false;
var index = 0;
var errorCallback = function (error) {
if (!disposed) {
obs.onError(error);
}
};
var successCallback = function (data) {
if (!disposed) {
var i, len;
for (i = 0, len = data.value.length; i < len; i++) {
// The wrapper automatically checks for Dispose
// on the observer, so we don't need to check it here.
//obs.next(data.value[i]);
obs.onNext(data.value[i]);
}
if (data.value.length < pageSize) {
//obs.completed();
obs.onCompleted();
} else {
index += pageSize;
that.readRange(index, pageSize).then(successCallback, errorCallback);
}
}
};
that.readRange(index, pageSize).then(successCallback, errorCallback);
return { Dispose: function () {
obs.dispose(); // otherwise the check isStopped obs.onNext(data.value[i]);
disposed = true;
} };
});
};
/** Creates a function that handles a callback by setting the cache into failure mode.
* @method DataCache~cacheFailureCallback
* @param {String} message - Message text.
* @returns {Function} Function to use as error callback.
* This function will specifically handle problems with critical store resources
* during cache initialization.
*/
var cacheFailureCallback = function (message) {
return function (error) {
cacheFailure = { message: message, error: error };
// Destroy any pending clear or read operations.
// At this point there should be no prefetch operations.
// Count operations will go through but are benign because they
// won't interact with the store.
djsassert(prefetchOperations.length === 0, "prefetchOperations.length === 0");
var i, len;
for (i = 0, len = readOperations.length; i < len; i++) {
readOperations[i].fireRejected(cacheFailure);
}
for (i = 0, len = clearOperations.length; i < len; i++) {
clearOperations[i].fireRejected(cacheFailure);
}
// Null out the operation arrays.
readOperations = clearOperations = null;
};
};
/** Updates the cache's state and signals all pending operations of the change.
* @method DataCache~changeState
* @param {Object} newState - New cache state.
* This method is a no-op if the cache's current state and the new state are the same.
*/
var changeState = function (newState) {
if (newState !== state) {
state = newState;
var operations = clearOperations.concat(readOperations, prefetchOperations);
var i, len;
for (i = 0, len = operations.length; i < len; i++) {
operations[i].run(state);
}
}
};
/** Removes all the data stored in the cache.
* @method DataCache~clearStore
* @returns {DjsDeferred} A promise with no value.
*/
var clearStore = function () {
djsassert(state === CACHE_STATE_DESTROY || state === CACHE_STATE_INIT, "DataCache.clearStore() - cache is not on the destroy or initialize state, current sate = " + state);
var deferred = new DjsDeferred();
store.clear(function () {
// Reset the cache settings.
actualCacheSize = 0;
allDataLocal = false;
collectionCount = 0;
highestSavedPage = 0;
highestSavedPageSize = 0;
overflowed = cacheSize === 0;
// version is not reset, in case there is other state in eg V1.1 that is still around.
// Reset the cache stats.
stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
that.stats = stats;
store.close();
deferred.resolve();
}, function (err) {
deferred.reject(err);
});
return deferred;
};
/** Removes an operation from the caches queues and changes the cache state to idle.
* @method DataCache~dequeueOperation
* @param {DataCacheOperation} operation - Operation to dequeue.
* This method is used as a handler for the operation's oncomplete event.
*/
var dequeueOperation = function (operation) {
var removed = removeFromArray(clearOperations, operation);
if (!removed) {
removed = removeFromArray(readOperations, operation);
if (!removed) {
removeFromArray(prefetchOperations, operation);
}
}
pendingOperations--;
changeState(CACHE_STATE_IDLE);
};
/** Requests data from the cache source.
* @method DataCache~fetchPage
* @param {Number} start - Zero-based index of items to request.
* @returns {DjsDeferred} A promise for a page object with (i)ndex, (c)ount, (d)ata.
*/
var fetchPage = function (start) {
djsassert(state !== CACHE_STATE_DESTROY, "DataCache.fetchPage() - cache is on the destroy state");
djsassert(state !== CACHE_STATE_IDLE, "DataCache.fetchPage() - cache is on the idle state");
var deferred = new DjsDeferred();
var canceled = false;
var request = source.read(start, pageSize, function (data) {
var length = getJsonValueArraryLength(data);
var page = { i: start, c: length, d: data };
deferred.resolve(page);
}, function (err) {
deferred.reject(err);
});
return extend(deferred, {
cancel: function () {
if (request) {
request.abort();
canceled = true;
request = null;
}
}
});
};
/** Filters the cache data based a predicate.
* @method DataCache~filter
* @param {Number} index - The index of the item to start filtering from.
* @param {Number} count - Maximum number of items to include in the result.
* @param {Function} predicate - Callback function returning a boolean that determines whether an item should be included in the result or not.
* @param {Boolean} backwards - True if the filtering should move backward from the specified index, falsey otherwise.
* Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
* @returns {DjsDeferred} A promise for an array of results.
*/
var filter = function (index, count, predicate, backwards) {
index = parseInt10(index);
count = parseInt10(count);
if (isNaN(index)) {
throw { message: "'index' must be a valid number.", index: index };
}
if (isNaN(count)) {
throw { message: "'count' must be a valid number.", count: count };
}
if (cacheFailure) {
throw cacheFailure;
}
index = Math.max(index, 0);
var deferred = createDeferred();
var returnData = {};
returnData.value = [];
var canceled = false;
var pendingReadRange = null;
var readMore = function (readIndex, readCount) {
if (!canceled) {
if (count > 0 && returnData.value.length >= count) {
deferred.resolve(returnData);
} else {
pendingReadRange = that.readRange(readIndex, readCount).then(function (data) {
if (data["@odata.context"] && !returnData["@odata.context"]) {
returnData["@odata.context"] = data["@odata.context"];
}
for (var i = 0, length = data.value.length; i < length && (count < 0 || returnData.value.length < count); i++) {
var dataIndex = backwards ? length - i - 1 : i;
var item = data.value[dataIndex];
if (predicate(item)) {
var element = {
index: readIndex + dataIndex,
item: item
};
backwards ? returnData.value.unshift(element) : returnData.value.push(element);
}
}
// Have we reached the end of the collection?
if ((!backwards && data.value.length < readCount) || (backwards && readIndex <= 0)) {
deferred.resolve(returnData);
} else {
var nextIndex = backwards ? Math.max(readIndex - pageSize, 0) : readIndex + readCount;
readMore(nextIndex, pageSize);
}
}, function (err) {
deferred.reject(err);
});
}
}
};
// Initially, we read from the given starting index to the next/previous page boundary
var initialPage = snapToPageBoundaries(index, index, pageSize);
var initialIndex = backwards ? initialPage.i : index;
var initialCount = backwards ? index - initialPage.i + 1 : initialPage.i + initialPage.c - index;
readMore(initialIndex, initialCount);
return extend(deferred.promise(), {
/** Aborts the filter operation (used within promise callback)
* @method DataCache#cancelFilter
*/
cancel: function () {
if (pendingReadRange) {
pendingReadRange.cancel();
}
canceled = true;
}
});
};
/** Fires an onidle event if any functions are assigned.
* @method DataCache~fireOnIdle
*/
var fireOnIdle = function () {
if (that.onidle && pendingOperations === 0) {
that.onidle();
}
};
/** Creates and starts a new prefetch operation.
* @method DataCache~prefetch
* @param {Number} start - Zero-based index of the items to prefetch.
* This method is a no-op if any of the following conditions is true:
* 1.- prefetchSize is 0
* 2.- All data has been read and stored locally in the cache.
* 3.- There is already an all data prefetch operation queued.
* 4.- The cache has run out of available space (overflowed).
*/
var prefetch = function (start) {
if (allDataLocal || prefetchSize === 0 || overflowed) {
return;
}
djsassert(state === CACHE_STATE_READ, "DataCache.prefetch() - cache is not on the read state, current state: " + state);
if (prefetchOperations.length === 0 || (prefetchOperations[0] && prefetchOperations[0].c !== -1)) {
// Merging prefetch operations would be a nice optimization here.
var op = new DataCacheOperation(prefetchStateMachine, null, true, start, prefetchSize, null, prefetchSize);
queueAndStart(op, prefetchOperations);
}
};
/** Queues an operation and runs it.
* @param {DataCacheOperation} op - Operation to queue.
* @param {Array} queue - Array that will store the operation.
*/
var queueAndStart = function (op, queue) {
op.oncomplete = dequeueOperation;
queue.push(op);
pendingOperations++;
op.run(state);
};
/** Requests a page from the cache local store.
* @method DataCache~readPage
* @param {Number} key - Zero-based index of the reuqested page.
* @returns {DjsDeferred} A promise for a found flag and page object with (i)ndex, (c)ount, (d)ata, and (t)icks.
*/
var readPage = function (key) {
djsassert(state !== CACHE_STATE_DESTROY, "DataCache.readPage() - cache is on the destroy state");
var canceled = false;
var deferred = extend(new DjsDeferred(), {
/** Aborts the readPage operation. (used within promise callback)
* @method DataCache#cancelReadPage
*/
cancel: function () {
canceled = true;
}
});
var error = storeFailureCallback(deferred, "Read page from store failure");
store.contains(key, function (contained) {
if (canceled) {
return;
}
if (contained) {
store.read(key, function (_, data) {
if (!canceled) {
deferred.resolve(data !== undefined, data);
}
}, error);
return;
}
deferred.resolve(false);
}, error);
return deferred;
};
/** Saves a page to the cache local store.
* @method DataCache~savePage
* @param {Number} key - Zero-based index of the requested page.
* @param {Object} page - Object with (i)ndex, (c)ount, (d)ata, and (t)icks.
* @returns {DjsDeferred} A promise with no value.
*/
var savePage = function (key, page) {
djsassert(state !== CACHE_STATE_DESTROY, "DataCache.savePage() - cache is on the destroy state");
djsassert(state !== CACHE_STATE_IDLE, "DataCache.savePage() - cache is on the idle state");
var canceled = false;
var deferred = extend(new DjsDeferred(), {
/** Aborts the savePage operation. (used within promise callback)
* @method DataCache#cancelReadPage
*/
cancel: function () {
canceled = true;
}
});
var error = storeFailureCallback(deferred, "Save page to store failure");
var resolve = function () {
deferred.resolve(true);
};
if (page.c > 0) {
var pageBytes = estimateSize(page);
overflowed = cacheSize >= 0 && cacheSize < actualCacheSize + pageBytes;
if (!overflowed) {
store.addOrUpdate(key, page, function () {
updateSettings(page, pageBytes);
saveSettings(resolve, error);
}, error);
} else {
resolve();
}
} else {
updateSettings(page, 0);
saveSettings(resolve, error);
}
return deferred;
};
/** Saves the cache's current settings to the local store.
* @method DataCache~saveSettings
* @param {Function} success - Success callback.
* @param {Function} error - Errror callback.
*/
var saveSettings = function (success, error) {
var settings = {
actualCacheSize: actualCacheSize,
allDataLocal: allDataLocal,
cacheSize: cacheSize,
collectionCount: collectionCount,
highestSavedPage: highestSavedPage,
highestSavedPageSize: highestSavedPageSize,
pageSize: pageSize,
sourceId: source.identifier,
version: version
};
store.addOrUpdate("__settings", settings, success, error);
};
/** Creates a function that handles a store error.
* @method DataCache~storeFailureCallback
* @param {DjsDeferred} deferred - Deferred object to resolve.
* @returns {Function} Function to use as error callback.
* This function will specifically handle problems when interacting with the store.
*/
var storeFailureCallback = function (deferred/*, message*/) {
return function (/*error*/) {
// var console = windo1w.console;
// if (console && console.log) {
// console.log(message);
// console.dir(error);
// }
deferred.resolve(false);
};
};
/** Updates the cache's settings based on a page object.
* @method DataCache~updateSettings
* @param {Object} page - Object with (i)ndex, (c)ount, (d)ata.
* @param {Number} pageBytes - Size of the page in bytes.
*/
var updateSettings = function (page, pageBytes) {
var pageCount = page.c;
var pageIndex = page.i;
// Detect the collection size.
if (pageCount === 0) {
if (highestSavedPage === pageIndex - pageSize) {
collectionCount = highestSavedPage + highestSavedPageSize;
}
} else {
highestSavedPage = Math.max(highestSavedPage, pageIndex);
if (highestSavedPage === pageIndex) {
highestSavedPageSize = pageCount;
}
actualCacheSize += pageBytes;
if (pageCount < pageSize && !collectionCount) {
collectionCount = pageIndex + pageCount;
}
}
// Detect the end of the collection.
if (!allDataLocal && collectionCount === highestSavedPage + highestSavedPageSize) {
allDataLocal = true;
}
};
/** State machine describing the behavior for cancelling a read or prefetch operation.
* @method DataCache~cancelStateMachine
* @param {DataCacheOperation} operation - Operation being run.
* @param {Object} opTargetState - Operation state to transition to.
* @param {Object} cacheState - Current cache state.
* @param {Object} [data] -
* This state machine contains behavior common to read and prefetch operations.
*/
var cancelStateMachine = function (operation, opTargetState, cacheState, data) {
var canceled = operation.canceled && opTargetState !== OPERATION_STATE_END;
if (canceled) {
if (opTargetState === OPERATION_STATE_CANCEL) {
// Cancel state.
// Data is expected to be any pending request made to the cache.
if (data && data.cancel) {
data.cancel();
}
}
}
return canceled;
};
/** State machine describing the behavior of a clear operation.
* @method DataCache~destroyStateMachine
* @param {DataCacheOperation} operation - Operation being run.
* @param {Object} opTargetState - Operation state to transition to.
* @param {Object} cacheState - Current cache state.
* Clear operations have the highest priority and can't be interrupted by other operations; however,
* they will preempt any other operation currently executing.
*/
var destroyStateMachine = function (operation, opTargetState, cacheState) {
var transition = operation.transition;
// Signal the cache that a clear operation is running.
if (cacheState !== CACHE_STATE_DESTROY) {
changeState(CACHE_STATE_DESTROY);
return true;
}
switch (opTargetState) {
case OPERATION_STATE_START:
// Initial state of the operation.
transition(DESTROY_STATE_CLEAR);
break;
case OPERATION_STATE_END:
// State that signals the operation is done.
fireOnIdle();
break;
case DESTROY_STATE_CLEAR:
// State that clears all the local data of the cache.
clearStore().then(function () {
// Terminate the operation once the local store has been cleared.
operation.complete();
});
// Wait until the clear request completes.
operation.wait();
break;
default:
return false;
}
return true;
};
/** State machine describing the behavior of a prefetch operation.
* @method DataCache~prefetchStateMachine
* @param {DataCacheOperation} operation - Operation being run.
* @param {Object} opTargetState - Operation state to transition to.
* @param {Object} cacheState - Current cache state.
* @param {Object} [data] -
* Prefetch operations have the lowest priority and will be interrupted by operations of
* other kinds. A preempted prefetch operation will resume its execution only when the state
* of the cache returns to idle.
*
* If a clear operation starts executing then all the prefetch operations are canceled,
* even if they haven't started executing yet.
*/
var prefetchStateMachine = function (operation, opTargetState, cacheState, data) {
// Handle cancelation
if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
var transition = operation.transition;
// Handle preemption
if (cacheState !== CACHE_STATE_PREFETCH) {
if (cacheState === CACHE_STATE_DESTROY) {
if (opTargetState !== OPERATION_STATE_CANCEL) {
operation.cancel();
}
} else if (cacheState === CACHE_STATE_IDLE) {
// Signal the cache that a prefetch operation is running.
changeState(CACHE_STATE_PREFETCH);
}
return true;
}
switch (opTargetState) {
case OPERATION_STATE_START:
// Initial state of the operation.
if (prefetchOperations[0] === operation) {
transition(READ_STATE_LOCAL, operation.i);
}
break;
case READ_STATE_DONE:
// State that determines if the operation can be resolved or has to
// continue processing.
// Data is expected to be the read page.
var pending = operation.pending;
if (pending > 0) {
pending -= Math.min(pending, data.c);
}
// Are we done, or has all the data been stored?
if (allDataLocal || pending === 0 || data.c < pageSize || overflowed) {
operation.complete();
} else {
// Continue processing the operation.
operation.pending = pending;
transition(READ_STATE_LOCAL, data.i + pageSize);
}
break;
default:
return readSaveStateMachine(operation, opTargetState, cacheState, data, true);
}
}
return true;
};
/** State machine describing the behavior of a read operation.
* @method DataCache~readStateMachine
* @param {DataCacheOperation} operation - Operation being run.
* @param {Object} opTargetState - Operation state to transition to.
* @param {Object} cacheState - Current cache state.
* @param {Object} [data] -
* Read operations have a higher priority than prefetch operations, but lower than
* clear operations. They will preempt any prefetch operation currently running
* but will be interrupted by a clear operation.
*
* If a clear operation starts executing then all the currently running
* read operations are canceled. Read operations that haven't started yet will
* wait in the start state until the destory operation finishes.
*/
var readStateMachine = function (operation, opTargetState, cacheState, data) {
// Handle cancelation
if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
var transition = operation.transition;
// Handle preemption
if (cacheState !== CACHE_STATE_READ && opTargetState !== OPERATION_STATE_START) {
if (cacheState === CACHE_STATE_DESTROY) {
if (opTargetState !== OPERATION_STATE_START) {
operation.cancel();
}
} else if (cacheState !== CACHE_STATE_WRITE) {
// Signal the cache that a read operation is running.
djsassert(state == CACHE_STATE_IDLE || state === CACHE_STATE_PREFETCH, "DataCache.readStateMachine() - cache is not on the read or idle state.");
changeState(CACHE_STATE_READ);
}
return true;
}
switch (opTargetState) {
case OPERATION_STATE_START:
// Initial state of the operation.
// Wait until the cache is idle or prefetching.
if (cacheState === CACHE_STATE_IDLE || cacheState === CACHE_STATE_PREFETCH) {
// Signal the cache that a read operation is running.
changeState(CACHE_STATE_READ);
if (operation.c >= 0) {
// Snap the requested range to a page boundary.
var range = snapToPageBoundaries(operation.i, operation.c, pageSize);
transition(READ_STATE_LOCAL, range.i);
} else {
transition(READ_STATE_DONE, operation);
}
}
break;
case READ_STATE_DONE:
// State that determines if the operation can be resolved or has to
// continue processing.
// Data is expected to be the read page.
appendPage(operation, data);
var len = getJsonValueArraryLength(operation.d);
// Are we done?
if (operation.c === len || data.c < pageSize) {
// Update the stats, request for a prefetch operation.
stats.cacheReads++;
prefetch(data.i + data.c);
// Terminate the operation.
operation.complete();
} else {
// Continue processing the operation.
transition(READ_STATE_LOCAL, data.i + pageSize);
}
break;
default:
return readSaveStateMachine(operation, opTargetState, cacheState, data, false);
}
}
return true;
};
/** State machine describing the behavior for reading and saving data into the cache.
* @method DataCache~readSaveStateMachine
* @param {DataCacheOperation} operation - Operation being run.
* @param {Object} opTargetState - Operation state to transition to.
* @param {Object} cacheState - Current cache state.
* @param {Object} [data] -
* @param {Boolean} isPrefetch - Flag indicating whether a read (false) or prefetch (true) operation is running.
* This state machine contains behavior common to read and prefetch operations.
*/
var readSaveStateMachine = function (operation, opTargetState, cacheState, data, isPrefetch) {
var error = operation.error;
var transition = operation.transition;
var wait = operation.wait;
var request;
switch (opTargetState) {
case OPERATION_STATE_END:
// State that signals the operation is done.
fireOnIdle();
break;
case READ_STATE_LOCAL:
// State that requests for a page from the local store.
// Data is expected to be the index of the page to request.
request = readPage(data).then(function (found, page) {
// Signal the cache that a read operation is running.
if (!operation.canceled) {
if (found) {
// The page is in the local store, check if the operation can be resolved.
transition(READ_STATE_DONE, page);
} else {
// The page is not in the local store, request it from the source.
transition(READ_STATE_SOURCE, data);
}
}
});
break;
case READ_STATE_SOURCE:
// State that requests for a page from the cache source.
// Data is expected to be the index of the page to request.
request = fetchPage(data).then(function (page) {
// Signal the cache that a read operation is running.
if (!operation.canceled) {
// Update the stats and save the page to the local store.
if (isPrefetch) {
stats.prefetches++;
} else {
stats.netReads++;
}
transition(READ_STATE_SAVE, page);
}
}, error);
break;
case READ_STATE_SAVE:
// State that saves a page to the local store.
// Data is expected to be the page to save.
// Write access to the store is exclusive.
if (cacheState !== CACHE_STATE_WRITE) {
changeState(CACHE_STATE_WRITE);
request = savePage(data.i, data).then(function (saved) {
if (!operation.canceled) {
if (!saved && isPrefetch) {
operation.pending = 0;
}
// Check if the operation can be resolved.
transition(READ_STATE_DONE, data);
}
changeState(CACHE_STATE_IDLE);
});
}
break;
default:
// Unknown state that can't be handled by this state machine.
return false;
}
if (request) {
// The operation might have been canceled between stack frames do to the async calls.
if (operation.canceled) {
request.cancel();
} else if (operation.s === opTargetState) {
// Wait for the request to complete.
wait(request);
}
}
return true;
};
// Initialize the cache.
store.read("__settings", function (_, settings) {
if (assigned(settings)) {
var settingsVersion = settings.version;
if (!settingsVersion || settingsVersion.indexOf("1.") !== 0) {
cacheFailureCallback("Unsupported cache store version " + settingsVersion)();
return;
}
if (pageSize !== settings.pageSize || source.identifier !== settings.sourceId) {
// The shape or the source of the data was changed so invalidate the store.
clearStore().then(function () {
// Signal the cache is fully initialized.
changeState(CACHE_STATE_IDLE);
}, cacheFailureCallback("Unable to clear store during initialization"));
} else {
// Restore the saved settings.
actualCacheSize = settings.actualCacheSize;
allDataLocal = settings.allDataLocal;
cacheSize = settings.cacheSize;
collectionCount = settings.collectionCount;
highestSavedPage = settings.highestSavedPage;
highestSavedPageSize = settings.highestSavedPageSize;
version = settingsVersion;
// Signal the cache is fully initialized.
changeState(CACHE_STATE_IDLE);
}
} else {
// This is a brand new cache.
saveSettings(function () {
// Signal the cache is fully initialized.
changeState(CACHE_STATE_IDLE);
}, cacheFailureCallback("Unable to write settings during initialization."));
}
}, cacheFailureCallback("Unable to read settings from store."));
return that;
}
/** Creates a data cache for a collection that is efficiently loaded on-demand.
* @param options
* Options for the data cache, including name, source, pageSize, TODO check doku
* prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
* @returns {DataCache} A new data cache instance.
*/
function createDataCache (options) {
checkUndefinedGreaterThanZero(options.pageSize, "pageSize");
checkUndefinedOrNumber(options.cacheSize, "cacheSize");
checkUndefinedOrNumber(options.prefetchSize, "prefetchSize");
if (!assigned(options.name)) {
throw { message: "Undefined or null name", options: options };
}
if (!assigned(options.source)) {
throw { message: "Undefined source", options: options };
}
return new DataCache(options);
}
/** estimateSize (see {@link estimateSize}) */
exports.estimateSize = estimateSize;
/** createDataCache */
exports.createDataCache = createDataCache;
}, "source" : function(exports, module, require) {
'use strict';
/** @module cache/source */
var utils = require("./../utils.js");
var odataRequest = require("./../odata.js");
var parseInt10 = utils.parseInt10;
var normalizeURICase = utils.normalizeURICase;
/** Appends the specified escaped query option to the specified URI.
* @param {String} uri - URI to append option to.
* @param {String} queryOption - Escaped query option to append.
*/
function appendQueryOption(uri, queryOption) {
var separator = (uri.indexOf("?") >= 0) ? "&" : "?";
return uri + separator + queryOption;
}
/** Appends the specified segment to the given URI.
* @param {String} uri - URI to append a segment to.
* @param {String} segment - Segment to append.
* @returns {String} The original URI with a new segment appended.
*/
function appendSegment(uri, segment) {
var index = uri.indexOf("?");
var queryPortion = "";
if (index >= 0) {
queryPortion = uri.substr(index);
uri = uri.substr(0, index);
}
if (uri[uri.length - 1] !== "/") {
uri += "/";
}
return uri + segment + queryPortion;
}
/** Builds a request object to GET the specified URI.
* @param {String} uri - URI for request.
* @param {Object} options - Additional options.
*/
function buildODataRequest(uri, options) {
return {
method: "GET",
requestUri: uri,
user: options.user,
password: options.password,
enableJsonpCallback: options.enableJsonpCallback,
callbackParameterName: options.callbackParameterName,
formatQueryString: options.formatQueryString
};
}
/** Finds the index where the value of a query option starts.
* @param {String} uri - URI to search in.
* @param {String} name - Name to look for.
* @returns {Number} The index where the query option starts.
*/
function findQueryOptionStart(uri, name) {
var result = -1;
var queryIndex = uri.indexOf("?");
if (queryIndex !== -1) {
var start = uri.indexOf("?" + name + "=", queryIndex);
if (start === -1) {
start = uri.indexOf("&" + name + "=", queryIndex);
}
if (start !== -1) {
result = start + name.length + 2;
}
}
return result;
}
/** Gets data from an OData service.
* @param {String} uri - URI to the OData service.
* @param {Object} options - Object with additional well-known request options.
* @param {Function} success - Success callback.
* @param {Function} error - Error callback.
* @returns {Object} Object with an abort method.
*/
function queryForData (uri, options, success, error) {
return queryForDataInternal(uri, options, {}, success, error);
}
/** Gets data from an OData service taking into consideration server side paging.
* @param {String} uri - URI to the OData service.
* @param {Object} options - Object with additional well-known request options.
* @param {Array} data - Array that stores the data provided by the OData service.
* @param {Function} success - Success callback.
* @param {Function} error - Error callback.
* @returns {Object} Object with an abort method.
*/
function queryForDataInternal(uri, options, data, success, error) {
var request = buildODataRequest(uri, options);
var currentRequest = odataRequest.request(request, function (newData) {
var nextLink = newData["@odata.nextLink"];
if (nextLink) {
var index = uri.indexOf(".svc/", 0);
if (index != -1) {
nextLink = uri.substring(0, index + 5) + nextLink;
}
}
if (data.value && newData.value) {
data.value = data.value.concat(newData.value);
}
else {
for (var property in newData) {
if (property != "@odata.nextLink") {
data[property] = newData[property];
}
}
}
if (nextLink) {
currentRequest = queryForDataInternal(nextLink, options, data, success, error);
}
else {
success(data);
}
}, error, undefined, options.httpClient, options.metadata);
return {
abort: function () {
currentRequest.abort();
}
};
}
/** Creates a data cache source object for requesting data from an OData service.
* @class ODataCacheSource
* @param options - Options for the cache data source.
* @returns {ODataCacheSource} A new data cache source instance.
*/
function ODataCacheSource (options) {
var that = this;
var uri = options.source;
that.identifier = normalizeURICase(encodeURI(decodeURI(uri)));
that.options = options;
/** Gets the number of items in the collection.
* @method ODataCacheSource#count
* @param {Function} success - Success callback with the item count.
* @param {Function} error - Error callback.
* @returns {Object} Request object with an abort method.
*/
that.count = function (success, error) {
var options = that.options;
return odataRequest.request(
buildODataRequest(appendSegment(uri, "$count"), options),
function (data) {
var count = parseInt10(data.toString());
if (isNaN(count)) {
error({ message: "Count is NaN", count: count });
} else {
success(count);
}
}, error, undefined, options.httpClient, options.metadata
);
};
/** Gets a number of consecutive items from the collection.
* @method ODataCacheSource#read
* @param {Number} index - Zero-based index of the items to retrieve.
* @param {Number} count - Number of items to retrieve.
* @param {Function} success - Success callback with the requested items.
* @param {Function} error - Error callback.
* @returns {Object} Request object with an abort method.
*/
that.read = function (index, count, success, error) {
var queryOptions = "$skip=" + index + "&$top=" + count;
return queryForData(appendQueryOption(uri, queryOptions), that.options, success, error);
};
return that;
}
/** ODataCacheSource (see {@link ODataCacheSource}) */
exports.ODataCacheSource = ODataCacheSource;}, "deferred" : function(exports, module, require) {
'use strict';
/** @module odatajs/deferred */
/** Creates a new function to forward a call.
* @param {Object} thisValue - Value to use as the 'this' object.
* @param {String} name - Name of function to forward to.
* @param {Object} returnValue - Return value for the forward call (helps keep identity when chaining calls).
* @returns {Function} A new function that will forward a call.
*/
function forwardCall(thisValue, name, returnValue) {
return function () {
thisValue[name].apply(thisValue, arguments);
return returnValue;
};
}
/** Initializes a new DjsDeferred object.
* <ul>
* <li> Compability Note A - Ordering of callbacks through chained 'then' invocations <br>
*
* The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
* implies that .then() returns a distinct object.
*
* For compatibility with http://api.jquery.com/category/deferred-object/
* we return this same object. This affects ordering, as
* the jQuery version will fire callbacks in registration
* order regardless of whether they occur on the result
* or the original object.
* </li>
* <li>Compability Note B - Fulfillment value <br>
*
* The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
* implies that the result of a success callback is the
* fulfillment value of the object and is received by
* other success callbacks that are chained.
*
* For compatibility with http://api.jquery.com/category/deferred-object/
* we disregard this value instead.
* </li></ul>
* @class DjsDeferred
*/
function DjsDeferred() {
this._arguments = undefined;
this._done = undefined;
this._fail = undefined;
this._resolved = false;
this._rejected = false;
}
DjsDeferred.prototype = {
/** Adds success and error callbacks for this deferred object.
* See Compatibility Note A.
* @method DjsDeferred#then
* @param {function} [fulfilledHandler] - Success callback ( may be null)
* @param {function} [errorHandler] - Error callback ( may be null)
*/
then: function (fulfilledHandler, errorHandler) {
if (fulfilledHandler) {
if (!this._done) {
this._done = [fulfilledHandler];
} else {
this._done.push(fulfilledHandler);
}
}
if (errorHandler) {
if (!this._fail) {
this._fail = [errorHandler];
} else {
this._fail.push(errorHandler);
}
}
//// See Compatibility Note A in the DjsDeferred constructor.
//// if (!this._next) {
//// this._next = createDeferred();
//// }
//// return this._next.promise();
if (this._resolved) {
this.resolve.apply(this, this._arguments);
} else if (this._rejected) {
this.reject.apply(this, this._arguments);
}
return this;
},
/** Invokes success callbacks for this deferred object.
* All arguments are forwarded to success callbacks.
* @method DjsDeferred#resolve
*/
resolve: function (/* args */) {
if (this._done) {
var i, len;
for (i = 0, len = this._done.length; i < len; i++) {
//// See Compability Note B - Fulfillment value.
//// var nextValue =
this._done[i].apply(null, arguments);
}
//// See Compatibility Note A in the DjsDeferred constructor.
//// this._next.resolve(nextValue);
//// delete this._next;
this._done = undefined;
this._resolved = false;
this._arguments = undefined;
} else {
this._resolved = true;
this._arguments = arguments;
}
},
/** Invokes error callbacks for this deferred object.
* All arguments are forwarded to error callbacks.
* @method DjsDeferred#reject
*/
reject: function (/* args */) {
if (this._fail) {
var i, len;
for (i = 0, len = this._fail.length; i < len; i++) {
this._fail[i].apply(null, arguments);
}
this._fail = undefined;
this._rejected = false;
this._arguments = undefined;
} else {
this._rejected = true;
this._arguments = arguments;
}
},
/** Returns a version of this object that has only the read-only methods available.
* @method DjsDeferred#promise
* @returns An object with only the promise object.
*/
promise: function () {
var result = {};
result.then = forwardCall(this, "then", result);
return result;
}
};
/** Creates a deferred object.
* @returns {DjsDeferred} A new deferred object. If jQuery is installed, then a jQueryDeferred object is returned, which provides a superset of features.
*/
function createDeferred() {
if (window.jQuery && window.jQuery.Deferred) {
return new window.jQuery.Deferred();
} else {
return new DjsDeferred();
}
}
/** createDeferred (see {@link module:datajs/deferred~createDeferred}) */
exports.createDeferred = createDeferred;
/** DjsDeferred (see {@link DjsDeferred}) */
exports.DjsDeferred = DjsDeferred;}, "odata" : function(exports, module, require) {
'use strict';
/** @module odata */
// Imports
var utils = require('./utils.js');
var odataUtils = exports.utils = require('./odata/odatautils.js');
var odataHandler = exports.handler = require('./odata/handler.js');
var odataMetadata = exports.metadata = require('./odata/metadata.js');
var odataNetServer = exports.netServer = require('./odata/net.js');
var odataNetBrowser = exports.netBrowser = require('./odata/net-browser.js');
var odataNet = exports.net = utils.inBrowser() ? odataNetBrowser : odataNetServer;
var odataJson = exports.json = require('./odata/json.js');
exports.batch = require('./odata/batch.js');
var assigned = utils.assigned;
var defined = utils.defined;
var throwErrorCallback = utils.throwErrorCallback;
var invokeRequest = odataUtils.invokeRequest;
var MAX_DATA_SERVICE_VERSION = odataHandler.MAX_DATA_SERVICE_VERSION;
var prepareRequest = odataUtils.prepareRequest;
var metadataParser = odataMetadata.metadataParser;
// CONTENT START
var handlers = [odataJson.jsonHandler, odataHandler.textHandler];
/** Dispatches an operation to handlers.
* @param {String} handlerMethod - Name of handler method to invoke.
* @param {Object} requestOrResponse - request/response argument for delegated call.
* @param {Object} context - context argument for delegated call.
*/
function dispatchHandler(handlerMethod, requestOrResponse, context) {
var i, len;
for (i = 0, len = handlers.length; i < len && !handlers[i][handlerMethod](requestOrResponse, context); i++) {
}
if (i === len) {
throw { message: "no handler for data" };
}
}
/** Default success handler for OData.
* @param data - Data to process.
*/
exports.defaultSuccess = function (data) {
window.alert(window.JSON.stringify(data));
};
exports.defaultError = throwErrorCallback;
exports.defaultHandler = {
/** Reads the body of the specified response by delegating to JSON handlers.
* @param response - Response object.
* @param context - Operation context.
*/
read: function (response, context) {
if (response && assigned(response.body) && response.headers["Content-Type"]) {
dispatchHandler("read", response, context);
}
},
/** Write the body of the specified request by delegating to JSON handlers.
* @param request - Reques tobject.
* @param context - Operation context.
*/
write: function (request, context) {
dispatchHandler("write", request, context);
},
maxDataServiceVersion: MAX_DATA_SERVICE_VERSION,
accept: "application/json;q=0.9, */*;q=0.1"
};
exports.defaultMetadata = []; //TODO check why is the defaultMetadata an Array? and not an Object.
/** Reads data from the specified URL.
* @param urlOrRequest - URL to read data from.
* @param {Function} [success] -
* @param {Function} [error] -
* @param {Object} [handler] -
* @param {Object} [httpClient] -
* @param {Object} [metadata] -
*/
exports.read = function (urlOrRequest, success, error, handler, httpClient, metadata) {
var request;
if (urlOrRequest instanceof String || typeof urlOrRequest === "string") {
request = { requestUri: urlOrRequest };
} else {
request = urlOrRequest;
}
return exports.request(request, success, error, handler, httpClient, metadata);
};
/** Sends a request containing OData payload to a server.
* @param {Object} request - Object that represents the request to be sent.
* @param {Function} [success] -
* @param {Function} [error] -
* @param {Object} [handler] -
* @param {Object} [httpClient] -
* @param {Object} [metadata] -
*/
exports.request = function (request, success, error, handler, httpClient, metadata) {
success = success || exports.defaultSuccess;
error = error || exports.defaultError;
handler = handler || exports.defaultHandler;
httpClient = httpClient || odataNet.defaultHttpClient;
metadata = metadata || exports.defaultMetadata;
// Augment the request with additional defaults.
request.recognizeDates = utils.defined(request.recognizeDates, odataJson.jsonHandler.recognizeDates);
request.callbackParameterName = utils.defined(request.callbackParameterName, httpClient.callbackParameterName, odataNet.defaultHttpClient.callbackParameterName);
request.formatQueryString = utils.defined(request.formatQueryString, httpClient.formatQueryString, odataNet.defaultHttpClient.formatQueryString);
request.enableJsonpCallback = utils.defined(request.enableJsonpCallback, httpClient.enableJsonpCallback, odataNet.defaultHttpClient.enableJsonpCallback);
// Create the base context for read/write operations, also specifying complete settings.
var context = {
metadata: metadata,
recognizeDates: request.recognizeDates,
callbackParameterName: request.callbackParameterName,
formatQueryString: request.formatQueryString,
enableJsonpCallback: request.enableJsonpCallback
};
try {
odataUtils.prepareRequest(request, handler, context);
return odataUtils.invokeRequest(request, success, error, handler, httpClient, context);
} catch (err) {
// errors in success handler for sync requests are catched here and result in error handler calls.
// So here we fix this and throw that error further.
if (err.bIsSuccessHandlerError) {
throw err;
} else {
error(err);
}
}
};
/** Parses the csdl metadata to ODataJS metatdata format. This method can be used when the metadata is retrieved using something other than odatajs
* @param {string} csdlMetadataDocument - A string that represents the entire csdl metadata.
* @returns {Object} An object that has the representation of the metadata in odatajs format.
*/
exports.parseMetadata = function (csdlMetadataDocument) {
return metadataParser(null, csdlMetadataDocument);
};
// Configure the batch handler to use the default handler for the batch parts.
exports.batch.batchHandler.partHandler = exports.defaultHandler;
exports.metadataHandler = odataMetadata.metadataHandler;
exports.jsonHandler = odataJson.jsonHandler;
}, "batch" : function(exports, module, require) {
'use strict';
/** @module odata/batch */
var utils = require('./../utils.js');
var odataUtils = require('./odatautils.js');
var odataHandler = require('./handler.js');
var extend = utils.extend;
var isArray = utils.isArray;
var trimString = utils.trimString;
var contentType = odataHandler.contentType;
var handler = odataHandler.handler;
var isBatch = odataUtils.isBatch;
var MAX_DATA_SERVICE_VERSION = odataHandler.MAX_DATA_SERVICE_VERSION;
var normalizeHeaders = odataUtils.normalizeHeaders;
//TODO var payloadTypeOf = odata.payloadTypeOf;
var prepareRequest = odataUtils.prepareRequest;
// Imports
// CONTENT START
var batchMediaType = "multipart/mixed";
var responseStatusRegex = /^HTTP\/1\.\d (\d{3}) (.*)$/i;
var responseHeaderRegex = /^([^()<>@,;:\\"\/[\]?={} \t]+)\s?:\s?(.*)/;
/** Calculates a random 16 bit number and returns it in hexadecimal format.
* @returns {String} A 16-bit number in hex format.
*/
function hex16() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substr(1);
}
/** Creates a string that can be used as a multipart request boundary.
* @param {String} [prefix] -
* @returns {String} Boundary string of the format: <prefix><hex16>-<hex16>-<hex16>
*/
function createBoundary(prefix) {
return prefix + hex16() + "-" + hex16() + "-" + hex16();
}
/** Gets the handler for data serialization of individual requests / responses in a batch.
* @param context - Context used for data serialization.
* @returns Handler object
*/
function partHandler(context) {
return context.handler.partHandler;
}
/** Gets the current boundary used for parsing the body of a multipart response.
* @param context - Context used for parsing a multipart response.
* @returns {String} Boundary string.
*/
function currentBoundary(context) {
var boundaries = context.boundaries;
return boundaries[boundaries.length - 1];
}
/** Parses a batch response.
* @param handler - This handler.
* @param {String} text - Batch text.
* @param {Object} context - Object with parsing context.
* @return An object representation of the batch.
*/
function batchParser(handler, text, context) {
var boundary = context.contentType.properties["boundary"];
return { __batchResponses: readBatch(text, { boundaries: [boundary], handlerContext: context }) };
}
/** Serializes a batch object representation into text.
* @param handler - This handler.
* @param {Object} data - Representation of a batch.
* @param {Object} context - Object with parsing context.
* @return An text representation of the batch object; undefined if not applicable.#
*/
function batchSerializer(handler, data, context) {
var cType = context.contentType = context.contentType || contentType(batchMediaType);
if (cType.mediaType === batchMediaType) {
return writeBatch(data, context);
}
}
/** Parses a multipart/mixed response body from from the position defined by the context.
* @param {String} text - Body of the multipart/mixed response.
* @param context - Context used for parsing.
* @return Array of objects representing the individual responses.
*/
function readBatch(text, context) {
var delimiter = "--" + currentBoundary(context);
// Move beyond the delimiter and read the complete batch
readTo(text, context, delimiter);
// Ignore the incoming line
readLine(text, context);
// Read the batch parts
var responses = [];
var partEnd = null;
while (partEnd !== "--" && context.position < text.length) {
var partHeaders = readHeaders(text, context);
var partContentType = contentType(partHeaders["Content-Type"]);
var changeResponses;
if (partContentType && partContentType.mediaType === batchMediaType) {
context.boundaries.push(partContentType.properties.boundary);
try {
changeResponses = readBatch(text, context);
} catch (e) {
e.response = readResponse(text, context, delimiter);
changeResponses = [e];
}
responses.push({ __changeResponses: changeResponses });
context.boundaries.pop();
readTo(text, context, "--" + currentBoundary(context));
} else {
if (!partContentType || partContentType.mediaType !== "application/http") {
throw { message: "invalid MIME part type " };
}
// Skip empty line
readLine(text, context);
// Read the response
var response = readResponse(text, context, delimiter);
try {
if (response.statusCode >= 200 && response.statusCode <= 299) {
partHandler(context.handlerContext).read(response, context.handlerContext);
} else {
// Keep track of failed responses and continue processing the batch.
response = { message: "HTTP request failed", response: response };
}
} catch (e) {
response = e;
}
responses.push(response);
}
partEnd = text.substr(context.position, 2);
// Ignore the incoming line.
readLine(text, context);
}
return responses;
}
/** Parses the http headers in the text from the position defined by the context.
* @param {String} text - Text containing an http response's headers
* @param context - Context used for parsing.
* @returns Object containing the headers as key value pairs.
* This function doesn't support split headers and it will stop reading when it hits two consecutive line breaks.
*/
function readHeaders(text, context) {
var headers = {};
var parts;
var line;
var pos;
do {
pos = context.position;
line = readLine(text, context);
parts = responseHeaderRegex.exec(line);
if (parts !== null) {
headers[parts[1]] = parts[2];
} else {
// Whatever was found is not a header, so reset the context position.
context.position = pos;
}
} while (line && parts);
normalizeHeaders(headers);
return headers;
}
/** Parses an HTTP response.
* @param {String} text -Text representing the http response.
* @param context optional - Context used for parsing.
* @param {String} delimiter -String used as delimiter of the multipart response parts.
* @return Object representing the http response.
*/
function readResponse(text, context, delimiter) {
// Read the status line.
var pos = context.position;
var match = responseStatusRegex.exec(readLine(text, context));
var statusCode;
var statusText;
var headers;
if (match) {
statusCode = match[1];
statusText = match[2];
headers = readHeaders(text, context);
readLine(text, context);
} else {
context.position = pos;
}
return {
statusCode: statusCode,
statusText: statusText,
headers: headers,
body: readTo(text, context, "\r\n" + delimiter)
};
}
/** Returns a substring from the position defined by the context up to the next line break (CRLF).
* @param {String} text - Input string.
* @param context - Context used for reading the input string.
* @returns {String} Substring to the first ocurrence of a line break or null if none can be found.
*/
function readLine(text, context) {
return readTo(text, context, "\r\n");
}
/** 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.
* @param {String} text - Input string.
* @param context - Context used for reading the input string.
* @param {String} [str] - Substring to read up to.
* @returns {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.
*/
function readTo(text, context, str) {
var start = context.position || 0;
var end = text.length;
if (str) {
end = text.indexOf(str, start);
if (end === -1) {
return null;
}
context.position = end + str.length;
} else {
context.position = end;
}
return text.substring(start, end);
}
/** Serializes a batch request object to a string.
* @param data - Batch request object in payload representation format
* @param context - Context used for the serialization
* @returns {String} String representing the batch request
*/
function writeBatch(data, context) {
if (!isBatch(data)) {
throw { message: "Data is not a batch object." };
}
var batchBoundary = createBoundary("batch_");
var batchParts = data.__batchRequests;
var batch = "";
var i, len;
for (i = 0, len = batchParts.length; i < len; i++) {
batch += writeBatchPartDelimiter(batchBoundary, false) +
writeBatchPart(batchParts[i], context);
}
batch += writeBatchPartDelimiter(batchBoundary, true);
// Register the boundary with the request content type.
var contentTypeProperties = context.contentType.properties;
contentTypeProperties.boundary = batchBoundary;
return batch;
}
/** Creates the delimiter that indicates that start or end of an individual request.
* @param {String} boundary Boundary string used to indicate the start of the request
* @param {Boolean} close - Flag indicating that a close delimiter string should be generated
* @returns {String} Delimiter string
*/
function writeBatchPartDelimiter(boundary, close) {
var result = "\r\n--" + boundary;
if (close) {
result += "--";
}
return result + "\r\n";
}
/** Serializes a part of a batch request to a string. A part can be either a GET request or
* a change set grouping several CUD (create, update, delete) requests.
* @param part - Request or change set object in payload representation format
* @param context - Object containing context information used for the serialization
* @param {boolean} [nested] -
* @returns {String} String representing the serialized part
* A change set is an array of request objects and they cannot be nested inside other change sets.
*/
function writeBatchPart(part, context, nested) {
var changeSet = part.__changeRequests;
var result;
if (isArray(changeSet)) {
if (nested) {
throw { message: "Not Supported: change set nested in other change set" };
}
var changeSetBoundary = createBoundary("changeset_");
result = "Content-Type: " + batchMediaType + "; boundary=" + changeSetBoundary + "\r\n";
var i, len;
for (i = 0, len = changeSet.length; i < len; i++) {
result += writeBatchPartDelimiter(changeSetBoundary, false) +
writeBatchPart(changeSet[i], context, true);
}
result += writeBatchPartDelimiter(changeSetBoundary, true);
} else {
result = "Content-Type: application/http\r\nContent-Transfer-Encoding: binary\r\n\r\n";
var partContext = extend({}, context);
partContext.handler = handler;
partContext.request = part;
partContext.contentType = null;
prepareRequest(part, partHandler(context), partContext);
result += writeRequest(part);
}
return result;
}
/** Serializes a request object to a string.
* @param request - Request object to serialize
* @returns {String} String representing the serialized request
*/
function writeRequest(request) {
var result = (request.method ? request.method : "GET") + " " + request.requestUri + " HTTP/1.1\r\n";
for (var name in request.headers) {
if (request.headers[name]) {
result = result + name + ": " + request.headers[name] + "\r\n";
}
}
result += "\r\n";
if (request.body) {
result += request.body;
}
return result;
}
/** batchHandler (see {@link module:odata/batch~batchParser}) */
exports.batchHandler = handler(batchParser, batchSerializer, batchMediaType, MAX_DATA_SERVICE_VERSION);
/** batchSerializer (see {@link module:odata/batch~batchSerializer}) */
exports.batchSerializer = batchSerializer;
/** writeRequest (see {@link module:odata/batch~writeRequest}) */
exports.writeRequest = writeRequest;}, "handler" : function(exports, module, require) {
'use strict';
/** @module odata/handler */
var utils = require('./../utils.js');
var oDataUtils = require('./odatautils.js');
// Imports.
var assigned = utils.assigned;
var extend = utils.extend;
var trimString = utils.trimString;
var maxVersion = oDataUtils.maxVersion;
var MAX_DATA_SERVICE_VERSION = "4.0";
/** Parses a string into an object with media type and properties.
* @param {String} str - String with media type to parse.
* @return null if the string is empty; an object with 'mediaType' and a 'properties' dictionary otherwise.
*/
function contentType(str) {
if (!str) {
return null;
}
var contentTypeParts = str.split(";");
var properties = {};
var i, len;
for (i = 1, len = contentTypeParts.length; i < len; i++) {
var contentTypeParams = contentTypeParts[i].split("=");
properties[trimString(contentTypeParams[0])] = contentTypeParams[1];
}
return { mediaType: trimString(contentTypeParts[0]), properties: properties };
}
/** Serializes an object with media type and properties dictionary into a string.
* @param contentType - Object with media type and properties dictionary to serialize.
* @return String representation of the media type object; undefined if contentType is null or undefined.
*/
function contentTypeToString(contentType) {
if (!contentType) {
return undefined;
}
var result = contentType.mediaType;
var property;
for (property in contentType.properties) {
result += ";" + property + "=" + contentType.properties[property];
}
return result;
}
/** Creates an object that is going to be used as the context for the handler's parser and serializer.
* @param contentType - Object with media type and properties dictionary.
* @param {String} dataServiceVersion - String indicating the version of the protocol to use.
* @param context - Operation context.
* @param handler - Handler object that is processing a request or response.
* @return Context object.
*/
function createReadWriteContext(contentType, dataServiceVersion, context, handler) {
var rwContext = {};
extend(rwContext, context);
extend(rwContext, {
contentType: contentType,
dataServiceVersion: dataServiceVersion,
handler: handler
});
return rwContext;
}
/** 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.
* @param request - Request object on which the header will be set.
* @param {String} name - Header name.
* @param {String} value - Header value.
*/
function fixRequestHeader(request, name, value) {
if (!request) {
return;
}
var headers = request.headers;
if (!headers[name]) {
headers[name] = value;
}
}
/** Sets the DataServiceVersion header of the request if its value is not yet defined or of a lower version.
* @param request - Request object on which the header will be set.
* @param {String} version - Version value.
* If the request has already a version value higher than the one supplied the this function does nothing.
*/
function fixDataServiceVersionHeader(request, version) {
if (request) {
var headers = request.headers;
var dsv = headers["OData-Version"];
headers["OData-Version"] = dsv ? maxVersion(dsv, version) : version;
}
}
/** Gets the value of a request or response header.
* @param requestOrResponse - Object representing a request or a response.
* @param {String} name - Name of the header to retrieve.
* @returns {String} String value of the header; undefined if the header cannot be found.
*/
function getRequestOrResponseHeader(requestOrResponse, name) {
var headers = requestOrResponse.headers;
return (headers && headers[name]) || undefined;
}
/** Gets the value of the Content-Type header from a request or response.
* @param requestOrResponse - Object representing a request or a response.
* @returns {Object} Object with 'mediaType' and a 'properties' dictionary; null in case that the header is not found or doesn't have a value.
*/
function getContentType(requestOrResponse) {
return contentType(getRequestOrResponseHeader(requestOrResponse, "Content-Type"));
}
var versionRE = /^\s?(\d+\.\d+);?.*$/;
/** Gets the value of the DataServiceVersion header from a request or response.
* @param requestOrResponse - Object representing a request or a response.
* @returns {String} Data service version; undefined if the header cannot be found.
*/
function getDataServiceVersion(requestOrResponse) {
var value = getRequestOrResponseHeader(requestOrResponse, "OData-Version");
if (value) {
var matches = versionRE.exec(value);
if (matches && matches.length) {
return matches[1];
}
}
// Fall through and return undefined.
}
/** Checks that a handler can process a particular mime type.
* @param handler - Handler object that is processing a resquest or response.
* @param cType - Object with 'mediaType' and a 'properties' dictionary.
* @returns {Boolean} True if the handler can process the mime type; false otherwise.
*
* The following check isn't as strict because if cType.mediaType = application/; it will match an accept value of "application/xml";
* however in practice we don't not expect to see such "suffixed" mimeTypes for the handlers.
*/
function handlerAccepts(handler, cType) {
return handler.accept.indexOf(cType.mediaType) >= 0;
}
/** Invokes the parser associated with a handler for reading the payload of a HTTP response.
* @param handler - Handler object that is processing the response.
* @param {Function} parseCallback - Parser function that will process the response payload.
* @param response - HTTP response whose payload is going to be processed.
* @param context - Object used as the context for processing the response.
* @returns {Boolean} True if the handler processed the response payload and the response.data property was set; false otherwise.
*/
function handlerRead(handler, parseCallback, response, context) {
if (!response || !response.headers) {
return false;
}
var cType = getContentType(response);
var version = getDataServiceVersion(response) || "";
var body = response.body;
if (!assigned(body)) {
return false;
}
if (handlerAccepts(handler, cType)) {
var readContext = createReadWriteContext(cType, version, context, handler);
readContext.response = response;
response.data = parseCallback(handler, body, readContext);
return response.data !== undefined;
}
return false;
}
/** Invokes the serializer associated with a handler for generating the payload of a HTTP request.
* @param handler - Handler object that is processing the request.
* @param {Function} serializeCallback - Serializer function that will generate the request payload.
* @param request - HTTP request whose payload is going to be generated.
* @param context - Object used as the context for serializing the request.
* @returns {Boolean} True if the handler serialized the request payload and the request.body property was set; false otherwise.
*/
function handlerWrite(handler, serializeCallback, request, context) {
if (!request || !request.headers) {
return false;
}
var cType = getContentType(request);
var version = getDataServiceVersion(request);
if (!cType || handlerAccepts(handler, cType)) {
var writeContext = createReadWriteContext(cType, version, context, handler);
writeContext.request = request;
request.body = serializeCallback(handler, request.data, writeContext);
if (request.body !== undefined) {
fixDataServiceVersionHeader(request, writeContext.dataServiceVersion || "4.0");
fixRequestHeader(request, "Content-Type", contentTypeToString(writeContext.contentType));
fixRequestHeader(request, "OData-MaxVersion", handler.maxDataServiceVersion);
return true;
}
}
return false;
}
/** Creates a handler object for processing HTTP requests and responses.
* @param {Function} parseCallback - Parser function that will process the response payload.
* @param {Function} serializeCallback - Serializer function that will generate the request payload.
* @param {String} accept - String containing a comma separated list of the mime types that this handler can work with.
* @param {String} maxDataServiceVersion - String indicating the highest version of the protocol that this handler can work with.
* @returns {Object} Handler object.
*/
function handler(parseCallback, serializeCallback, accept, maxDataServiceVersion) {
return {
accept: accept,
maxDataServiceVersion: maxDataServiceVersion,
read: function (response, context) {
return handlerRead(this, parseCallback, response, context);
},
write: function (request, context) {
return handlerWrite(this, serializeCallback, request, context);
}
};
}
function textParse(handler, body /*, context */) {
return body;
}
function textSerialize(handler, data /*, context */) {
if (assigned(data)) {
return data.toString();
} else {
return undefined;
}
}
exports.textHandler = handler(textParse, textSerialize, "text/plain", MAX_DATA_SERVICE_VERSION);
exports.contentType = contentType;
exports.contentTypeToString = contentTypeToString;
exports.handler = handler;
exports.createReadWriteContext = createReadWriteContext;
exports.fixRequestHeader = fixRequestHeader;
exports.getRequestOrResponseHeader = getRequestOrResponseHeader;
exports.getContentType = getContentType;
exports.getDataServiceVersion = getDataServiceVersion;
exports.MAX_DATA_SERVICE_VERSION = MAX_DATA_SERVICE_VERSION;}, "json" : function(exports, module, require) {
/** @module odata/json */
var utils = require('./../utils.js');
var oDataUtils = require('./odatautils.js');
var oDataHandler = require('./handler.js');
var odataNs = "odata";
var odataAnnotationPrefix = odataNs + ".";
var contextUrlAnnotation = "@" + odataAnnotationPrefix + "context";
var assigned = utils.assigned;
var defined = utils.defined;
var isArray = utils.isArray;
//var isDate = utils.isDate;
var isObject = utils.isObject;
//var normalizeURI = utils.normalizeURI;
var parseInt10 = utils.parseInt10;
var getFormatKind = utils.getFormatKind;
var convertByteArrayToHexString = utils.convertByteArrayToHexString;
var formatDateTimeOffset = oDataUtils.formatDateTimeOffset;
var formatDuration = oDataUtils.formatDuration;
var formatNumberWidth = oDataUtils.formatNumberWidth;
var getCanonicalTimezone = oDataUtils.getCanonicalTimezone;
var handler = oDataUtils.handler;
var isComplex = oDataUtils.isComplex;
var isPrimitive = oDataUtils.isPrimitive;
var isCollectionType = oDataUtils.isCollectionType;
var lookupComplexType = oDataUtils.lookupComplexType;
var lookupEntityType = oDataUtils.lookupEntityType;
var lookupSingleton = oDataUtils.lookupSingleton;
var lookupEntitySet = oDataUtils.lookupEntitySet;
var lookupDefaultEntityContainer = oDataUtils.lookupDefaultEntityContainer;
var lookupProperty = oDataUtils.lookupProperty;
var MAX_DATA_SERVICE_VERSION = oDataUtils.MAX_DATA_SERVICE_VERSION;
var maxVersion = oDataUtils.maxVersion;
var isPrimitiveEdmType = oDataUtils.isPrimitiveEdmType;
var isGeographyEdmType = oDataUtils.isGeographyEdmType;
var isGeometryEdmType = oDataUtils.isGeometryEdmType;
var PAYLOADTYPE_FEED = "f";
var PAYLOADTYPE_ENTRY = "e";
var PAYLOADTYPE_PROPERTY = "p";
var PAYLOADTYPE_COLLECTION = "c";
var PAYLOADTYPE_ENUMERATION_PROPERTY = "enum";
var PAYLOADTYPE_SVCDOC = "s";
var PAYLOADTYPE_ENTITY_REF_LINK = "erl";
var PAYLOADTYPE_ENTITY_REF_LINKS = "erls";
var PAYLOADTYPE_VALUE = "v";
var PAYLOADTYPE_DELTA = "d";
var DELTATYPE_FEED = "f";
var DELTATYPE_DELETED_ENTRY = "de";
var DELTATYPE_LINK = "l";
var DELTATYPE_DELETED_LINK = "dl";
var jsonMediaType = "application/json";
var jsonContentType = oDataHandler.contentType(jsonMediaType);
var jsonSerializableMetadata = ["@odata.id", "@odata.type"];
/** Extend JSON OData payload with metadata
* @param handler - This handler.
* @param text - Payload text (this parser also handles pre-parsed objects).
* @param {Object} context - Object with parsing context.
* @return An object representation of the OData payload.
*/
function jsonParser(handler, text, context) {
var recognizeDates = defined(context.recognizeDates, handler.recognizeDates);
var model = context.metadata;
var json = (typeof text === "string") ? JSON.parse(text) : text;
var metadataContentType;
if (assigned(context.contentType) && assigned(context.contentType.properties)) {
metadataContentType = context.contentType.properties["odata.metadata"]; //TODO convert to lower before comparism
}
var payloadFormat = getFormatKind(metadataContentType, 1); // none: 0, minimal: 1, full: 2
// No errors should be throw out if we could not parse the json payload, instead we should just return the original json object.
if (payloadFormat === 0) {
return json;
}
else if (payloadFormat === 1) {
return addMinimalMetadataToJsonPayload(json, model, recognizeDates);
}
else if (payloadFormat === 2) {
// to do: using the EDM Model to get the type of each property instead of just guessing.
return addFullMetadataToJsonPayload(json, model, recognizeDates);
}
else {
return json;
}
}
// The regular expression corresponds to something like this:
// /Date(123+60)/
//
// This first number is date ticks, the + may be a - and is optional,
// with the second number indicating a timezone offset in minutes.
//
// On the wire, the leading and trailing forward slashes are
// escaped without being required to so the chance of collisions is reduced;
// however, by the time we see the objects, the characters already
// look like regular forward slashes.
var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
// Some JSON implementations cannot produce the character sequence \/
// which is needed to format DateTime and DateTimeOffset into the
// JSON string representation defined by the OData protocol.
// See the history of this file for a candidate implementation of
// a 'formatJsonDateString' function.
var jsonReplacer = function (_, value) {
/// <summary>JSON replacer function for converting a value to its JSON representation.</summary>
/// <param value type="Object">Value to convert.</param>
/// <returns type="String">JSON representation of the input value.</returns>
/// <remarks>
/// This method is used during JSON serialization and invoked only by the JSON.stringify function.
/// It should never be called directly.
/// </remarks>
if (value && value.__edmType === "Edm.Time") {
return formatDuration(value);
} else {
return value;
}
};
/** Serializes a ODataJs payload structure to the wire format which can be send to the server
* @param handler - This handler.
* @param data - Data to serialize.
* @param {Object} context - Object with serialization context.
* @returns {String} The string representation of data.
*/
function jsonSerializer(handler, data, context) {
var dataServiceVersion = context.dataServiceVersion || "4.0";
var cType = context.contentType = context.contentType || jsonContentType;
if (cType && cType.mediaType === jsonContentType.mediaType) {
context.dataServiceVersion = maxVersion(dataServiceVersion, "4.0");
var newdata = formatJsonRequestPayload(data);
if (newdata) {
return JSON.stringify(newdata,jsonReplacer);
}
}
return undefined;
}
/** Convert OData objects for serialisation in to a new data structure
* @param data - Data to serialize.
* @returns {String} The string representation of data.
*/
function formatJsonRequestPayload(data) {
if (!data) {
return data;
}
if (isPrimitive(data)) {
return data;
}
if (isArray(data)) {
var newArrayData = [];
var i, len;
for (i = 0, len = data.length; i < len; i++) {
newArrayData[i] = formatJsonRequestPayload(data[i]);
}
return newArrayData;
}
var newdata = {};
for (var property in data) {
if (isJsonSerializableProperty(property)) {
newdata[property] = formatJsonRequestPayload(data[property]);
}
}
return newdata;
}
/** Determine form the attribute name if the attribute is a serializable property
* @param attribute
* @returns {boolean}
*/
function isJsonSerializableProperty(attribute) {
if (!attribute) {
return false;
}
if (attribute.indexOf("@odata.") == -1) {
return true;
}
var i, len;
for (i = 0, len = jsonSerializableMetadata.length; i < len; i++) {
var name = jsonSerializableMetadata[i];
if (attribute.indexOf(name) != -1) {
return true;
}
}
return false;
}
/** Creates an object containing information for the json payload.
* @param {String} kind - JSON payload kind
* @param {String} type - Type name of the JSON payload.
* @returns {Object} Object with kind and type fields.
*/
function jsonMakePayloadInfo(kind, type) {
return { kind: kind, type: type || null };
}
/** Add metadata to an JSON payload complex object containing full metadata
* @param {Object} data - Data structure to be extended
* @param {Object} model - Metadata model
* @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.
*/
function addFullMetadataToJsonPayload(data, model, recognizeDates) {
var type;
if (utils.isObject(data)) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (key.indexOf('@') === -1) {
if (utils.isArray(data[key])) {
for (var i = 0; i < data[key].length; ++i) {
addFullMetadataToJsonPayload(data[key][i], model, recognizeDates);
}
} else if (utils.isObject(data[key])) {
if (data[key] !== null) {
//don't step into geo.. objects
type = data[key+'@odata.type'];
if (!type) {
//type unknown
addFullMetadataToJsonPayload(data[key], model, recognizeDates);
} else {
type = type.substring(1);
if (isGeographyEdmType(type) || isGeometryEdmType(type)) {
// don't add type info for geo* types
} else {
addFullMetadataToJsonPayload(data[key], model, recognizeDates);
}
}
}
} else {
type = data[key + '@odata.type'];
// On .Net OData library, some basic EDM type is omitted, e.g. Edm.String, Edm.Int, and etc.
// For the full metadata payload, we need to full fill the @data.type for each property if it is missing.
// We do this is to help the OlingoJS consumers to easily get the type of each property.
if (!assigned(type)) {
// Guessing the "type" from the type of the value is not the right way here.
// To do: we need to get the type from metadata instead of guessing.
var typeFromObject = typeof data[key];
if (typeFromObject === 'string') {
addType(data, key, 'String');
} else if (typeFromObject === 'boolean') {
addType(data, key, 'Boolean');
} else if (typeFromObject === 'number') {
if (data[key] % 1 === 0) { // has fraction
addType(data, key, 'Int32'); // the biggst integer
} else {
addType(data, key, 'Decimal'); // the biggst float single,doulbe,decimal
}
}
}
else {
if (recognizeDates) {
convertDatesNoEdm(data, key, type.substring(1));
}
}
}
}
}
}
}
return data;
}
/** Loop through the properties of an JSON payload object, look up the type info of the property and call
* the appropriate add*MetadataToJsonPayloadObject function
* @param {Object} data - Data structure to be extended
* @param {String} objectInfoType - Information about the data (name,type,typename,...)
* @param {String} baseURI - Base Url
* @param {Object} model - Metadata model
* @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.
*/
function checkProperties(data, objectInfoType, baseURI, model, recognizeDates) {
for (var name in data) {
if (name.indexOf("@") === -1) {
var curType = objectInfoType;
var propertyValue = data[name];
var property = lookupProperty(curType.property,name); //TODO SK add check for parent type
while (( property === null) && (curType.baseType !== undefined)) {
curType = lookupEntityType(curType.baseType, model);
property = lookupProperty(curType.property,name);
}
if ( isArray(propertyValue)) {
//data[name+'@odata.type'] = '#' + property.type;
if (isCollectionType(property.type)) {
addTypeColNoEdm(data,name,property.type.substring(11,property.type.length-1));
} else {
addTypeNoEdm(data,name,property.type);
}
for ( var i = 0; i < propertyValue.length; i++) {
addMetadataToJsonMinimalPayloadComplex(propertyValue[i], property, baseURI, model, recognizeDates);
}
} else if (isObject(propertyValue) && (propertyValue !== null)) {
addMetadataToJsonMinimalPayloadComplex(propertyValue, property, baseURI, model, recognizeDates);
} else {
//data[name+'@odata.type'] = '#' + property.type;
addTypeNoEdm(data,name,property.type);
if (recognizeDates) {
convertDates(data, name, property.type);
}
}
}
}
}
/** Add metadata to an JSON payload object containing minimal metadata
* @param {Object} data - Json response payload object
* @param {Object} model - Object describing an OData conceptual schema
* @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.
* @returns {Object} Object in the library's representation.
*/
function addMinimalMetadataToJsonPayload(data, model, recognizeDates) {
if (!assigned(model) || isArray(model)) {
return data;
}
var baseURI = data[contextUrlAnnotation];
var payloadInfo = createPayloadInfo(data, model);
switch (payloadInfo.detectedPayloadKind) {
case PAYLOADTYPE_VALUE:
if (payloadInfo.type !== null) {
return addMetadataToJsonMinimalPayloadEntity(data, payloadInfo, baseURI, model, recognizeDates);
} else {
return addTypeNoEdm(data,'value', payloadInfo.typeName);
}
case PAYLOADTYPE_FEED:
return addMetadataToJsonMinimalPayloadFeed(data, model, payloadInfo, baseURI, recognizeDates);
case PAYLOADTYPE_ENTRY:
return addMetadataToJsonMinimalPayloadEntity(data, payloadInfo, baseURI, model, recognizeDates);
case PAYLOADTYPE_COLLECTION:
return addMetadataToJsonMinimalPayloadCollection(data, model, payloadInfo, baseURI, recognizeDates);
case PAYLOADTYPE_PROPERTY:
if (payloadInfo.type !== null) {
return addMetadataToJsonMinimalPayloadEntity(data, payloadInfo, baseURI, model, recognizeDates);
} else {
return addTypeNoEdm(data,'value', payloadInfo.typeName);
}
case PAYLOADTYPE_SVCDOC:
return data;
case PAYLOADTYPE_LINKS:
return data;
}
return data;
}
/** Add metadata to an JSON payload feed object containing minimal metadata
* @param {Object} data - Data structure to be extended
* @param {Object} model - Metadata model
* @param {String} feedInfo - Information about the data (name,type,typename,...)
* @param {String} baseURI - Base Url
* @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.
*/
function addMetadataToJsonMinimalPayloadFeed(data, model, feedInfo, baseURI, recognizeDates) {
var entries = [];
var items = data.value;
var i,len;
var entry;
for (i = 0, len = items.length; i < len; i++) {
var item = items[i];
if ( defined(item['@odata.type'])) { // in case of mixed feeds
var typeName = item['@odata.type'].substring(1);
var type = lookupEntityType( typeName, model);
var entryInfo = {
contentTypeOdata : feedInfo.contentTypeOdata,
detectedPayloadKind : feedInfo.detectedPayloadKind,
name : feedInfo.name,
type : type,
typeName : typeName
};
entry = addMetadataToJsonMinimalPayloadEntity(item, entryInfo, baseURI, model, recognizeDates);
} else {
entry = addMetadataToJsonMinimalPayloadEntity(item, feedInfo, baseURI, model, recognizeDates);
}
entries.push(entry);
}
data.value = entries;
return data;
}
/** Add metadata to an JSON payload entity object containing minimal metadata
* @param {Object} data - Data structure to be extended
* @param {String} objectInfo - Information about the data (name,type,typename,...)
* @param {String} baseURI - Base Url
* @param {Object} model - Metadata model
* @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.
*/
function addMetadataToJsonMinimalPayloadEntity(data, objectInfo, baseURI, model, recognizeDates) {
addType(data,'',objectInfo.typeName);
var keyType = objectInfo.type;
while ((defined(keyType)) && ( keyType.key === undefined) && (keyType.baseType !== undefined)) {
keyType = lookupEntityType(keyType.baseType, model);
}
if (keyType.key !== undefined) {
var lastIdSegment = objectInfo.name + jsonGetEntryKey(data, keyType);
data['@odata.id'] = baseURI.substring(0, baseURI.lastIndexOf("$metadata")) + lastIdSegment;
data['@odata.editLink'] = lastIdSegment;
}
//var serviceURI = baseURI.substring(0, baseURI.lastIndexOf("$metadata"));
checkProperties(data, objectInfo.type, baseURI, model, recognizeDates);
return data;
}
/** Add metadata to an JSON payload complex object containing minimal metadata
* @param {Object} data - Data structure to be extended
* @param {String} property - Information about the data (name,type,typename,...)
* @param {String} baseURI - Base Url
* @param {Object} model - Metadata model
* @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.
*/
function addMetadataToJsonMinimalPayloadComplex(data, property, baseURI, model, recognizeDates) {
var type = property.type;
if (isCollectionType(property.type)) {
type =property.type.substring(11,property.type.length-1);
}
addType(data,'',property.type);
var propertyType = lookupComplexType(type, model);
if (propertyType === null) {
return; //TODO check what to do if the type is not known e.g. type #GeometryCollection
}
checkProperties(data, propertyType, baseURI, model, recognizeDates);
}
/** Add metadata to an JSON payload collection object containing minimal metadata
* @param {Object} data - Data structure to be extended
* @param {Object} model - Metadata model
* @param {String} collectionInfo - Information about the data (name,type,typename,...)
* @param {String} baseURI - Base Url
* @param {Boolean} recognizeDates - Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.
*/
function addMetadataToJsonMinimalPayloadCollection(data, model, collectionInfo, baseURI, recognizeDates) {
addTypeColNoEdm(data,'', collectionInfo.typeName);
if (collectionInfo.type !== null) {
var entries = [];
var items = data.value;
var i,len;
var entry;
for (i = 0, len = items.length; i < len; i++) {
var item = items[i];
if ( defined(item['@odata.type'])) { // in case of mixed collections
var typeName = item['@odata.type'].substring(1);
var type = lookupEntityType( typeName, model);
var entryInfo = {
contentTypeOdata : collectionInfo.contentTypeOdata,
detectedPayloadKind : collectionInfo.detectedPayloadKind,
name : collectionInfo.name,
type : type,
typeName : typeName
};
entry = addMetadataToJsonMinimalPayloadEntity(item, entryInfo, baseURI, model, recognizeDates);
} else {
entry = addMetadataToJsonMinimalPayloadEntity(item, collectionInfo, baseURI, model, recognizeDates);
}
entries.push(entry);
}
data.value = entries;
}
return data;
}
/** Add an OData type tag to an JSON payload object
* @param {Object} data - Data structure to be extended
* @param {String} name - Name of the property whose type is set
* @param {String} value - Type name
*/
function addType(data, name, value ) {
var fullName = name + '@odata.type';
if ( data[fullName] === undefined) {
data[fullName] = '#' + value;
}
}
/** Add an OData type tag to an JSON payload object collection (without "Edm." namespace)
* @param {Object} data - Data structure to be extended
* @param {String} name - Name of the property whose type is set
* @param {String} typeName - Type name
*/
function addTypeColNoEdm(data, name, typeName ) {
var fullName = name + '@odata.type';
if ( data[fullName] === undefined) {
if ( typeName.substring(0,4)==='Edm.') {
data[fullName] = '#Collection('+typeName.substring(4)+ ')';
} else {
data[fullName] = '#Collection('+typeName+ ')';
}
}
}
/** Add an OData type tag to an JSON payload object (without "Edm." namespace)
* @param {Object} data - Data structure to be extended
* @param {String} name - Name of the property whose type is set
* @param {String} value - Type name
*/
function addTypeNoEdm(data, name, value ) {
var fullName = name + '@odata.type';
if ( data[fullName] === undefined) {
if ( value.substring(0,4)==='Edm.') {
data[fullName] = '#' + value.substring(4);
} else {
data[fullName] = '#' + value;
}
}
return data;
}
/** Convert the date/time format of an property from the JSON payload object (without "Edm." namespace)
* @param {Object} data - Data structure to be extended
* @param propertyName - Name of the property to be changed
* @param type - Type
*/
function convertDates(data, propertyName,type) {
if (type === 'Edm.Date') {
data[propertyName] = oDataUtils.parseDate(data[propertyName], true);
} else if (type === 'Edm.DateTimeOffset') {
data[propertyName] = oDataUtils.parseDateTimeOffset(data[propertyName], true);
} else if (type === 'Edm.Duration') {
data[propertyName] = oDataUtils.parseDuration(data[propertyName], true);
} else if (type === 'Edm.Time') {
data[propertyName] = oDataUtils.parseTime(data[propertyName], true);
}
}
/** Convert the date/time format of an property from the JSON payload object
* @param {Object} data - Data structure to be extended
* @param propertyName - Name of the property to be changed
* @param type - Type
*/
function convertDatesNoEdm(data, propertyName,type) {
if (type === 'Date') {
data[propertyName] = oDataUtils.parseDate(data[propertyName], true);
} else if (type === 'DateTimeOffset') {
data[propertyName] = oDataUtils.parseDateTimeOffset(data[propertyName], true);
} else if (type === 'Duration') {
data[propertyName] = oDataUtils.parseDuration(data[propertyName], true);
} else if (type === 'Time') {
data[propertyName] = oDataUtils.parseTime(data[propertyName], true);
}
}
/** Formats a value according to Uri literal format
* @param value - Value to be formatted.
* @param type - Edm type of the value
* @returns {string} Value after formatting
*/
function formatLiteral(value, type) {
value = "" + formatRawLiteral(value, type);
value = encodeURIComponent(value.replace("'", "''"));
switch ((type)) {
case "Edm.Binary":
return "X'" + value + "'";
case "Edm.DateTime":
return "datetime" + "'" + value + "'";
case "Edm.DateTimeOffset":
return "datetimeoffset" + "'" + value + "'";
case "Edm.Decimal":
return value + "M";
case "Edm.Guid":
return "guid" + "'" + value + "'";
case "Edm.Int64":
return value + "L";
case "Edm.Float":
return value + "f";
case "Edm.Double":
return value + "D";
case "Edm.Geography":
return "geography" + "'" + value + "'";
case "Edm.Geometry":
return "geometry" + "'" + value + "'";
case "Edm.Time":
return "time" + "'" + value + "'";
case "Edm.String":
return "'" + value + "'";
default:
return value;
}
}
/** convert raw byteArray to hexString if the property is an binary property
* @param value - Value to be formatted.
* @param type - Edm type of the value
* @returns {string} Value after formatting
*/
function formatRawLiteral(value, type) {
switch (type) {
case "Edm.Binary":
return convertByteArrayToHexString(value);
default:
return value;
}
}
/** Formats the given minutes into (+/-)hh:mm format.
* @param {Number} minutes - Number of minutes to format.
* @returns {String} The minutes in (+/-)hh:mm format.
*/
function minutesToOffset(minutes) {
var sign;
if (minutes < 0) {
sign = "-";
minutes = -minutes;
} else {
sign = "+";
}
var hours = Math.floor(minutes / 60);
minutes = minutes - (60 * hours);
return sign + formatNumberWidth(hours, 2) + ":" + formatNumberWidth(minutes, 2);
}
/** Parses the JSON Date representation into a Date object.
* @param {String} value - String value.
* @returns {Date} A Date object if the value matches one; falsy otherwise.
*/
function parseJsonDateString(value) {
var arr = value && jsonDateRE.exec(value);
if (arr) {
// 0 - complete results; 1 - ticks; 2 - sign; 3 - minutes
var result = new Date(parseInt10(arr[1]));
if (arr[2]) {
var mins = parseInt10(arr[3]);
if (arr[2] === "-") {
mins = -mins;
}
// The offset is reversed to get back the UTC date, which is
// what the API will eventually have.
var current = result.getUTCMinutes();
result.setUTCMinutes(current - mins);
result.__edmType = "Edm.DateTimeOffset";
result.__offset = minutesToOffset(mins);
}
if (!isNaN(result.valueOf())) {
return result;
}
}
// Allow undefined to be returned.
}
/** Creates an object containing information for the context
* @param {String} fragments - Uri fragment
* @param {Object} model - Object describing an OData conceptual schema
* @returns {Object} type(optional) object containing type information for entity- and complex-types ( null if a typeName is a primitive)
*/
function parseContextUriFragment( fragments, model ) {
var ret = {};
if (fragments.indexOf('/') === -1 ) {
if (fragments.length === 0) {
// Capter 10.1
ret.detectedPayloadKind = PAYLOADTYPE_SVCDOC;
return ret;
} else if (fragments === 'Edm.Null') {
// Capter 10.15
ret.detectedPayloadKind = PAYLOADTYPE_VALUE;
ret.isNullProperty = true;
return ret;
} else if (fragments === 'Collection($ref)') {
// Capter 10.11
ret.detectedPayloadKind = PAYLOADTYPE_ENTITY_REF_LINKS;
return ret;
} else if (fragments === '$ref') {
// Capter 10.12
ret.detectedPayloadKind = PAYLOADTYPE_ENTITY_REF_LINK;
return ret;
} else {
//TODO check for navigation resource
}
}
ret.type = undefined;
ret.typeName = undefined;
var fragmentParts = fragments.split("/");
var type;
for(var i = 0; i < fragmentParts.length; ++i) {
var fragment = fragmentParts[i];
if (ret.typeName === undefined) {
//preparation
if ( fragment.indexOf('(') !== -1 ) {
//remove the query function, cut fragment to matching '('
var index = fragment.length - 2 ;
for ( var rCount = 1; rCount > 0 && index > 0; --index) {
if ( fragment.charAt(index)=='(') {
rCount --;
} else if ( fragment.charAt(index)==')') {
rCount ++;
}
}
if (index === 0) {
//TODO throw error
}
//remove the projected entity from the fragment; TODO decide if we want to store the projected entity
var inPharenthesis = fragment.substring(index+2,fragment.length - 1);
fragment = fragment.substring(0,index+1);
if (utils.startsWith(fragment, 'Collection')) {
ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION;
// Capter 10.14
ret.typeName = inPharenthesis;
type = lookupEntityType(ret.typeName, model);
if ( type !== null) {
ret.type = type;
continue;
}
type = lookupComplexType(ret.typeName, model);
if ( type !== null) {
ret.type = type;
continue;
}
ret.type = null;//in case of #Collection(Edm.String) only lastTypeName is filled
continue;
} else {
// projection: Capter 10.7, 10.8 and 10.9
ret.projection = inPharenthesis;
}
}
if (jsonIsPrimitiveType(fragment)) {
ret.typeName = fragment;
ret.type = null;
ret.detectedPayloadKind = PAYLOADTYPE_VALUE;
continue;
}
var container = lookupDefaultEntityContainer(model);
//check for entity
var entitySet = lookupEntitySet(container.entitySet, fragment);
if ( entitySet !== null) {
ret.typeName = entitySet.entityType;
ret.type = lookupEntityType( ret.typeName, model);
ret.name = fragment;
ret.detectedPayloadKind = PAYLOADTYPE_FEED;
// Capter 10.2
continue;
}
//check for singleton
var singleton = lookupSingleton(container.singleton, fragment);
if ( singleton !== null) {
ret.typeName = singleton.entityType;
ret.type = lookupEntityType( ret.typeName, model);
ret.name = fragment;
ret.detectedPayloadKind = PAYLOADTYPE_ENTRY;
// Capter 10.4
continue;
}
//TODO throw ERROR
} else {
//check for $entity
if (utils.endsWith(fragment, '$entity') && (ret.detectedPayloadKind === PAYLOADTYPE_FEED)) {
//TODO ret.name = fragment;
ret.detectedPayloadKind = PAYLOADTYPE_ENTRY;
// Capter 10.3 and 10.6
continue;
}
//check for derived types
if (fragment.indexOf('.') !== -1) {
// Capter 10.6
ret.typeName = fragment;
type = lookupEntityType(ret.typeName, model);
if ( type !== null) {
ret.type = type;
continue;
}
type = lookupComplexType(ret.typeName, model);
if ( type !== null) {
ret.type = type;
continue;
}
//TODO throw ERROR invalid type
}
//check for property value
if ( ret.detectedPayloadKind === PAYLOADTYPE_FEED || ret.detectedPayloadKind === PAYLOADTYPE_ENTRY) {
var property = lookupProperty(ret.type.property, fragment);
if (property !== null) {
//PAYLOADTYPE_COLLECTION
ret.typeName = property.type;
if (utils.startsWith(property.type, 'Collection')) {
ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION;
var tmp12 = property.type.substring(10+1,property.type.length - 1);
ret.typeName = tmp12;
ret.type = lookupComplexType(tmp12, model);
ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION;
} else {
ret.type = lookupComplexType(property.type, model);
ret.detectedPayloadKind = PAYLOADTYPE_PROPERTY;
}
ret.name = fragment;
// Capter 10.15
}
continue;
}
if (fragment === '$delta') {
ret.deltaKind = DELTATYPE_FEED;
continue;
} else if (utils.endsWith(fragment, '/$deletedEntity')) {
ret.deltaKind = DELTATYPE_DELETED_ENTRY;
continue;
} else if (utils.endsWith(fragment, '/$link')) {
ret.deltaKind = DELTATYPE_LINK;
continue;
} else if (utils.endsWith(fragment, '/$deletedLink')) {
ret.deltaKind = DELTATYPE_DELETED_LINK;
continue;
}
//TODO throw ERROr
}
}
return ret;
}
/** Infers the information describing the JSON payload from its metadata annotation, structure, and data model.
* @param {Object} data - Json response payload object.
* @param {Object} model - Object describing an OData conceptual schema.
* 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
* 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
* the function will report its kind as PAYLOADTYPE_FEED unless the inferFeedAsComplexType flag is set to true. This flag comes from the user request
* and allows the user to control how the library behaves with an ambigous JSON payload.
* @return Object with kind and type fields. Null if there is no metadata annotation or the payload info cannot be obtained..
*/
function createPayloadInfo(data, model) {
var metadataUri = data[contextUrlAnnotation];
if (!metadataUri || typeof metadataUri !== "string") {
return null;
}
var fragmentStart = metadataUri.lastIndexOf("#");
if (fragmentStart === -1) {
return jsonMakePayloadInfo(PAYLOADTYPE_SVCDOC);
}
var fragment = metadataUri.substring(fragmentStart + 1);
return parseContextUriFragment(fragment,model);
}
/** Gets the key of an entry.
* @param {Object} data - JSON entry.
* @param {Object} data - EDM entity model for key loockup.
* @returns {string} Entry instance key.
*/
function jsonGetEntryKey(data, entityModel) {
var entityInstanceKey;
var entityKeys = entityModel.key.propertyRef;
var type;
entityInstanceKey = "(";
if (entityKeys.length == 1) {
type = lookupProperty(entityModel.property, entityKeys[0].name).type;
entityInstanceKey += formatLiteral(data[entityKeys[0].name], type);
} else {
var first = true;
for (var i = 0; i < entityKeys.length; i++) {
if (!first) {
entityInstanceKey += ",";
} else {
first = false;
}
type = lookupProperty(entityModel.property, entityKeys[i].name).type;
entityInstanceKey += entityKeys[i].name + "=" + formatLiteral(data[entityKeys[i].name], type);
}
}
entityInstanceKey += ")";
return entityInstanceKey;
}
/** Determines whether a type name is a primitive type in a JSON payload.
* @param {String} typeName - Type name to test.
* @returns {Boolean} True if the type name an EDM primitive type or an OData spatial type; false otherwise.
*/
function jsonIsPrimitiveType(typeName) {
return isPrimitiveEdmType(typeName) || isGeographyEdmType(typeName) || isGeometryEdmType(typeName);
}
var jsonHandler = oDataHandler.handler(jsonParser, jsonSerializer, jsonMediaType, MAX_DATA_SERVICE_VERSION);
jsonHandler.recognizeDates = false;
exports.createPayloadInfo = createPayloadInfo;
exports.jsonHandler = jsonHandler;
exports.jsonParser = jsonParser;
exports.jsonSerializer = jsonSerializer;
exports.parseJsonDateString = parseJsonDateString;}, "metadata" : function(exports, module, require) {
'use strict';
/** @module odata/metadata */
var utils = require('./../utils.js');
var oDSxml = require('./../xml.js');
var odataHandler = require('./handler.js');
// imports
var contains = utils.contains;
var normalizeURI = utils.normalizeURI;
var xmlAttributes = oDSxml.xmlAttributes;
var xmlChildElements = oDSxml.xmlChildElements;
var xmlFirstChildElement = oDSxml.xmlFirstChildElement;
var xmlInnerText = oDSxml.xmlInnerText;
var xmlLocalName = oDSxml.xmlLocalName;
var xmlNamespaceURI = oDSxml.xmlNamespaceURI;
var xmlNS = oDSxml.xmlNS;
var xmlnsNS = oDSxml.xmlnsNS;
var xmlParse = oDSxml.xmlParse;
var ado = oDSxml.http + "docs.oasis-open.org/odata/"; // http://docs.oasis-open.org/odata/
var adoDs = ado + "ns"; // http://docs.oasis-open.org/odata/ns
var edmxNs = adoDs + "/edmx"; // http://docs.oasis-open.org/odata/ns/edmx
var edmNs1 = adoDs + "/edm"; // http://docs.oasis-open.org/odata/ns/edm
var odataMetaXmlNs = adoDs + "/metadata"; // http://docs.oasis-open.org/odata/ns/metadata
var MAX_DATA_SERVICE_VERSION = odataHandler.MAX_DATA_SERVICE_VERSION;
var xmlMediaType = "application/xml";
/** Creates an object that describes an element in an schema.
* @param {Array} attributes - List containing the names of the attributes allowed for this element.
* @param {Array} elements - List containing the names of the child elements allowed for this element.
* @param {Boolean} text - Flag indicating if the element's text value is of interest or not.
* @param {String} ns - Namespace to which the element belongs to.
* If a child element name ends with * then it is understood by the schema that that child element can appear 0 or more times.
* @returns {Object} Object with attributes, elements, text, and ns fields.
*/
function schemaElement(attributes, elements, text, ns) {
return {
attributes: attributes,
elements: elements,
text: text || false,
ns: ns
};
}
// It's assumed that all elements may have Documentation children and Annotation elements.
// See http://docs.oasis-open.org/odata/odata/v4.0/cs01/part3-csdl/odata-v4.0-cs01-part3-csdl.html for a CSDL reference.
var schema = {
elements: {
Action: schemaElement(
/*attributes*/["Name", "IsBound", "EntitySetPath"],
/*elements*/["ReturnType", "Parameter*", "Annotation*"]
),
ActionImport: schemaElement(
/*attributes*/["Name", "Action", "EntitySet", "Annotation*"]
),
Annotation: schemaElement(
/*attributes*/["Term", "Qualifier", "Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "AnnotationPath", "NavigationPropertyPath", "Path", "PropertyPath", "UrlRef"],
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
AnnotationPath: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Annotations: schemaElement(
/*attributes*/["Target", "Qualifier"],
/*elements*/["Annotation*"]
),
Apply: schemaElement(
/*attributes*/["Function"],
/*elements*/["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"]
),
And: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Or: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Not: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Eq: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Ne: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Gt: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Ge: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Lt: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Le: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Binary: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Bool: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Cast: schemaElement(
/*attributes*/["Type"],
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Collection: schemaElement(
/*attributes*/null,
/*elements*/["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*"]
),
ComplexType: schemaElement(
/*attributes*/["Name", "BaseType", "Abstract", "OpenType"],
/*elements*/["Property*", "NavigationProperty*", "Annotation*"]
),
Date: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
DateTimeOffset: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Decimal: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Duration: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
EntityContainer: schemaElement(
/*attributes*/["Name", "Extends"],
/*elements*/["EntitySet*", "Singleton*", "ActionImport*", "FunctionImport*", "Annotation*"]
),
EntitySet: schemaElement(
/*attributes*/["Name", "EntityType", "IncludeInServiceDocument"],
/*elements*/["NavigationPropertyBinding*", "Annotation*"]
),
EntityType: schemaElement(
/*attributes*/["Name", "BaseType", "Abstract", "OpenType", "HasStream"],
/*elements*/["Key", "Property*", "NavigationProperty*", "Annotation*"]
),
EnumMember: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
EnumType: schemaElement(
/*attributes*/["Name", "UnderlyingType", "IsFlags"],
/*elements*/["Member*"]
),
Float: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Function: schemaElement(
/*attributes*/["Name", "IsBound", "IsComposable", "EntitySetPath"],
/*elements*/["ReturnType", "Parameter*", "Annotation*"]
),
FunctionImport: schemaElement(
/*attributes*/["Name", "Function", "EntitySet", "IncludeInServiceDocument", "Annotation*"]
),
Guid: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
If: schemaElement(
/*attributes*/null,
/*elements*/["Binary*", "Bool*", "Date*", "DateTimeOffset*", "Decimal*", "Duration*", "EnumMember*", "Float*", "Guid*", "Int*", "String*", "TimeOfDay*", "And*", "Or*", "Not*", "Eq*", "Ne*", "Gt*", "Ge*", "Lt*", "Le*", "AnnotationPath*", "Apply*", "Cast*", "Collection*", "If*", "IsOf*", "LabeledElement*", "LabeledElementReference*", "Null*", "NavigationPropertyPath*", "Path*", "PropertyPath*", "Record*", "UrlRef*", "Annotation*"]
),
Int: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
IsOf: schemaElement(
/*attributes*/["Type", "MaxLength", "Precision", "Scale", "SRID", "Annotation*"],
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Key: schemaElement(
/*attributes*/null,
/*elements*/["PropertyRef*"]
),
LabeledElement: schemaElement(
/*attributes*/["Name"],
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
LabeledElementReference: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Member: schemaElement(
/*attributes*/["Name", "Value"],
/*element*/["Annotation*"]
),
NavigationProperty: schemaElement(
/*attributes*/["Name", "Type", "Nullable", "Partner", "ContainsTarget"],
/*elements*/["ReferentialConstraint*", "OnDelete", "Annotation*"]
),
NavigationPropertyBinding: schemaElement(
/*attributes*/["Path", "Target"]
),
NavigationPropertyPath: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Null: schemaElement(
/*attributes*/null,
/*elements*/["Annotation*"]
),
OnDelete: schemaElement(
/*attributes*/["Action"],
/*elements*/["Annotation*"]
),
Path: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Parameter: schemaElement(
/*attributes*/["Name", "Type", "Nullable", "MaxLength", "Precision", "Scale", "SRID"],
/*elements*/["Annotation*"]
),
Property: schemaElement(
/*attributes*/["Name", "Type", "Nullable", "MaxLength", "Precision", "Scale", "Unicode", "SRID", "DefaultValue"],
/*elements*/["Annotation*"]
),
PropertyPath: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
PropertyRef: schemaElement(
/*attributes*/["Name", "Alias"]
),
PropertyValue: schemaElement(
/*attributes*/["Property", "Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "AnnotationPath", "NavigationPropertyPath", "Path", "PropertyPath", "UrlRef"],
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
Record: schemaElement(
/*attributes*/null,
/*Elements*/["PropertyValue*", "Annotation*"]
),
ReferentialConstraint: schemaElement(
/*attributes*/["Property", "ReferencedProperty", "Annotation*"]
),
ReturnType: schemaElement(
/*attributes*/["Type", "Nullable", "MaxLength", "Precision", "Scale", "SRID"]
),
String: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
Schema: schemaElement(
/*attributes*/["Namespace", "Alias"],
/*elements*/["Action*", "Annotations*", "Annotation*", "ComplexType*", "EntityContainer", "EntityType*", "EnumType*", "Function*", "Term*", "TypeDefinition*", "Annotation*"]
),
Singleton: schemaElement(
/*attributes*/["Name", "Type"],
/*elements*/["NavigationPropertyBinding*", "Annotation*"]
),
Term: schemaElement(
/*attributes*/["Name", "Type", "BaseTerm", "DefaultValue ", "AppliesTo", "Nullable", "MaxLength", "Precision", "Scale", "SRID"],
/*elements*/["Annotation*"]
),
TimeOfDay: schemaElement(
/*attributes*/null,
/*elements*/null,
/*text*/true
),
TypeDefinition: schemaElement(
/*attributes*/["Name", "UnderlyingType", "MaxLength", "Unicode", "Precision", "Scale", "SRID"],
/*elements*/["Annotation*"]
),
UrlRef: schemaElement(
/*attributes*/null,
/*elements*/["Binary", "Bool", "Date", "DateTimeOffset", "Decimal", "Duration", "EnumMember", "Float", "Guid", "Int", "String", "TimeOfDay", "And", "Or", "Not", "Eq", "Ne", "Gt", "Ge", "Lt", "Le", "AnnotationPath", "Apply", "Cast", "Collection", "If", "IsOf", "LabeledElement", "LabeledElementReference", "Null", "NavigationPropertyPath", "Path", "PropertyPath", "Record", "UrlRef", "Annotation*"]
),
// See http://msdn.microsoft.com/en-us/library/dd541238(v=prot.10) for an EDMX reference.
Edmx: schemaElement(
/*attributes*/["Version"],
/*elements*/["DataServices", "Reference*"],
/*text*/false,
/*ns*/edmxNs
),
DataServices: schemaElement(
/*attributes*/["m:MaxDataServiceVersion", "m:DataServiceVersion"],
/*elements*/["Schema*"],
/*text*/false,
/*ns*/edmxNs
),
Reference: schemaElement(
/*attributes*/["Uri"],
/*elements*/["Include*", "IncludeAnnotations*", "Annotation*"],
/*text*/false,
/*ns*/edmxNs
),
Include: schemaElement(
/*attributes*/["Namespace", "Alias"],
/*elements*/null,
/*text*/false,
/*ns*/edmxNs
),
IncludeAnnotations: schemaElement(
/*attributes*/["TermNamespace", "Qualifier", "TargetNamespace"],
/*elements*/null,
/*text*/false,
/*ns*/edmxNs
)
}
};
/** Converts a Pascal-case identifier into a camel-case identifier.
* @param {String} text - Text to convert.
* @returns {String} Converted text.
* If the text starts with multiple uppercase characters, it is left as-is.
*/
function scriptCase(text) {
if (!text) {
return text;
}
if (text.length > 1) {
var firstTwo = text.substr(0, 2);
if (firstTwo === firstTwo.toUpperCase()) {
return text;
}
return text.charAt(0).toLowerCase() + text.substr(1);
}
return text.charAt(0).toLowerCase();
}
/** Gets the schema node for the specified element.
* @param {Object} parentSchema - Schema of the parent XML node of 'element'.
* @param candidateName - XML element name to consider.
* @returns {Object} The schema that describes the specified element; null if not found.
*/
function getChildSchema(parentSchema, candidateName) {
var elements = parentSchema.elements;
if (!elements) {
return null;
}
var i, len;
for (i = 0, len = elements.length; i < len; i++) {
var elementName = elements[i];
var multipleElements = false;
if (elementName.charAt(elementName.length - 1) === "*") {
multipleElements = true;
elementName = elementName.substr(0, elementName.length - 1);
}
if (candidateName === elementName) {
var propertyName = scriptCase(elementName);
return { isArray: multipleElements, propertyName: propertyName };
}
}
return null;
}
/** Checks whether the specifies namespace URI is one of the known CSDL namespace URIs.
* @param {String} nsURI - Namespace URI to check.
* @returns {Boolean} true if nsURI is a known CSDL namespace; false otherwise.
*/
function isEdmNamespace(nsURI) {
return nsURI === edmNs1;
}
/** Parses a CSDL document.
* @param element - DOM element to parse.
* @returns {Object} An object describing the parsed element.
*/
function parseConceptualModelElement(element) {
var localName = xmlLocalName(element);
var nsURI = xmlNamespaceURI(element);
var elementSchema = schema.elements[localName];
if (!elementSchema) {
return null;
}
if (elementSchema.ns) {
if (nsURI !== elementSchema.ns) {
return null;
}
} else if (!isEdmNamespace(nsURI)) {
return null;
}
var item = {};
var attributes = elementSchema.attributes || [];
xmlAttributes(element, function (attribute) {
var localName = xmlLocalName(attribute);
var nsURI = xmlNamespaceURI(attribute);
var value = attribute.value;
// Don't do anything with xmlns attributes.
if (nsURI === xmlnsNS) {
return;
}
// Currently, only m: for metadata is supported as a prefix in the internal schema table,
// un-prefixed element names imply one a CSDL element.
var schemaName = null;
if (isEdmNamespace(nsURI) || nsURI === null) {
schemaName = "";
} else if (nsURI === odataMetaXmlNs) {
schemaName = "m:";
}
if (schemaName !== null) {
schemaName += localName;
if (contains(attributes, schemaName)) {
item[scriptCase(localName)] = value;
}
}
});
xmlChildElements(element, function (child) {
var localName = xmlLocalName(child);
var childSchema = getChildSchema(elementSchema, localName);
if (childSchema) {
if (childSchema.isArray) {
var arr = item[childSchema.propertyName];
if (!arr) {
arr = [];
item[childSchema.propertyName] = arr;
}
arr.push(parseConceptualModelElement(child));
} else {
item[childSchema.propertyName] = parseConceptualModelElement(child);
}
}
});
if (elementSchema.text) {
item.text = xmlInnerText(element);
}
return item;
}
/** Parses a metadata document.
* @param handler - This handler.
* @param {String} text - Metadata text.
* @returns An object representation of the conceptual model.
*/
function metadataParser(handler, text) {
var doc = xmlParse(text);
var root = xmlFirstChildElement(doc);
return parseConceptualModelElement(root) || undefined;
}
exports.metadataHandler = odataHandler.handler(metadataParser, null, xmlMediaType, MAX_DATA_SERVICE_VERSION);
exports.schema = schema;
exports.scriptCase = scriptCase;
exports.getChildSchema = getChildSchema;
exports.parseConceptualModelElement = parseConceptualModelElement;
exports.metadataParser = metadataParser;}, "net" : function(exports, module, require) {
/** @module odata/net */
/*for browser*/
var utils = require('./../utils.js');
// Imports.
var defined = utils.defined;
var delay = utils.delay;
var ticks = 0;
/* Checks whether the specified request can be satisfied with a JSONP request.
* @param request - Request object to check.
* @returns {Boolean} true if the request can be satisfied; false otherwise.
* Requests that 'degrade' without changing their meaning by going through JSONP
* are considered usable.
*
* We allow data to come in a different format, as the servers SHOULD honor the Accept
* request but may in practice return content with a different MIME type.
*/
function canUseJSONP(request) {
return !(request.method && request.method !== "GET");
}
/** Creates an IFRAME tag for loading the JSONP script
* @param {String} url - The source URL of the script
* @returns {HTMLElement} The IFRAME tag
*/
function createIFrame(url) {
var iframe = window.document.createElement("IFRAME");
iframe.style.display = "none";
var attributeEncodedUrl = url.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
var html = "<html><head><script type=\"text/javascript\" src=\"" + attributeEncodedUrl + "\"><\/script><\/head><body><\/body><\/html>";
var body = window.document.getElementsByTagName("BODY")[0];
body.appendChild(iframe);
writeHtmlToIFrame(iframe, html);
return iframe;
}
/** Creates a XmlHttpRequest object.
* @returns {XmlHttpRequest} XmlHttpRequest object.
*/
function createXmlHttpRequest() {
if (window.XMLHttpRequest) {
return new window.XMLHttpRequest();
}
var exception;
if (window.ActiveXObject) {
try {
return new window.ActiveXObject("Msxml2.XMLHTTP.6.0");
} catch (_) {
try {
return new window.ActiveXObject("Msxml2.XMLHTTP.3.0");
} catch (e) {
exception = e;
}
}
} else {
exception = { message: "XMLHttpRequest not supported" };
}
throw exception;
}
/** Checks whether the specified URL is an absolute URL.
* @param {String} url - URL to check.
* @returns {Boolean} true if the url is an absolute URL; false otherwise.
*/
function isAbsoluteUrl(url) {
return url.indexOf("http://") === 0 ||
url.indexOf("https://") === 0 ||
url.indexOf("file://") === 0;
}
/** Checks whether the specified URL is local to the current context.
* @param {String} url - URL to check.
* @returns {Boolean} true if the url is a local URL; false otherwise.
*/
function isLocalUrl(url) {
if (!isAbsoluteUrl(url)) {
return true;
}
// URL-embedded username and password will not be recognized as same-origin URLs.
var location = window.location;
var locationDomain = location.protocol + "//" + location.host + "/";
return (url.indexOf(locationDomain) === 0);
}
/** Removes a callback used for a JSONP request.
* @param {String} name - Function name to remove.
* @param {Number} tick - Tick count used on the callback.
*/
function removeCallback(name, tick) {
try {
delete window[name];
} catch (err) {
window[name] = undefined;
if (tick === ticks - 1) {
ticks -= 1;
}
}
}
/** Removes an iframe.
* @param {Object} iframe - The iframe to remove.
* @returns {Object} Null value to be assigned to iframe reference.
*/
function removeIFrame(iframe) {
if (iframe) {
writeHtmlToIFrame(iframe, "");
iframe.parentNode.removeChild(iframe);
}
return null;
}
/** Reads response headers into array.
* @param {XMLHttpRequest} xhr - HTTP request with response available.
* @param {Array} headers - Target array to fill with name/value pairs.
*/
function readResponseHeaders(xhr, headers) {
var responseHeaders = xhr.getAllResponseHeaders().split(/\r?\n/);
var i, len;
for (i = 0, len = responseHeaders.length; i < len; i++) {
if (responseHeaders[i]) {
var header = responseHeaders[i].split(": ");
headers[header[0]] = header[1];
}
}
}
/** Writes HTML to an IFRAME document.
* @param {HTMLElement} iframe - The IFRAME element to write to.
* @param {String} html - The HTML to write.
*/
function writeHtmlToIFrame(iframe, html) {
var frameDocument = (iframe.contentWindow) ? iframe.contentWindow.document : iframe.contentDocument.document;
frameDocument.open();
frameDocument.write(html);
frameDocument.close();
}
exports.defaultHttpClient = {
callbackParameterName: "$callback",
formatQueryString: "$format=json",
enableJsonpCallback: false,
/** Performs a network request.
* @param {Object} request - Request description
* @param {Function} success - Success callback with the response object.
* @param {Function} error - Error callback with an error object.
* @returns {Object} Object with an 'abort' method for the operation.
*/
request: function createRequest() {
var that = this;
return function (request, success, error) {
var result = {};
var xhr = null;
var done = false;
var iframe;
result.abort = function () {
iframe = removeIFrame(iframe);
if (done) {
return;
}
done = true;
if (xhr) {
xhr.abort();
xhr = null;
}
error({ message: "Request aborted" });
};
var handleTimeout = function () {
iframe = removeIFrame(iframe);
if (!done) {
done = true;
xhr = null;
error({ message: "Request timed out" });
}
};
var name;
var url = request.requestUri;
var enableJsonpCallback = defined(request.enableJsonpCallback, that.enableJsonpCallback);
var callbackParameterName = defined(request.callbackParameterName, that.callbackParameterName);
var formatQueryString = defined(request.formatQueryString, that.formatQueryString);
if (!enableJsonpCallback || isLocalUrl(url)) {
xhr = createXmlHttpRequest();
xhr.onreadystatechange = function () {
if (done || xhr === null || xhr.readyState !== 4) {
return;
}
// Workaround for XHR behavior on IE.
var statusText = xhr.statusText;
var statusCode = xhr.status;
if (statusCode === 1223) {
statusCode = 204;
statusText = "No Content";
}
var headers = [];
readResponseHeaders(xhr, headers);
var response = { requestUri: url, statusCode: statusCode, statusText: statusText, headers: headers, body: xhr.responseText };
done = true;
xhr = null;
if (statusCode >= 200 && statusCode <= 299) {
success(response);
} else {
error({ message: "HTTP request failed", request: request, response: response });
}
};
xhr.open(request.method || "GET", url, true, request.user, request.password);
// Set the name/value pairs.
if (request.headers) {
for (name in request.headers) {
xhr.setRequestHeader(name, request.headers[name]);
}
}
// Set NTLM/Kerberos auth token
if (request.withCredentials) {
xhr.withCredentials = true;
}
// Set the timeout if available.
if (request.timeoutMS) {
xhr.timeout = request.timeoutMS;
xhr.ontimeout = handleTimeout;
}
if (typeof request.body === 'undefined') {
xhr.send();
}
else {
xhr.send(request.body);
}
} else {
if (!canUseJSONP(request)) {
throw { message: "Request is not local and cannot be done through JSONP." };
}
var tick = ticks;
ticks += 1;
var tickText = tick.toString();
var succeeded = false;
var timeoutId;
name = "handleJSONP_" + tickText;
window[name] = function (data) {
iframe = removeIFrame(iframe);
if (!done) {
succeeded = true;
window.clearTimeout(timeoutId);
removeCallback(name, tick);
// Workaround for IE8 and IE10 below where trying to access data.constructor after the IFRAME has been removed
// throws an "unknown exception"
if (window.ActiveXObject) {
data = window.JSON.parse(window.JSON.stringify(data));
}
var headers;
if (!formatQueryString || formatQueryString == "$format=json") {
headers = { "Content-Type": "application/json;odata.metadata=minimal", "OData-Version": "4.0" };
} else {
// the formatQueryString should be in the format of "$format=xxx", xxx should be one of the application/json;odata.metadata=minimal(none or full)
// set the content-type with the string xxx which stars from index 8.
headers = { "Content-Type": formatQueryString.substring(8), "OData-Version": "4.0" };
}
// Call the success callback in the context of the parent window, instead of the IFRAME
delay(function () {
removeIFrame(iframe);
success({ body: data, statusCode: 200, headers: headers });
});
}
};
// Default to two minutes before timing out, 1000 ms * 60 * 2 = 120000.
var timeoutMS = (request.timeoutMS) ? request.timeoutMS : 120000;
timeoutId = window.setTimeout(handleTimeout, timeoutMS);
var queryStringParams = callbackParameterName + "=parent." + name;
if (formatQueryString) {
queryStringParams += "&" + formatQueryString;
}
var qIndex = url.indexOf("?");
if (qIndex === -1) {
url = url + "?" + queryStringParams;
} else if (qIndex === url.length - 1) {
url = url + queryStringParams;
} else {
url = url + "&" + queryStringParams;
}
iframe = createIFrame(url);
}
return result;
}
}()
};
exports.canUseJSONP = canUseJSONP;
exports.isAbsoluteUrl = isAbsoluteUrl;
exports.isLocalUrl = isLocalUrl;}, "odatautils" : function(exports, module, require) {
'use strict';
/** @module odata/utils */
var utils = require('./../utils.js');
// Imports
var assigned = utils.assigned;
var contains = utils.contains;
var find = utils.find;
var isArray = utils.isArray;
var isDate = utils.isDate;
var isObject = utils.isObject;
var parseInt10 = utils.parseInt10;
/** Gets the type name of a data item value that belongs to a feed, an entry, a complex type property, or a collection property
* @param {string} value - Value of the data item from which the type name is going to be retrieved.
* @param {object} [metadata] - Object containing metadata about the data tiem.
* @returns {string} Data item type name; null if the type name cannot be found within the value or the metadata
* This function will first try to get the type name from the data item's value itself if it is an object with a __metadata property; otherwise
* it will try to recover it from the metadata. If both attempts fail, it will return null.
*/
var dataItemTypeName = function (value, metadata) {
var valueTypeName = ((value && value.__metadata) || {}).type;
return valueTypeName || (metadata ? metadata.type : null);
};
var EDM = "Edm.";
var EDM_BOOLEAN = EDM + "Boolean";
var EDM_BYTE = EDM + "Byte";
var EDM_SBYTE = EDM + "SByte";
var EDM_INT16 = EDM + "Int16";
var EDM_INT32 = EDM + "Int32";
var EDM_INT64 = EDM + "Int64";
var EDM_SINGLE = EDM + "Single";
var EDM_DOUBLE = EDM + "Double";
var EDM_DECIMAL = EDM + "Decimal";
var EDM_STRING = EDM + "String";
var EDM_BINARY = EDM + "Binary";
var EDM_DATE = EDM + "Date";
var EDM_DATETIMEOFFSET = EDM + "DateTimeOffset";
var EDM_DURATION = EDM + "Duration";
var EDM_GUID = EDM + "Guid";
var EDM_TIMEOFDAY = EDM + "Time";
var GEOGRAPHY = "Geography";
var EDM_GEOGRAPHY = EDM + GEOGRAPHY;
var EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY + "Point";
var EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY + "LineString";
var EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY + "Polygon";
var EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY + "Collection";
var EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY + "MultiPolygon";
var EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY + "MultiLineString";
var EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY + "MultiPoint";
var GEOGRAPHY_POINT = GEOGRAPHY + "Point";
var GEOGRAPHY_LINESTRING = GEOGRAPHY + "LineString";
var GEOGRAPHY_POLYGON = GEOGRAPHY + "Polygon";
var GEOGRAPHY_COLLECTION = GEOGRAPHY + "Collection";
var GEOGRAPHY_MULTIPOLYGON = GEOGRAPHY + "MultiPolygon";
var GEOGRAPHY_MULTILINESTRING = GEOGRAPHY + "MultiLineString";
var GEOGRAPHY_MULTIPOINT = GEOGRAPHY + "MultiPoint";
var GEOMETRY = "Geometry";
var EDM_GEOMETRY = EDM + GEOMETRY;
var EDM_GEOMETRY_POINT = EDM_GEOMETRY + "Point";
var EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY + "LineString";
var EDM_GEOMETRY_POLYGON = EDM_GEOMETRY + "Polygon";
var EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY + "Collection";
var EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY + "MultiPolygon";
var EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY + "MultiLineString";
var EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY + "MultiPoint";
var GEOMETRY_POINT = GEOMETRY + "Point";
var GEOMETRY_LINESTRING = GEOMETRY + "LineString";
var GEOMETRY_POLYGON = GEOMETRY + "Polygon";
var GEOMETRY_COLLECTION = GEOMETRY + "Collection";
var GEOMETRY_MULTIPOLYGON = GEOMETRY + "MultiPolygon";
var GEOMETRY_MULTILINESTRING = GEOMETRY + "MultiLineString";
var GEOMETRY_MULTIPOINT = GEOMETRY + "MultiPoint";
var GEOJSON_POINT = "Point";
var GEOJSON_LINESTRING = "LineString";
var GEOJSON_POLYGON = "Polygon";
var GEOJSON_MULTIPOINT = "MultiPoint";
var GEOJSON_MULTILINESTRING = "MultiLineString";
var GEOJSON_MULTIPOLYGON = "MultiPolygon";
var GEOJSON_GEOMETRYCOLLECTION = "GeometryCollection";
var primitiveEdmTypes = [
EDM_STRING,
EDM_INT32,
EDM_INT64,
EDM_BOOLEAN,
EDM_DOUBLE,
EDM_SINGLE,
EDM_DATE,
EDM_DATETIMEOFFSET,
EDM_DURATION,
EDM_TIMEOFDAY,
EDM_DECIMAL,
EDM_GUID,
EDM_BYTE,
EDM_INT16,
EDM_SBYTE,
EDM_BINARY
];
var geometryEdmTypes = [
EDM_GEOMETRY,
EDM_GEOMETRY_POINT,
EDM_GEOMETRY_LINESTRING,
EDM_GEOMETRY_POLYGON,
EDM_GEOMETRY_COLLECTION,
EDM_GEOMETRY_MULTIPOLYGON,
EDM_GEOMETRY_MULTILINESTRING,
EDM_GEOMETRY_MULTIPOINT
];
var geometryTypes = [
GEOMETRY,
GEOMETRY_POINT,
GEOMETRY_LINESTRING,
GEOMETRY_POLYGON,
GEOMETRY_COLLECTION,
GEOMETRY_MULTIPOLYGON,
GEOMETRY_MULTILINESTRING,
GEOMETRY_MULTIPOINT
];
var geographyEdmTypes = [
EDM_GEOGRAPHY,
EDM_GEOGRAPHY_POINT,
EDM_GEOGRAPHY_LINESTRING,
EDM_GEOGRAPHY_POLYGON,
EDM_GEOGRAPHY_COLLECTION,
EDM_GEOGRAPHY_MULTIPOLYGON,
EDM_GEOGRAPHY_MULTILINESTRING,
EDM_GEOGRAPHY_MULTIPOINT
];
var geographyTypes = [
GEOGRAPHY,
GEOGRAPHY_POINT,
GEOGRAPHY_LINESTRING,
GEOGRAPHY_POLYGON,
GEOGRAPHY_COLLECTION,
GEOGRAPHY_MULTIPOLYGON,
GEOGRAPHY_MULTILINESTRING,
GEOGRAPHY_MULTIPOINT
];
/** Invokes a function once per schema in metadata.
* @param metadata - Metadata store; one of edmx, schema, or an array of any of them.
* @param {Function} callback - Callback function to invoke once per schema.
* @returns The first truthy value to be returned from the callback; null or the last falsy value otherwise.
*/
function forEachSchema(metadata, callback) {
if (!metadata) {
return null;
}
if (isArray(metadata)) {
var i, len, result;
for (i = 0, len = metadata.length; i < len; i++) {
result = forEachSchema(metadata[i], callback);
if (result) {
return result;
}
}
return null;
} else {
if (metadata.dataServices) {
return forEachSchema(metadata.dataServices.schema, callback);
}
return callback(metadata);
}
}
/** Formats a millisecond and a nanosecond value into a single string.
* @param {Number} ms - Number of milliseconds to format.
* @param {Number} ns - Number of nanoseconds to format.
* @returns {String} Formatted text.
* If the value is already as string it's returned as-is.
*/
function formatMilliseconds(ms, ns) {
// Avoid generating milliseconds if not necessary.
if (ms === 0) {
ms = "";
} else {
ms = "." + formatNumberWidth(ms.toString(), 3);
}
if (ns > 0) {
if (ms === "") {
ms = ".000";
}
ms += formatNumberWidth(ns.toString(), 4);
}
return ms;
}
function formatDateTimeOffsetJSON(value) {
return "\/Date(" + value.getTime() + ")\/";
}
/** Formats a DateTime or DateTimeOffset value a string.
* @param {Date} value - Value to format
* @returns {String} Formatted text.
* If the value is already as string it's returned as-is
´*/
function formatDateTimeOffset(value) {
if (typeof value === "string") {
return value;
}
var hasOffset = isDateTimeOffset(value);
var offset = getCanonicalTimezone(value.__offset);
if (hasOffset && offset !== "Z") {
// We're about to change the value, so make a copy.
value = new Date(value.valueOf());
var timezone = parseTimezone(offset);
var hours = value.getUTCHours() + (timezone.d * timezone.h);
var minutes = value.getUTCMinutes() + (timezone.d * timezone.m);
value.setUTCHours(hours, minutes);
} else if (!hasOffset) {
// Don't suffix a 'Z' for Edm.DateTime values.
offset = "";
}
var year = value.getUTCFullYear();
var month = value.getUTCMonth() + 1;
var sign = "";
if (year <= 0) {
year = -(year - 1);
sign = "-";
}
var ms = formatMilliseconds(value.getUTCMilliseconds(), value.__ns);
return sign +
formatNumberWidth(year, 4) + "-" +
formatNumberWidth(month, 2) + "-" +
formatNumberWidth(value.getUTCDate(), 2) + "T" +
formatNumberWidth(value.getUTCHours(), 2) + ":" +
formatNumberWidth(value.getUTCMinutes(), 2) + ":" +
formatNumberWidth(value.getUTCSeconds(), 2) +
ms + offset;
}
/** Converts a duration to a string in xsd:duration format.
* @param {Object} value - Object with ms and __edmType properties.
* @returns {String} String representation of the time object in xsd:duration format.
*/
function formatDuration(value) {
var ms = value.ms;
var sign = "";
if (ms < 0) {
sign = "-";
ms = -ms;
}
var days = Math.floor(ms / 86400000);
ms -= 86400000 * days;
var hours = Math.floor(ms / 3600000);
ms -= 3600000 * hours;
var minutes = Math.floor(ms / 60000);
ms -= 60000 * minutes;
var seconds = Math.floor(ms / 1000);
ms -= seconds * 1000;
return sign + "P" +
formatNumberWidth(days, 2) + "DT" +
formatNumberWidth(hours, 2) + "H" +
formatNumberWidth(minutes, 2) + "M" +
formatNumberWidth(seconds, 2) +
formatMilliseconds(ms, value.ns) + "S";
}
/** Formats the specified value to the given width.
* @param {Number} value - Number to format (non-negative).
* @param {Number} width - Minimum width for number.
* @param {Boolean} append - Flag indicating if the value is padded at the beginning (false) or at the end (true).
* @returns {String} Text representation.
*/
function formatNumberWidth(value, width, append) {
var result = value.toString(10);
while (result.length < width) {
if (append) {
result += "0";
} else {
result = "0" + result;
}
}
return result;
}
/** Gets the canonical timezone representation.
* @param {String} timezone - Timezone representation.
* @returns {String} An 'Z' string if the timezone is absent or 0; the timezone otherwise.
*/
function getCanonicalTimezone(timezone) {
return (!timezone || timezone === "Z" || timezone === "+00:00" || timezone === "-00:00") ? "Z" : timezone;
}
/** Gets the type of a collection type name.
* @param {String} typeName - Type name of the collection.
* @returns {String} Type of the collection; null if the type name is not a collection type.
*/
function getCollectionType(typeName) {
if (typeof typeName === "string") {
var end = typeName.indexOf(")", 10);
if (typeName.indexOf("Collection(") === 0 && end > 0) {
return typeName.substring(11, end);
}
}
return null;
}
/** Sends a request containing OData payload to a server.
* @param request - Object that represents the request to be sent..
* @param success - Callback for a successful read operation.
* @param error - Callback for handling errors.
* @param handler - Handler for data serialization.
* @param httpClient - HTTP client layer.
* @param context - Context used for processing the request
*/
function invokeRequest(request, success, error, handler, httpClient, context) {
return httpClient.request(request, function (response) {
try {
if (response.headers) {
normalizeHeaders(response.headers);
}
if (response.data === undefined && response.statusCode !== 204) {
handler.read(response, context);
}
} catch (err) {
if (err.request === undefined) {
err.request = request;
}
if (err.response === undefined) {
err.response = response;
}
error(err);
return;
}
// errors in success handler for sync requests result in error handler calls. So here we fix this.
try {
success(response.data, response);
} catch (err) {
err.bIsSuccessHandlerError = true;
throw err;
}
}, error);
}
/** Tests whether a value is a batch object in the library's internal representation.
* @param value - Value to test.
* @returns {Boolean} True is the value is a batch object; false otherwise.
*/
function isBatch(value) {
return isComplex(value) && isArray(value.__batchRequests);
}
// Regular expression used for testing and parsing for a collection type.
var collectionTypeRE = /Collection\((.*)\)/;
/** Tests whether a value is a collection value in the library's internal representation.
* @param value - Value to test.
* @param {String} typeName - Type name of the value. This is used to disambiguate from a collection property value.
* @returns {Boolean} True is the value is a feed value; false otherwise.
*/
function isCollection(value, typeName) {
var colData = value && value.results || value;
return !!colData &&
(isCollectionType(typeName)) ||
(!typeName && isArray(colData) && !isComplex(colData[0]));
}
/** Checks whether the specified type name is a collection type.
* @param {String} typeName - Name of type to check.
* @returns {Boolean} True if the type is the name of a collection type; false otherwise.
*/
function isCollectionType(typeName) {
return collectionTypeRE.test(typeName);
}
/** Tests whether a value is a complex type value in the library's internal representation.
* @param value - Value to test.
* @returns {Boolean} True is the value is a complex type value; false otherwise.
*/
function isComplex(value) {
return !!value &&
isObject(value) &&
!isArray(value) &&
!isDate(value);
}
/** Checks whether a Date object is DateTimeOffset value
* @param {Date} value - Value to check
* @returns {Boolean} true if the value is a DateTimeOffset, false otherwise.
*/
function isDateTimeOffset(value) {
return (value.__edmType === "Edm.DateTimeOffset" || (!value.__edmType && value.__offset));
}
/** Tests whether a value is a deferred navigation property in the library's internal representation.
* @param value - Value to test.
* @returns {Boolean} True is the value is a deferred navigation property; false otherwise.
*/
function isDeferred(value) {
if (!value && !isComplex(value)) {
return false;
}
var metadata = value.__metadata || {};
var deferred = value.__deferred || {};
return !metadata.type && !!deferred.uri;
}
/** Tests whether a value is an entry object in the library's internal representation.
* @param value - Value to test.
* @returns {Boolean} True is the value is an entry object; false otherwise.
*/
function isEntry(value) {
return isComplex(value) && value.__metadata && "uri" in value.__metadata;
}
/** Tests whether a value is a feed value in the library's internal representation.
* @param value - Value to test.
* @param {String} typeName - Type name of the value. This is used to disambiguate from a collection property value.
* @returns {Boolean} True is the value is a feed value; false otherwise.
*/
function isFeed(value, typeName) {
var feedData = value && value.results || value;
return isArray(feedData) && (
(!isCollectionType(typeName)) &&
(isComplex(feedData[0]))
);
}
/** Checks whether the specified type name is a geography EDM type.
* @param {String} typeName - Name of type to check.
* @returns {Boolean} True if the type is a geography EDM type; false otherwise.
*/
function isGeographyEdmType(typeName) {
//check with edm
return contains(geographyEdmTypes, typeName) ||
(typeName.indexOf('.') === -1 && contains(geographyTypes, typeName));
}
/** Checks whether the specified type name is a geometry EDM type.
* @param {String} typeName - Name of type to check.
* @returns {Boolean} True if the type is a geometry EDM type; false otherwise.
*/
function isGeometryEdmType(typeName) {
return contains(geometryEdmTypes, typeName) ||
(typeName.indexOf('.') === -1 && contains(geometryTypes, typeName));
}
/** Tests whether a value is a named stream value in the library's internal representation.
* @param value - Value to test.
* @returns {Boolean} True is the value is a named stream; false otherwise.
*/
function isNamedStream(value) {
if (!value && !isComplex(value)) {
return false;
}
var metadata = value.__metadata;
var mediaResource = value.__mediaresource;
return !metadata && !!mediaResource && !!mediaResource.media_src;
}
/** Tests whether a value is a primitive type value in the library's internal representation.
* @param value - Value to test.
* @returns {Boolean} True is the value is a primitive type value.
* Date objects are considered primitive types by the library.
*/
function isPrimitive(value) {
return isDate(value) ||
typeof value === "string" ||
typeof value === "number" ||
typeof value === "boolean";
}
/** Checks whether the specified type name is a primitive EDM type.
* @param {String} typeName - Name of type to check.
* @returns {Boolean} True if the type is a primitive EDM type; false otherwise.
*/
function isPrimitiveEdmType(typeName) {
return contains(primitiveEdmTypes, typeName);
}
/** Gets the kind of a navigation property value.
* @param value - Value of the navigation property.
* @param {Object} [propertyModel] - Object that describes the navigation property in an OData conceptual schema.
* @returns {String} String value describing the kind of the navigation property; null if the kind cannot be determined.
*/
function navigationPropertyKind(value, propertyModel) {
if (isDeferred(value)) {
return "deferred";
}
if (isEntry(value)) {
return "entry";
}
if (isFeed(value)) {
return "feed";
}
if (propertyModel && propertyModel.relationship) {
if (value === null || value === undefined || !isFeed(value)) {
return "entry";
}
return "feed";
}
return null;
}
/** Looks up a property by name.
* @param {Array} properties - Array of property objects as per EDM metadata (may be null)
* @param {String} name - Name to look for.
* @returns {Object} The property object; null if not found.
*/
function lookupProperty(properties, name) {
return find(properties, function (property) {
return property.name === name;
});
}
/** Looks up a type object by name.
* @param {String} name - Name, possibly null or empty.
* @param metadata - Metadata store; one of edmx, schema, or an array of any of them.
* @param {String} kind - Kind of object to look for as per EDM metadata.
* @returns An type description if the name is found; null otherwise
*/
function lookupInMetadata(name, metadata, kind) {
return (name) ? forEachSchema(metadata, function (schema) {
return lookupInSchema(name, schema, kind);
}) : null;
}
/** Looks up a entity set by name.
* @param {Array} entitySets - Array of entity set objects as per EDM metadata( may be null)
* @param {String} name - Name to look for.
* @returns {Object} The entity set object; null if not found.
*/
function lookupEntitySet(entitySets, name) {
return find(entitySets, function (entitySet) {
return entitySet.name === name;
});
}
/** Looks up a entity set by name.
* @param {Array} singletons - Array of entity set objects as per EDM metadata (may be null)
* @param {String} name - Name to look for.
* @returns {Object} The entity set object; null if not found.
*/
function lookupSingleton(singletons, name) {
return find(singletons, function (singleton) {
return singleton.name === name;
});
}
/** Looks up an action by name.
* @param {String} name - Name, possibly null or empty.
* @param metadata - Metadata store; one of edmx, schema, or an array of any of them.
* @returns An action description if the name is found; null otherwise.
*/
function lookupAction(name, metadata) {
return lookupInMetadata(name, metadata, "action");
}
/** Looks up a function by name.
* @param {String} name - Name, possibly null or empty.
* @param metadata - Metadata store; one of edmx, schema, or an array of any of them.
* @returns A function description if the name is found; null otherwise.
*/
function lookupFunction(name, metadata) {
return lookupInMetadata(name, metadata, "function");
}
/** Looks up a complex type object by name.
* @param {String} name - Name, possibly null or empty.
* @param metadata - Metadata store; one of edmx, schema, or an array of any of them.
* @returns A complex type description if the name is found; null otherwise.
*/
function lookupComplexType(name, metadata) {
return lookupInMetadata(name, metadata, "complexType");
}
/** Looks up an entity type object by name.
* @param {String} name - Name, possibly null or empty.
* @param metadata - Metadata store; one of edmx, schema, or an array of any of them.
* @returns An entity type description if the name is found; null otherwise.
*/
function lookupEntityType(name, metadata) {
return lookupInMetadata(name, metadata, "entityType");
}
/** Looks up an
* @param metadata - Metadata store; one of edmx, schema, or an array of any of them.
* @returns An entity container description if the name is found; null otherwise.
*/
function lookupDefaultEntityContainer(metadata) {
return forEachSchema(metadata, function (schema) {
if (isObject(schema.entityContainer)) {
return schema.entityContainer;
}
});
}
/** Looks up an entity container object by name.
* @param {String} name - Name, possibly null or empty.
* @param metadata - Metadata store; one of edmx, schema, or an array of any of them.
* @returns An entity container description if the name is found; null otherwise.
*/
function lookupEntityContainer(name, metadata) {
return lookupInMetadata(name, metadata, "entityContainer");
}
/** Looks up a function import by name.
* @param {Array} functionImports - Array of function import objects as per EDM metadata (May be null)
* @param {String} name - Name to look for.
* @returns {Object} The function import object; null if not found.
*/
function lookupFunctionImport(functionImports, name) {
return find(functionImports, function (functionImport) {
return functionImport.name === name;
});
}
/** Looks up an action import by name.
* @param {Array} actionImports - Array of action import objects as per EDM metadata (May be null)
* @param {String} name - Name to look for.
* @returns {Object} The action import object; null if not found.
*/
function lookupActionImport(actionImports, name) {
return find(actionImports, function (actionImport) {
return actionImport.name === name;
});
}
/** Looks up the target entity type for a navigation property.
* @param {Object} navigationProperty -
* @param {Object} metadata -
* @returns {String} The entity type name for the specified property, null if not found.
*/
function lookupNavigationPropertyType(navigationProperty, metadata) {
var result = null;
if (navigationProperty) {
var rel = navigationProperty.relationship;
var association = forEachSchema(metadata, function (schema) {
// The name should be the namespace qualified name in 'ns'.'type' format.
var nameOnly = removeNamespace(schema.namespace, rel);
var associations = schema.association;
if (nameOnly && associations) {
var i, len;
for (i = 0, len = associations.length; i < len; i++) {
if (associations[i].name === nameOnly) {
return associations[i];
}
}
}
return null;
});
if (association) {
var end = association.end[0];
if (end.role !== navigationProperty.toRole) {
end = association.end[1];
// For metadata to be valid, end.role === navigationProperty.toRole now.
}
result = end.type;
}
}
return result;
}
/** Looks up the target entityset name for a navigation property.
* @param {Object} navigationProperty -
* @param {Object} sourceEntitySetName -
* @param {Object} metadata -
* metadata
* @returns {String} The entityset name for the specified property, null if not found.
*/
function lookupNavigationPropertyEntitySet(navigationProperty, sourceEntitySetName, metadata) {
if (navigationProperty) {
var rel = navigationProperty.relationship;
var associationSet = forEachSchema(metadata, function (schema) {
var containers = schema.entityContainer;
for (var i = 0; i < containers.length; i++) {
var associationSets = containers[i].associationSet;
if (associationSets) {
for (var j = 0; j < associationSets.length; j++) {
if (associationSets[j].association == rel) {
return associationSets[j];
}
}
}
}
return null;
});
if (associationSet && associationSet.end[0] && associationSet.end[1]) {
return (associationSet.end[0].entitySet == sourceEntitySetName) ? associationSet.end[1].entitySet : associationSet.end[0].entitySet;
}
}
return null;
}
/** Gets the entitySet info, container name and functionImports for an entitySet
* @param {Object} entitySetName -
* @param {Object} metadata -
* @returns {Object} The info about the entitySet.
*/
function getEntitySetInfo(entitySetName, metadata) {
var info = forEachSchema(metadata, function (schema) {
var container = schema.entityContainer;
var entitySets = container.entitySet;
if (entitySets) {
for (var j = 0; j < entitySets.length; j++) {
if (entitySets[j].name == entitySetName) {
return { entitySet: entitySets[j], containerName: container.name, functionImport: container.functionImport };
}
}
}
return null;
});
return info;
}
/** Given an expected namespace prefix, removes it from a full name.
* @param {String} ns - Expected namespace.
* @param {String} fullName - Full name in 'ns'.'name' form.
* @returns {String} The local name, null if it isn't found in the expected namespace.
*/
function removeNamespace(ns, fullName) {
if (fullName.indexOf(ns) === 0 && fullName.charAt(ns.length) === ".") {
return fullName.substr(ns.length + 1);
}
return null;
}
/** Looks up a schema object by name.
* @param {String} name - Name (assigned).
* @param schema - Schema object as per EDM metadata.
* @param {String} kind - Kind of object to look for as per EDM metadata.
* @returns An entity type description if the name is found; null otherwise.
*/
function lookupInSchema(name, schema, kind) {
if (name && schema) {
// The name should be the namespace qualified name in 'ns'.'type' format.
var nameOnly = removeNamespace(schema.namespace, name);
if (nameOnly) {
return find(schema[kind], function (item) {
return item.name === nameOnly;
});
}
}
return null;
}
/** Compares to version strings and returns the higher one.
* @param {String} left - Version string in the form "major.minor.rev"
* @param {String} right - Version string in the form "major.minor.rev"
* @returns {String} The higher version string.
*/
function maxVersion(left, right) {
if (left === right) {
return left;
}
var leftParts = left.split(".");
var rightParts = right.split(".");
var len = (leftParts.length >= rightParts.length) ?
leftParts.length :
rightParts.length;
for (var i = 0; i < len; i++) {
var leftVersion = leftParts[i] && parseInt10(leftParts[i]);
var rightVersion = rightParts[i] && parseInt10(rightParts[i]);
if (leftVersion > rightVersion) {
return left;
}
if (leftVersion < rightVersion) {
return right;
}
}
}
var normalHeaders = {
// Headers shared by request and response
"content-type": "Content-Type",
"content-encoding": "Content-Encoding",
"content-length": "Content-Length",
"odata-version": "OData-Version",
// Headers used by request
"accept": "Accept",
"accept-charset": "Accept-Charset",
"if-match": "If-Match",
"if-none-match": "If-None-Match",
"odata-isolation": "OData-Isolation",
"odata-maxversion": "OData-MaxVersion",
"prefer": "Prefer",
"content-id": "Content-ID",
"content-transfer-encoding": "Content-Transfer-Encoding",
// Headers used by response
"etag": "ETag",
"location": "Location",
"odata-entityid": "OData-EntityId",
"preference-applied": "Preference-Applied",
"retry-after": "Retry-After"
};
/** Normalizes headers so they can be found with consistent casing.
* @param {Object} headers - Dictionary of name/value pairs.
*/
function normalizeHeaders(headers) {
for (var name in headers) {
var lowerName = name.toLowerCase();
var normalName = normalHeaders[lowerName];
if (normalName && name !== normalName) {
var val = headers[name];
delete headers[name];
headers[normalName] = val;
}
}
}
/** Parses a string into a boolean value.
* @param propertyValue - Value to parse.
* @returns {Boolean} true if the property value is 'true'; false otherwise.
*/
function parseBool(propertyValue) {
if (typeof propertyValue === "boolean") {
return propertyValue;
}
return typeof propertyValue === "string" && propertyValue.toLowerCase() === "true";
}
// The captured indices for this expression are:
// 0 - complete input
// 1,2,3 - year with optional minus sign, month, day
// 4,5,6 - hours, minutes, seconds
// 7 - optional milliseconds
// 8 - everything else (presumably offset information)
var parseDateTimeRE = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d+))?(.*)$/;
/** Parses a string into a DateTime value.
* @param {String} value - Value to parse.
* @param {Boolean} withOffset - Whether offset is expected.
* @param {Boolean} nullOnError - return null instead of throwing an exception
* @returns {Date} The parsed value.
*/
function parseDateTimeMaybeOffset(value, withOffset, nullOnError) {
// We cannot parse this in cases of failure to match or if offset information is specified.
var parts = parseDateTimeRE.exec(value);
var offset = (parts) ? getCanonicalTimezone(parts[8]) : null;
if (!parts || (!withOffset && offset !== "Z")) {
if (nullOnError) {
return null;
}
throw { message: "Invalid date/time value" };
}
// Pre-parse years, account for year '0' being invalid in dateTime.
var year = parseInt10(parts[1]);
if (year <= 0) {
year++;
}
// Pre-parse optional milliseconds, fill in default. Fail if value is too precise.
var ms = parts[7];
var ns = 0;
if (!ms) {
ms = 0;
} else {
if (ms.length > 7) {
if (nullOnError) {
return null;
}
throw { message: "Cannot parse date/time value to given precision." };
}
ns = formatNumberWidth(ms.substring(3), 4, true);
ms = formatNumberWidth(ms.substring(0, 3), 3, true);
ms = parseInt10(ms);
ns = parseInt10(ns);
}
// Pre-parse other time components and offset them if necessary.
var hours = parseInt10(parts[4]);
var minutes = parseInt10(parts[5]);
var seconds = parseInt10(parts[6]) || 0;
if (offset !== "Z") {
// The offset is reversed to get back the UTC date, which is
// what the API will eventually have.
var timezone = parseTimezone(offset);
var direction = -(timezone.d);
hours += timezone.h * direction;
minutes += timezone.m * direction;
}
// Set the date and time separately with setFullYear, so years 0-99 aren't biased like in Date.UTC.
var result = new Date();
result.setUTCFullYear(
year, // Year.
parseInt10(parts[2]) - 1, // Month (zero-based for Date.UTC and setFullYear).
parseInt10(parts[3]) // Date.
);
result.setUTCHours(hours, minutes, seconds, ms);
if (isNaN(result.valueOf())) {
if (nullOnError) {
return null;
}
throw { message: "Invalid date/time value" };
}
if (withOffset) {
result.__edmType = "Edm.DateTimeOffset";
result.__offset = offset;
}
if (ns) {
result.__ns = ns;
}
return result;
}
/** Parses a string into a Date object.
* @param {String} propertyValue - Value to parse.
* @param {Boolean} nullOnError - return null instead of throwing an exception
* @returns {Date} The parsed with year, month, day set, time values are set to 0
*/
function parseDate(propertyValue, nullOnError) {
var parts = propertyValue.split('-');
if (parts.length != 3 && nullOnError) {
return null;
}
return new Date(
parseInt10(parts[0]), // Year.
parseInt10(parts[1]) - 1, // Month (zero-based for Date.UTC and setFullYear).
parseInt10(parts[2],
0,0,0,0) // Date.
);
}
var parseTimeOfDayRE = /^(\d+):(\d+)(:(\d+)(.(\d+))?)?$/;
/**Parses a time into a Date object.
* @param propertyValue
* @param {Boolean} nullOnError - return null instead of throwing an exception
* @returns {{h: Number, m: Number, s: Number, ms: Number}}
*/
function parseTimeOfDay(propertyValue, nullOnError) {
var parts = parseTimeOfDayRE.exec(propertyValue);
return {
'h' :parseInt10(parts[1]),
'm' :parseInt10(parts[2]),
's' :parseInt10(parts[4]),
'ms' :parseInt10(parts[6])
};
}
/** Parses a string into a DateTimeOffset value.
* @param {String} propertyValue - Value to parse.
* @param {Boolean} nullOnError - return null instead of throwing an exception
* @returns {Date} The parsed value.
* The resulting object is annotated with an __edmType property and
* an __offset property reflecting the original intended offset of
* the value. The time is adjusted for UTC time, as the current
* timezone-aware Date APIs will only work with the local timezone.
*/
function parseDateTimeOffset(propertyValue, nullOnError) {
return parseDateTimeMaybeOffset(propertyValue, true, nullOnError);
}
// The captured indices for this expression are:
// 0 - complete input
// 1 - direction
// 2,3,4 - years, months, days
// 5,6,7,8 - hours, minutes, seconds, miliseconds
var parseTimeRE = /^([+-])?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?/;
function isEdmDurationValue(value) {
parseTimeRE.test(value);
}
/** Parses a string in xsd:duration format.
* @param {String} duration - Duration value.
* This method will throw an exception if the input string has a year or a month component.
* @returns {Object} Object representing the time
*/
function parseDuration(duration) {
var parts = parseTimeRE.exec(duration);
if (parts === null) {
throw { message: "Invalid duration value." };
}
var years = parts[2] || "0";
var months = parts[3] || "0";
var days = parseInt10(parts[4] || 0);
var hours = parseInt10(parts[5] || 0);
var minutes = parseInt10(parts[6] || 0);
var seconds = parseFloat(parts[7] || 0);
if (years !== "0" || months !== "0") {
throw { message: "Unsupported duration value." };
}
var ms = parts[8];
var ns = 0;
if (!ms) {
ms = 0;
} else {
if (ms.length > 7) {
throw { message: "Cannot parse duration value to given precision." };
}
ns = formatNumberWidth(ms.substring(3), 4, true);
ms = formatNumberWidth(ms.substring(0, 3), 3, true);
ms = parseInt10(ms);
ns = parseInt10(ns);
}
ms += seconds * 1000 + minutes * 60000 + hours * 3600000 + days * 86400000;
if (parts[1] === "-") {
ms = -ms;
}
var result = { ms: ms, __edmType: "Edm.Time" };
if (ns) {
result.ns = ns;
}
return result;
}
/** Parses a timezone description in (+|-)nn:nn format.
* @param {String} timezone - Timezone offset.
* @returns {Object} An object with a (d)irection property of 1 for + and -1 for -, offset (h)ours and offset (m)inutes.
*/
function parseTimezone(timezone) {
var direction = timezone.substring(0, 1);
direction = (direction === "+") ? 1 : -1;
var offsetHours = parseInt10(timezone.substring(1));
var offsetMinutes = parseInt10(timezone.substring(timezone.indexOf(":") + 1));
return { d: direction, h: offsetHours, m: offsetMinutes };
}
/** Prepares a request object so that it can be sent through the network.
* @param request - Object that represents the request to be sent.
* @param handler - Handler for data serialization
* @param context - Context used for preparing the request
*/
function prepareRequest(request, handler, context) {
// Default to GET if no method has been specified.
if (!request.method) {
request.method = "GET";
}
if (!request.headers) {
request.headers = {};
} else {
normalizeHeaders(request.headers);
}
if (request.headers.Accept === undefined) {
request.headers.Accept = handler.accept;
}
if (assigned(request.data) && request.body === undefined) {
handler.write(request, context);
}
if (!assigned(request.headers["OData-MaxVersion"])) {
request.headers["OData-MaxVersion"] = handler.maxDataServiceVersion || "4.0";
}
if (request.async === undefined) {
request.async = true;
}
}
/** Traverses a tree of objects invoking callback for every value.
* @param {Object} item - Object or array to traverse.
* @param {Object} owner - Pass through each callback
* @param {Function} callback - Callback function with key and value, similar to JSON.parse reviver.
* @returns {Object} The object with traversed properties.
Unlike the JSON reviver, this won't delete null members.
*/
function traverseInternal(item, owner, callback) {
if (item && typeof item === "object") {
for (var name in item) {
var value = item[name];
var result = traverseInternal(value, name, callback);
result = callback(name, result, owner);
if (result !== value) {
if (value === undefined) {
delete item[name];
} else {
item[name] = result;
}
}
}
}
return item;
}
/** Traverses a tree of objects invoking callback for every value.
* @param {Object} item - Object or array to traverse.
* @param {Function} callback - Callback function with key and value, similar to JSON.parse reviver.
* @returns {Object} The traversed object.
* Unlike the JSON reviver, this won't delete null members.
*/
function traverse(item, callback) {
return callback("", traverseInternal(item, "", callback));
}
exports.dataItemTypeName = dataItemTypeName;
exports.EDM_BINARY = EDM_BINARY;
exports.EDM_BOOLEAN = EDM_BOOLEAN;
exports.EDM_BYTE = EDM_BYTE;
exports.EDM_DATE = EDM_DATE;
exports.EDM_DATETIMEOFFSET = EDM_DATETIMEOFFSET;
exports.EDM_DURATION = EDM_DURATION;
exports.EDM_DECIMAL = EDM_DECIMAL;
exports.EDM_DOUBLE = EDM_DOUBLE;
exports.EDM_GEOGRAPHY = EDM_GEOGRAPHY;
exports.EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY_POINT;
exports.EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY_LINESTRING;
exports.EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY_POLYGON;
exports.EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY_COLLECTION;
exports.EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY_MULTIPOLYGON;
exports.EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY_MULTILINESTRING;
exports.EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY_MULTIPOINT;
exports.EDM_GEOMETRY = EDM_GEOMETRY;
exports.EDM_GEOMETRY_POINT = EDM_GEOMETRY_POINT;
exports.EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY_LINESTRING;
exports.EDM_GEOMETRY_POLYGON = EDM_GEOMETRY_POLYGON;
exports.EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY_COLLECTION;
exports.EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY_MULTIPOLYGON;
exports.EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY_MULTILINESTRING;
exports.EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY_MULTIPOINT;
exports.EDM_GUID = EDM_GUID;
exports.EDM_INT16 = EDM_INT16;
exports.EDM_INT32 = EDM_INT32;
exports.EDM_INT64 = EDM_INT64;
exports.EDM_SBYTE = EDM_SBYTE;
exports.EDM_SINGLE = EDM_SINGLE;
exports.EDM_STRING = EDM_STRING;
exports.EDM_TIMEOFDAY = EDM_TIMEOFDAY;
exports.GEOJSON_POINT = GEOJSON_POINT;
exports.GEOJSON_LINESTRING = GEOJSON_LINESTRING;
exports.GEOJSON_POLYGON = GEOJSON_POLYGON;
exports.GEOJSON_MULTIPOINT = GEOJSON_MULTIPOINT;
exports.GEOJSON_MULTILINESTRING = GEOJSON_MULTILINESTRING;
exports.GEOJSON_MULTIPOLYGON = GEOJSON_MULTIPOLYGON;
exports.GEOJSON_GEOMETRYCOLLECTION = GEOJSON_GEOMETRYCOLLECTION;
exports.forEachSchema = forEachSchema;
exports.formatDateTimeOffset = formatDateTimeOffset;
exports.formatDateTimeOffsetJSON = formatDateTimeOffsetJSON;
exports.formatDuration = formatDuration;
exports.formatNumberWidth = formatNumberWidth;
exports.getCanonicalTimezone = getCanonicalTimezone;
exports.getCollectionType = getCollectionType;
exports.invokeRequest = invokeRequest;
exports.isBatch = isBatch;
exports.isCollection = isCollection;
exports.isCollectionType = isCollectionType;
exports.isComplex = isComplex;
exports.isDateTimeOffset = isDateTimeOffset;
exports.isDeferred = isDeferred;
exports.isEntry = isEntry;
exports.isFeed = isFeed;
exports.isGeographyEdmType = isGeographyEdmType;
exports.isGeometryEdmType = isGeometryEdmType;
exports.isNamedStream = isNamedStream;
exports.isPrimitive = isPrimitive;
exports.isPrimitiveEdmType = isPrimitiveEdmType;
exports.lookupComplexType = lookupComplexType;
exports.lookupDefaultEntityContainer = lookupDefaultEntityContainer;
exports.lookupEntityContainer = lookupEntityContainer;
exports.lookupEntitySet = lookupEntitySet;
exports.lookupSingleton = lookupSingleton;
exports.lookupEntityType = lookupEntityType;
exports.lookupFunctionImport = lookupFunctionImport;
exports.lookupNavigationPropertyType = lookupNavigationPropertyType;
exports.lookupNavigationPropertyEntitySet = lookupNavigationPropertyEntitySet;
exports.lookupInSchema = lookupInSchema;
exports.lookupProperty = lookupProperty;
exports.lookupInMetadata = lookupInMetadata;
exports.getEntitySetInfo = getEntitySetInfo;
exports.maxVersion = maxVersion;
exports.navigationPropertyKind = navigationPropertyKind;
exports.normalizeHeaders = normalizeHeaders;
exports.parseBool = parseBool;
exports.parseDate = parseDate;
exports.parseDateTimeOffset = parseDateTimeOffset;
exports.parseDuration = parseDuration;
exports.parseTimeOfDay = parseTimeOfDay;
exports.parseInt10 = parseInt10;
exports.prepareRequest = prepareRequest;
exports.removeNamespace = removeNamespace;
exports.traverse = traverse;
}, "store" : function(exports, module, require) {
//'use strict';
/** @module store */
exports.defaultStoreMechanism = "best";
/** Creates a new store object.
* @param {String} name - Store name.
* @param {String} [mechanism] -
* @returns {Object} Store object.
*/
exports.createStore = function (name, mechanism) {
if (!mechanism) {
mechanism = exports.defaultStoreMechanism;
}
if (mechanism === "best") {
mechanism = (DomStore.isSupported()) ? "dom" : "memory";
}
var factory = mechanisms[mechanism];
if (factory) {
return factory.create(name);
}
throw { message: "Failed to create store", name: name, mechanism: mechanism };
};
exports.DomStore = DomStore = require('./store/dom.js');
exports.IndexedDBStore = IndexedDBStore = require('./store/indexeddb.js');
exports.MemoryStore = MemoryStore = require('./store/memory.js');
var mechanisms = {
indexeddb: IndexedDBStore,
dom: DomStore,
memory: MemoryStore
};
exports.mechanisms = mechanisms;
}, "dom" : function(exports, module, require) {
'use strict';
/** @module store/dom */
var utils = require('./../utils.js');
// Imports.
var throwErrorCallback = utils.throwErrorCallback;
var delay = utils.delay;
var localStorage = null;
/** This method is used to override the Date.toJSON method and is called only by
* JSON.stringify. It should never be called directly.
* @summary Converts a Date object into an object representation friendly to JSON serialization.
* @returns {Object} Object that represents the Date.
*/
function domStoreDateToJSON() {
var newValue = { v: this.valueOf(), t: "[object Date]" };
// Date objects might have extra properties on them so we save them.
for (var name in this) {
newValue[name] = this[name];
}
return newValue;
}
/** This method is used during JSON parsing and invoked only by the reviver function.
* It should never be called directly.
* @summary JSON reviver function for converting an object representing a Date in a JSON stream to a Date object
* @param value _
* @param value - Object to convert.
* @returns {Date} Date object.
*/
function domStoreJSONToDate(_, value) {
if (value && value.t === "[object Date]") {
var newValue = new Date(value.v);
for (var name in value) {
if (name !== "t" && name !== "v") {
newValue[name] = value[name];
}
}
value = newValue;
}
return value;
}
/** Qualifies the key with the name of the store.
* @param {Object} store - Store object whose name will be used for qualifying the key.
* @param {String} key - Key string.
* @returns {String} Fully qualified key string.
*/
function qualifyDomStoreKey(store, key) {
return store.name + "#!#" + key;
}
/** Gets the key part of a fully qualified key string.
* @param {Object} store - Store object whose name will be used for qualifying the key.
* @param {String} key - Fully qualified key string.
* @returns {String} Key part string
*/
function unqualifyDomStoreKey(store, key) {
return key.replace(store.name + "#!#", "");
}
/** Constructor for store objects that use DOM storage as the underlying mechanism.
* @class DomStore
* @constructor
* @param {String} name - Store name.
*/
function DomStore(name) {
this.name = name;
}
/** Creates a store object that uses DOM Storage as its underlying mechanism.
* @method module:store/dom~DomStore.create
* @param {String} name - Store name.
* @returns {Object} Store object.
*/
DomStore.create = function (name) {
if (DomStore.isSupported()) {
localStorage = localStorage || window.localStorage;
return new DomStore(name);
}
throw { message: "Web Storage not supported by the browser" };
};
/** Checks whether the underlying mechanism for this kind of store objects is supported by the browser.
* @method DomStore.isSupported
* @returns {Boolean} - True if the mechanism is supported by the browser; otherwise false.
*/
DomStore.isSupported = function () {
return !!window.localStorage;
};
/** Adds a new value identified by a key to the store.
* @method module:store/dom~DomStore#add
* @param {String} key - Key string.
* @param value - Value that is going to be added to the store.
* @param {Function} success - Callback for a successful add operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
* This method errors out if the store already contains the specified key.
*/
DomStore.prototype.add = function (key, value, success, error) {
error = error || this.defaultError;
var store = this;
this.contains(key, function (contained) {
if (!contained) {
store.addOrUpdate(key, value, success, error);
} else {
delay(error, { message: "key already exists", key: key });
}
}, error);
};
/** 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.
* @summary Adds or updates a value identified by a key to the store.
* @method module:store/dom~DomStore#addOrUpdate
* @param {String} key - Key string.
* @param value - Value that is going to be added or updated to the store.
* @param {Function} success - Callback for a successful add or update operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
DomStore.prototype.addOrUpdate = function (key, value, success, error) {
error = error || this.defaultError;
if (key instanceof Array) {
error({ message: "Array of keys not supported" });
} else {
var fullKey = qualifyDomStoreKey(this, key);
var oldDateToJSON = Date.prototype.toJSON;
try {
var storedValue = value;
if (storedValue !== undefined) {
// Dehydrate using json
Date.prototype.toJSON = domStoreDateToJSON;
storedValue = window.JSON.stringify(value);
}
// Save the json string.
localStorage.setItem(fullKey, storedValue);
delay(success, key, value);
}
catch (e) {
if (e.code === 22 || e.number === 0x8007000E) {
delay(error, { name: "QUOTA_EXCEEDED_ERR", error: e });
} else {
delay(error, e);
}
}
finally {
Date.prototype.toJSON = oldDateToJSON;
}
}
};
/** In case of an error, this method will not restore any keys that might have been deleted at that point.
* @summary Removes all the data associated with this store object.
* @method module:store/dom~DomStore#clear
* @param {Function} success - Callback for a successful clear operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
DomStore.prototype.clear = function (success, error) {
error = error || this.defaultError;
try {
var i = 0, len = localStorage.length;
while (len > 0 && i < len) {
var fullKey = localStorage.key(i);
var key = unqualifyDomStoreKey(this, fullKey);
if (fullKey !== key) {
localStorage.removeItem(fullKey);
len = localStorage.length;
} else {
i++;
}
}
delay(success);
}
catch (e) {
delay(error, e);
}
};
/** This function does nothing in DomStore as it does not have a connection model
* @method module:store/dom~DomStore#close
*/
DomStore.prototype.close = function () {
};
/** Checks whether a key exists in the store.
* @method module:store/dom~DomStore#contains
* @param {String} key - Key string.
* @param {Function} success - Callback indicating whether the store contains the key or not.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
DomStore.prototype.contains = function (key, success, error) {
error = error || this.defaultError;
try {
var fullKey = qualifyDomStoreKey(this, key);
var value = localStorage.getItem(fullKey);
delay(success, value !== null);
} catch (e) {
delay(error, e);
}
};
DomStore.prototype.defaultError = throwErrorCallback;
/** Gets all the keys that exist in the store.
* @method module:store/dom~DomStore#getAllKeys
* @param {Function} success - Callback for a successful get operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
DomStore.prototype.getAllKeys = function (success, error) {
error = error || this.defaultError;
var results = [];
var i, len;
try {
for (i = 0, len = localStorage.length; i < len; i++) {
var fullKey = localStorage.key(i);
var key = unqualifyDomStoreKey(this, fullKey);
if (fullKey !== key) {
results.push(key);
}
}
delay(success, results);
}
catch (e) {
delay(error, e);
}
};
/** Identifies the underlying mechanism used by the store.*/
DomStore.prototype.mechanism = "dom";
/** Reads the value associated to a key in the store.
* @method module:store/dom~DomStore#read
* @param {String} key - Key string.
* @param {Function} success - Callback for a successful reads operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
DomStore.prototype.read = function (key, success, error) {
error = error || this.defaultError;
if (key instanceof Array) {
error({ message: "Array of keys not supported" });
} else {
try {
var fullKey = qualifyDomStoreKey(this, key);
var value = localStorage.getItem(fullKey);
if (value !== null && value !== "undefined") {
// Hydrate using json
value = window.JSON.parse(value, domStoreJSONToDate);
}
else {
value = undefined;
}
delay(success, key, value);
} catch (e) {
delay(error, e);
}
}
};
/** Removes a key and its value from the store.
* @method module:store/dom~DomStore#remove
* @param {String} key - Key string.
* @param {Function} success - Callback for a successful remove operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
DomStore.prototype.remove = function (key, success, error) {
error = error || this.defaultError;
if (key instanceof Array) {
error({ message: "Batches not supported" });
} else {
try {
var fullKey = qualifyDomStoreKey(this, key);
localStorage.removeItem(fullKey);
delay(success);
} catch (e) {
delay(error, e);
}
}
};
/** Updates the value associated to a key in the store.
* @method module:store/dom~DomStore#update
* @param {String} key - Key string.
* @param value - New value.
* @param {Function} success - Callback for a successful update operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked
* This method errors out if the specified key is not found in the store.
*/
DomStore.prototype.update = function (key, value, success, error) {
error = error || this.defaultError;
var store = this;
this.contains(key, function (contained) {
if (contained) {
store.addOrUpdate(key, value, success, error);
} else {
delay(error, { message: "key not found", key: key });
}
}, error);
};
module.exports = DomStore;}, "indexeddb" : function(exports, module, require) {
'use strict';
/** @module store/indexeddb */
var utils = require('./../utils.js');
// Imports.
var throwErrorCallback = utils.throwErrorCallback;
var delay = utils.delay;
var indexedDB = utils.inBrowser() ? window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB : undefined;
var IDBKeyRange = utils.inBrowser() ? window.IDBKeyRange || window.webkitIDBKeyRange : undefined;
var IDBTransaction = utils.inBrowser() ? window.IDBTransaction || window.webkitIDBTransaction || {} : {} ;
var IDBT_READ_ONLY = IDBTransaction.READ_ONLY || "readonly";
var IDBT_READ_WRITE = IDBTransaction.READ_WRITE || "readwrite";
/** Returns either a specific error handler or the default error handler
* @param {Function} error - The specific error handler
* @param {Function} defaultError - The default error handler
* @returns {Function} The error callback
*/
function getError(error, defaultError) {
return function (e) {
var errorFunc = error || defaultError;
if (!errorFunc) {
return;
}
// Old api quota exceeded error support.
if (Object.prototype.toString.call(e) === "[object IDBDatabaseException]") {
if (e.code === 11 /* IndexedDb disk quota exceeded */) {
errorFunc({ name: "QuotaExceededError", error: e });
return;
}
errorFunc(e);
return;
}
var errName;
try {
var errObj = e.target.error || e;
errName = errObj.name;
} catch (ex) {
errName = (e.type === "blocked") ? "IndexedDBBlocked" : "UnknownError";
}
errorFunc({ name: errName, error: e });
};
}
/** Opens the store object's indexed db database.
* @param {IndexedDBStore} store - The store object
* @param {Function} success - The success callback
* @param {Function} error - The error callback
*/
function openStoreDb(store, success, error) {
var storeName = store.name;
var dbName = "_odatajs_" + storeName;
var request = indexedDB.open(dbName);
request.onblocked = error;
request.onerror = error;
request.onupgradeneeded = function () {
var db = request.result;
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName);
}
};
request.onsuccess = function (event) {
var db = request.result;
if (!db.objectStoreNames.contains(storeName)) {
// Should we use the old style api to define the database schema?
if ("setVersion" in db) {
var versionRequest = db.setVersion("1.0");
versionRequest.onsuccess = function () {
var transaction = versionRequest.transaction;
transaction.oncomplete = function () {
success(db);
};
db.createObjectStore(storeName, null, false);
};
versionRequest.onerror = error;
versionRequest.onblocked = error;
return;
}
// The database doesn't have the expected store.
// Fabricate an error object for the event for the schema mismatch
// and error out.
event.target.error = { name: "DBSchemaMismatch" };
error(event);
return;
}
db.onversionchange = function(event) {
event.target.close();
};
success(db);
};
}
/** Opens a new transaction to the store
* @param {IndexedDBStore} store - The store object
* @param {Integer} mode - The read/write mode of the transaction (constants from IDBTransaction)
* @param {Function} success - The success callback
* @param {Function} error - The error callback
*/
function openTransaction(store, mode, success, error) {
var storeName = store.name;
var storeDb = store.db;
var errorCallback = getError(error, store.defaultError);
if (storeDb) {
success(storeDb.transaction(storeName, mode));
return;
}
openStoreDb(store, function (db) {
store.db = db;
success(db.transaction(storeName, mode));
}, errorCallback);
}
/** Creates a new IndexedDBStore.
* @class IndexedDBStore
* @constructor
* @param {String} name - The name of the store.
* @returns {Object} The new IndexedDBStore.
*/
function IndexedDBStore(name) {
this.name = name;
}
/** Creates a new IndexedDBStore.
* @method module:store/indexeddb~IndexedDBStore.create
* @param {String} name - The name of the store.
* @returns {Object} The new IndexedDBStore.
*/
IndexedDBStore.create = function (name) {
if (IndexedDBStore.isSupported()) {
return new IndexedDBStore(name);
}
throw { message: "IndexedDB is not supported on this browser" };
};
/** Returns whether IndexedDB is supported.
* @method module:store/indexeddb~IndexedDBStore.isSupported
* @returns {Boolean} True if IndexedDB is supported, false otherwise.
*/
IndexedDBStore.isSupported = function () {
return !!indexedDB;
};
/** Adds a key/value pair to the store
* @method module:store/indexeddb~IndexedDBStore#add
* @param {String} key - The key
* @param {Object} value - The value
* @param {Function} success - The success callback
* @param {Function} error - The error callback
*/
IndexedDBStore.prototype.add = function (key, value, success, error) {
var name = this.name;
var defaultError = this.defaultError;
var keys = [];
var values = [];
if (key instanceof Array) {
keys = key;
values = value;
} else {
keys = [key];
values = [value];
}
openTransaction(this, IDBT_READ_WRITE, function (transaction) {
transaction.onabort = getError(error, defaultError, key, "add");
transaction.oncomplete = function () {
if (key instanceof Array) {
success(keys, values);
} else {
success(key, value);
}
};
for (var i = 0; i < keys.length && i < values.length; i++) {
transaction.objectStore(name).add({ v: values[i] }, keys[i]);
}
}, error);
};
/** Adds or updates a key/value pair in the store
* @method module:store/indexeddb~IndexedDBStore#addOrUpdate
* @param {String} key - The key
* @param {Object} value - The value
* @param {Function} success - The success callback
* @param {Function} error - The error callback
*/
IndexedDBStore.prototype.addOrUpdate = function (key, value, success, error) {
var name = this.name;
var defaultError = this.defaultError;
var keys = [];
var values = [];
if (key instanceof Array) {
keys = key;
values = value;
} else {
keys = [key];
values = [value];
}
openTransaction(this, IDBT_READ_WRITE, function (transaction) {
transaction.onabort = getError(error, defaultError);
transaction.oncomplete = function () {
if (key instanceof Array) {
success(keys, values);
} else {
success(key, value);
}
};
for (var i = 0; i < keys.length && i < values.length; i++) {
var record = { v: values[i] };
transaction.objectStore(name).put(record, keys[i]);
}
}, error);
};
/** Clears the store
* @method module:store/indexeddb~IndexedDBStore#clear
* @param {Function} success - The success callback
* @param {Function} error - The error callback
*/
IndexedDBStore.prototype.clear = function (success, error) {
var name = this.name;
var defaultError = this.defaultError;
openTransaction(this, IDBT_READ_WRITE, function (transaction) {
transaction.onerror = getError(error, defaultError);
transaction.oncomplete = function () {
success();
};
transaction.objectStore(name).clear();
}, error);
};
/** Closes the connection to the database
* @method module:store/indexeddb~IndexedDBStore#close
*/
IndexedDBStore.prototype.close = function () {
if (this.db) {
this.db.close();
this.db = null;
}
};
/** Returns whether the store contains a key
* @method module:store/indexeddb~IndexedDBStore#contains
* @param {String} key - The key
* @param {Function} success - The success callback
* @param {Function} error - The error callback
*/
IndexedDBStore.prototype.contains = function (key, success, error) {
var name = this.name;
var defaultError = this.defaultError;
openTransaction(this, IDBT_READ_ONLY, function (transaction) {
var objectStore = transaction.objectStore(name);
var request = objectStore.get(key);
transaction.oncomplete = function () {
success(!!request.result);
};
transaction.onerror = getError(error, defaultError);
}, error);
};
IndexedDBStore.prototype.defaultError = throwErrorCallback;
/** Gets all the keys from the store
* @method module:store/indexeddb~IndexedDBStore#getAllKeys
* @param {Function} success - The success callback
* @param {Function} error - The error callback
*/
IndexedDBStore.prototype.getAllKeys = function (success, error) {
var name = this.name;
var defaultError = this.defaultError;
openTransaction(this, IDBT_READ_WRITE, function (transaction) {
var results = [];
transaction.oncomplete = function () {
success(results);
};
var request = transaction.objectStore(name).openCursor();
request.onerror = getError(error, defaultError);
request.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
results.push(cursor.key);
// Some tools have issues because continue is a javascript reserved word.
cursor["continue"].call(cursor);
}
};
}, error);
};
/** Identifies the underlying mechanism used by the store.
*/
IndexedDBStore.prototype.mechanism = "indexeddb";
/** Reads the value for the specified key
* @method module:store/indexeddb~IndexedDBStore#read
* @param {String} key - The key
* @param {Function} success - The success callback
* @param {Function} error - The error callback
* If the key does not exist, the success handler will be called with value = undefined
*/
IndexedDBStore.prototype.read = function (key, success, error) {
var name = this.name;
var defaultError = this.defaultError;
var keys = (key instanceof Array) ? key : [key];
openTransaction(this, IDBT_READ_ONLY, function (transaction) {
var values = [];
transaction.onerror = getError(error, defaultError, key, "read");
transaction.oncomplete = function () {
if (key instanceof Array) {
success(keys, values);
} else {
success(keys[0], values[0]);
}
};
for (var i = 0; i < keys.length; i++) {
// Some tools have issues because get is a javascript reserved word.
var objectStore = transaction.objectStore(name);
var request = objectStore.get.call(objectStore, keys[i]);
request.onsuccess = function (event) {
var record = event.target.result;
values.push(record ? record.v : undefined);
};
}
}, error);
};
/** Removes the specified key from the store
* @method module:store/indexeddb~IndexedDBStore#remove
* @param {String} key - The key
* @param {Function} success - The success callback
* @param {Function} error - The error callback
*/
IndexedDBStore.prototype.remove = function (key, success, error) {
var name = this.name;
var defaultError = this.defaultError;
var keys = (key instanceof Array) ? key : [key];
openTransaction(this, IDBT_READ_WRITE, function (transaction) {
transaction.onerror = getError(error, defaultError);
transaction.oncomplete = function () {
success();
};
for (var i = 0; i < keys.length; i++) {
// Some tools have issues because continue is a javascript reserved word.
var objectStore = transaction.objectStore(name);
objectStore["delete"].call(objectStore, keys[i]);
}
}, error);
};
/** Updates a key/value pair in the store
* @method module:store/indexeddb~IndexedDBStore#update
* @param {String} key - The key
* @param {Object} value - The value
* @param {Function} success - The success callback
* @param {Function} error - The error callback
*/
IndexedDBStore.prototype.update = function (key, value, success, error) {
var name = this.name;
var defaultError = this.defaultError;
var keys = [];
var values = [];
if (key instanceof Array) {
keys = key;
values = value;
} else {
keys = [key];
values = [value];
}
openTransaction(this, IDBT_READ_WRITE, function (transaction) {
transaction.onabort = getError(error, defaultError);
transaction.oncomplete = function () {
if (key instanceof Array) {
success(keys, values);
} else {
success(key, value);
}
};
for (var i = 0; i < keys.length && i < values.length; i++) {
var request = transaction.objectStore(name).openCursor(IDBKeyRange.only(keys[i]));
var record = { v: values[i] };
request.pair = { key: keys[i], value: record };
request.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
cursor.update(event.target.pair.value);
} else {
transaction.abort();
}
}
}
}, error);
};
module.exports = IndexedDBStore;}, "memory" : function(exports, module, require) {
'use strict';
/** @module store/memory */
var utils = require('./../utils.js');
// Imports.
var throwErrorCallback = utils.throwErrorCallback;
var delay = utils.delay;
/** Constructor for store objects that use a sorted array as the underlying mechanism.
* @class MemoryStore
* @constructor
* @param {String} name - Store name.
*/
function MemoryStore(name) {
var holes = [];
var items = [];
var keys = {};
this.name = name;
var getErrorCallback = function (error) {
return error || this.defaultError;
};
/** Validates that the specified key is not undefined, not null, and not an array
* @param key - Key value.
* @param {Function} error - Error callback.
* @returns {Boolean} True if the key is valid. False if the key is invalid and the error callback has been queued for execution.
*/
function validateKeyInput(key, error) {
var messageString;
if (key instanceof Array) {
messageString = "Array of keys not supported";
}
if (key === undefined || key === null) {
messageString = "Invalid key";
}
if (messageString) {
delay(error, { message: messageString });
return false;
}
return true;
}
/** This method errors out if the store already contains the specified key.
* @summary Adds a new value identified by a key to the store.
* @method module:store/memory~MemoryStore#add
* @param {String} key - Key string.
* @param value - Value that is going to be added to the store.
* @param {Function} success - Callback for a successful add operation.
* @param {Function} error - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
this.add = function (key, value, success, error) {
error = getErrorCallback(error);
if (validateKeyInput(key, error)) {
if (!keys.hasOwnProperty(key)) {
this.addOrUpdate(key, value, success, error);
} else {
error({ message: "key already exists", key: key });
}
}
};
/** 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.
* @summary Adds or updates a value identified by a key to the store.
* @method module:store/memory~MemoryStore#addOrUpdate
* @param {String} key - Key string.
* @param value - Value that is going to be added or updated to the store.
* @param {Function} success - Callback for a successful add or update operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
this.addOrUpdate = function (key, value, success, error) {
error = getErrorCallback(error);
if (validateKeyInput(key, error)) {
var index = keys[key];
if (index === undefined) {
if (holes.length > 0) {
index = holes.splice(0, 1);
} else {
index = items.length;
}
}
items[index] = value;
keys[key] = index;
delay(success, key, value);
}
};
/** Removes all the data associated with this store object.
* @method module:store/memory~MemoryStore#clear
* @param {Function} success - Callback for a successful clear operation.
*/
this.clear = function (success) {
items = [];
keys = {};
holes = [];
delay(success);
};
/** Checks whether a key exists in the store.
* @method module:store/memory~MemoryStore#contains
* @param {String} key - Key string.
* @param {Function} success - Callback indicating whether the store contains the key or not.
*/
this.contains = function (key, success) {
var contained = keys.hasOwnProperty(key);
delay(success, contained);
};
/** Gets all the keys that exist in the store.
* @method module:store/memory~MemoryStore#getAllKeys
* @param {Function} success - Callback for a successful get operation.
*/
this.getAllKeys = function (success) {
var results = [];
for (var name in keys) {
results.push(name);
}
delay(success, results);
};
/** Reads the value associated to a key in the store.
* @method module:store/memory~MemoryStore#read
* @param {String} key - Key string.
* @param {Function} success - Callback for a successful reads operation.
* @param {Function} error - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
this.read = function (key, success, error) {
error = getErrorCallback(error);
if (validateKeyInput(key, error)) {
var index = keys[key];
delay(success, key, items[index]);
}
};
/** Removes a key and its value from the store.
* @method module:store/memory~MemoryStore#remove
* @param {String} key - Key string.
* @param {Function} success - Callback for a successful remove operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
*/
this.remove = function (key, success, error) {
error = getErrorCallback(error);
if (validateKeyInput(key, error)) {
var index = keys[key];
if (index !== undefined) {
if (index === items.length - 1) {
items.pop();
} else {
items[index] = undefined;
holes.push(index);
}
delete keys[key];
// The last item was removed, no need to keep track of any holes in the array.
if (items.length === 0) {
holes = [];
}
}
delay(success);
}
};
/** Updates the value associated to a key in the store.
* @method module:store/memory~MemoryStore#update
* @param {String} key - Key string.
* @param value - New value.
* @param {Function} success - Callback for a successful update operation.
* @param {Function} [error] - Callback for handling errors. If not specified then store.defaultError is invoked.
* This method errors out if the specified key is not found in the store.
*/
this.update = function (key, value, success, error) {
error = getErrorCallback(error);
if (validateKeyInput(key, error)) {
if (keys.hasOwnProperty(key)) {
this.addOrUpdate(key, value, success, error);
} else {
error({ message: "key not found", key: key });
}
}
};
}
/** Creates a store object that uses memory storage as its underlying mechanism.
* @method MemoryStore.create
* @param {String} name - Store name.
* @returns {Object} Store object.
*/
MemoryStore.create = function (name) {
return new MemoryStore(name);
};
/** Checks whether the underlying mechanism for this kind of store objects is supported by the browser.
* @method MemoryStore.isSupported
* @returns {Boolean} True if the mechanism is supported by the browser; otherwise false.
*/
MemoryStore.isSupported = function () {
return true;
};
/** This function does nothing in MemoryStore as it does not have a connection model.
*/
MemoryStore.prototype.close = function () {
};
MemoryStore.prototype.defaultError = throwErrorCallback;
/** Identifies the underlying mechanism used by the store.
*/
MemoryStore.prototype.mechanism = "memory";
/** MemoryStore (see {@link MemoryStore}) */
module.exports = MemoryStore;}, "utils" : function(exports, module, require) {
'use strict';
/** @module odatajs/utils */
function inBrowser() {
return typeof window !== 'undefined';
}
/** Creates a new ActiveXObject from the given progId.
* @param {String} progId - ProgId string of the desired ActiveXObject.
* @returns {Object} The ActiveXObject instance. Null if ActiveX is not supported by the browser.
* This function throws whatever exception might occur during the creation
* of the ActiveXObject.
*/
var activeXObject = function (progId) {
if (window.ActiveXObject) {
return new window.ActiveXObject(progId);
}
return null;
};
/** Checks whether the specified value is different from null and undefined.
* @param [value] Value to check ( may be null)
* @returns {Boolean} true if the value is assigned; false otherwise.
*/
function assigned(value) {
return value !== null && value !== undefined;
}
/** Checks whether the specified item is in the array.
* @param {Array} [arr] Array to check in.
* @param item - Item to look for.
* @returns {Boolean} true if the item is contained, false otherwise.
*/
function contains(arr, item) {
var i, len;
for (i = 0, len = arr.length; i < len; i++) {
if (arr[i] === item) {
return true;
}
}
return false;
}
/** Given two values, picks the first one that is not undefined.
* @param a - First value.
* @param b - Second value.
* @returns a if it's a defined value; else b.
*/
function defined(a, b) {
return (a !== undefined) ? a : b;
}
/** Delays the invocation of the specified function until execution unwinds.
* @param {Function} callback - Callback function.
*/
function delay(callback) {
if (arguments.length === 1) {
window.setTimeout(callback, 0);
return;
}
var args = Array.prototype.slice.call(arguments, 1);
window.setTimeout(function () {
callback.apply(this, args);
}, 0);
}
/** Throws an exception in case that a condition evaluates to false.
* @param {Boolean} condition - Condition to evaluate.
* @param {String} message - Message explaining the assertion.
* @param {Object} data - Additional data to be included in the exception.
*/
function djsassert(condition, message, data) {
if (!condition) {
throw { message: "Assert fired: " + message, data: data };
}
}
/** Extends the target with the specified values.
* @param {Object} target - Object to add properties to.
* @param {Object} values - Object with properties to add into target.
* @returns {Object} The target object.
*/
function extend(target, values) {
for (var name in values) {
target[name] = values[name];
}
return target;
}
function find(arr, callback) {
/** Returns the first item in the array that makes the callback function true.
* @param {Array} [arr] Array to check in. ( may be null)
* @param {Function} callback - Callback function to invoke once per item in the array.
* @returns The first item that makes the callback return true; null otherwise or if the array is null.
*/
if (arr) {
var i, len;
for (i = 0, len = arr.length; i < len; i++) {
if (callback(arr[i])) {
return arr[i];
}
}
}
return null;
}
function isArray(value) {
/** Checks whether the specified value is an array object.
* @param value - Value to check.
* @returns {Boolean} true if the value is an array object; false otherwise.
*/
return Object.prototype.toString.call(value) === "[object Array]";
}
/** Checks whether the specified value is a Date object.
* @param value - Value to check.
* @returns {Boolean} true if the value is a Date object; false otherwise.
*/
function isDate(value) {
return Object.prototype.toString.call(value) === "[object Date]";
}
/** Tests whether a value is an object.
* @param value - Value to test.
* @returns {Boolean} True is the value is an object; false otherwise.
* Per javascript rules, null and array values are objects and will cause this function to return true.
*/
function isObject(value) {
return typeof value === "object";
}
/** Parses a value in base 10.
* @param {String} value - String value to parse.
* @returns {Number} The parsed value, NaN if not a valid value.
*/
function parseInt10(value) {
return parseInt(value, 10);
}
/** Renames a property in an object.
* @param {Object} obj - Object in which the property will be renamed.
* @param {String} oldName - Name of the property that will be renamed.
* @param {String} newName - New name of the property.
* This function will not do anything if the object doesn't own a property with the specified old name.
*/
function renameProperty(obj, oldName, newName) {
if (obj.hasOwnProperty(oldName)) {
obj[newName] = obj[oldName];
delete obj[oldName];
}
}
/** Default error handler.
* @param {Object} error - Error to handle.
*/
function throwErrorCallback(error) {
throw error;
}
/** Removes leading and trailing whitespaces from a string.
* @param {String} str String to trim
* @returns {String} The string with no leading or trailing whitespace.
*/
function trimString(str) {
if (str.trim) {
return str.trim();
}
return str.replace(/^\s+|\s+$/g, '');
}
/** Returns a default value in place of undefined.
* @param [value] Value to check (may be null)
* @param defaultValue - Value to return if value is undefined.
* @returns value if it's defined; defaultValue otherwise.
* This should only be used for cases where falsy values are valid;
* otherwise the pattern should be 'x = (value) ? value : defaultValue;'.
*/
function undefinedDefault(value, defaultValue) {
return (value !== undefined) ? value : defaultValue;
}
// Regular expression that splits a uri into its components:
// 0 - is the matched string.
// 1 - is the scheme.
// 2 - is the authority.
// 3 - is the path.
// 4 - is the query.
// 5 - is the fragment.
var uriRegEx = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/;
var uriPartNames = ["scheme", "authority", "path", "query", "fragment"];
/** Gets information about the components of the specified URI.
* @param {String} uri - URI to get information from.
* @return {Object} An object with an isAbsolute flag and part names (scheme, authority, etc.) if available.
*/
function getURIInfo(uri) {
var result = { isAbsolute: false };
if (uri) {
var matches = uriRegEx.exec(uri);
if (matches) {
var i, len;
for (i = 0, len = uriPartNames.length; i < len; i++) {
if (matches[i + 1]) {
result[uriPartNames[i]] = matches[i + 1];
}
}
}
if (result.scheme) {
result.isAbsolute = true;
}
}
return result;
}
/** Builds a URI string from its components.
* @param {Object} uriInfo - An object with uri parts (scheme, authority, etc.).
* @returns {String} URI string.
*/
function getURIFromInfo(uriInfo) {
return "".concat(
uriInfo.scheme || "",
uriInfo.authority || "",
uriInfo.path || "",
uriInfo.query || "",
uriInfo.fragment || "");
}
// Regular expression that splits a uri authority into its subcomponents:
// 0 - is the matched string.
// 1 - is the userinfo subcomponent.
// 2 - is the host subcomponent.
// 3 - is the port component.
var uriAuthorityRegEx = /^\/{0,2}(?:([^@]*)@)?([^:]+)(?::{1}(\d+))?/;
// Regular expression that matches percentage enconded octects (i.e %20 or %3A);
var pctEncodingRegEx = /%[0-9A-F]{2}/ig;
/** Normalizes the casing of a URI.
* @param {String} uri - URI to normalize, absolute or relative.
* @returns {String} The URI normalized to lower case.
*/
function normalizeURICase(uri) {
var uriInfo = getURIInfo(uri);
var scheme = uriInfo.scheme;
var authority = uriInfo.authority;
if (scheme) {
uriInfo.scheme = scheme.toLowerCase();
if (authority) {
var matches = uriAuthorityRegEx.exec(authority);
if (matches) {
uriInfo.authority = "//" +
(matches[1] ? matches[1] + "@" : "") +
(matches[2].toLowerCase()) +
(matches[3] ? ":" + matches[3] : "");
}
}
}
uri = getURIFromInfo(uriInfo);
return uri.replace(pctEncodingRegEx, function (str) {
return str.toLowerCase();
});
}
/** Normalizes a possibly relative URI with a base URI.
* @param {String} uri - URI to normalize, absolute or relative
* @param {String} base - Base URI to compose with (may be null)
* @returns {String} The composed URI if relative; the original one if absolute.
*/
function normalizeURI(uri, base) {
if (!base) {
return uri;
}
var uriInfo = getURIInfo(uri);
if (uriInfo.isAbsolute) {
return uri;
}
var baseInfo = getURIInfo(base);
var normInfo = {};
var path;
if (uriInfo.authority) {
normInfo.authority = uriInfo.authority;
path = uriInfo.path;
normInfo.query = uriInfo.query;
} else {
if (!uriInfo.path) {
path = baseInfo.path;
normInfo.query = uriInfo.query || baseInfo.query;
} else {
if (uriInfo.path.charAt(0) === '/') {
path = uriInfo.path;
} else {
path = mergeUriPathWithBase(uriInfo.path, baseInfo.path);
}
normInfo.query = uriInfo.query;
}
normInfo.authority = baseInfo.authority;
}
normInfo.path = removeDotsFromPath(path);
normInfo.scheme = baseInfo.scheme;
normInfo.fragment = uriInfo.fragment;
return getURIFromInfo(normInfo);
}
/** Merges the path of a relative URI and a base URI.
* @param {String} uriPath - Relative URI path.
* @param {String} basePath - Base URI path.
* @returns {String} A string with the merged path.
*/
function mergeUriPathWithBase(uriPath, basePath) {
var path = "/";
var end;
if (basePath) {
end = basePath.lastIndexOf("/");
path = basePath.substring(0, end);
if (path.charAt(path.length - 1) !== "/") {
path = path + "/";
}
}
return path + uriPath;
}
/** Removes the special folders . and .. from a URI's path.
* @param {string} path - URI path component.
* @returns {String} Path without any . and .. folders.
*/
function removeDotsFromPath(path) {
var result = "";
var segment = "";
var end;
while (path) {
if (path.indexOf("..") === 0 || path.indexOf(".") === 0) {
path = path.replace(/^\.\.?\/?/g, "");
} else if (path.indexOf("/..") === 0) {
path = path.replace(/^\/\..\/?/g, "/");
end = result.lastIndexOf("/");
if (end === -1) {
result = "";
} else {
result = result.substring(0, end);
}
} else if (path.indexOf("/.") === 0) {
path = path.replace(/^\/\.\/?/g, "/");
} else {
segment = path;
end = path.indexOf("/", 1);
if (end !== -1) {
segment = path.substring(0, end);
}
result = result + segment;
path = path.replace(segment, "");
}
}
return result;
}
function convertByteArrayToHexString(str) {
var arr = [];
if (window.atob === undefined) {
arr = decodeBase64(str);
} else {
var binaryStr = window.atob(str);
for (var i = 0; i < binaryStr.length; i++) {
arr.push(binaryStr.charCodeAt(i));
}
}
var hexValue = "";
var hexValues = "0123456789ABCDEF";
for (var j = 0; j < arr.length; j++) {
var t = arr[j];
hexValue += hexValues[t >> 4];
hexValue += hexValues[t & 0x0F];
}
return hexValue;
}
function decodeBase64(str) {
var binaryString = "";
for (var i = 0; i < str.length; i++) {
var base65IndexValue = getBase64IndexValue(str[i]);
var binaryValue = "";
if (base65IndexValue !== null) {
binaryValue = base65IndexValue.toString(2);
binaryString += addBase64Padding(binaryValue);
}
}
var byteArray = [];
var numberOfBytes = parseInt(binaryString.length / 8, 10);
for (i = 0; i < numberOfBytes; i++) {
var intValue = parseInt(binaryString.substring(i * 8, (i + 1) * 8), 2);
byteArray.push(intValue);
}
return byteArray;
}
function getBase64IndexValue(character) {
var asciiCode = character.charCodeAt(0);
var asciiOfA = 65;
var differenceBetweenZanda = 6;
if (asciiCode >= 65 && asciiCode <= 90) { // between "A" and "Z" inclusive
return asciiCode - asciiOfA;
} else if (asciiCode >= 97 && asciiCode <= 122) { // between 'a' and 'z' inclusive
return asciiCode - asciiOfA - differenceBetweenZanda;
} else if (asciiCode >= 48 && asciiCode <= 57) { // between '0' and '9' inclusive
return asciiCode + 4;
} else if (character == "+") {
return 62;
} else if (character == "/") {
return 63;
} else {
return null;
}
}
function addBase64Padding(binaryString) {
while (binaryString.length < 6) {
binaryString = "0" + binaryString;
}
return binaryString;
}
function getJsonValueArraryLength(data) {
if (data && data.value) {
return data.value.length;
}
return 0;
}
function sliceJsonValueArray(data, start, end) {
if (data === undefined || data.value === undefined) {
return data;
}
if (start < 0) {
start = 0;
}
var length = getJsonValueArraryLength(data);
if (length < end) {
end = length;
}
var newdata = {};
for (var property in data) {
if (property == "value") {
newdata[property] = data[property].slice(start, end);
} else {
newdata[property] = data[property];
}
}
return newdata;
}
function concatJsonValueArray(data, concatData) {
if (concatData === undefined || concatData.value === undefined) {
return data;
}
if (data === undefined || Object.keys(data).length === 0) {
return concatData;
}
if (data.value === undefined) {
data.value = concatData.value;
return data;
}
data.value = data.value.concat(concatData.value);
return data;
}
function endsWith(input, search) {
return input.indexOf(search, input.length - search.length) !== -1;
}
function startsWith (input, search) {
return input.indexOf(search) === 0;
}
function getFormatKind(format, defaultFormatKind) {
var formatKind = defaultFormatKind;
if (!assigned(format)) {
return formatKind;
}
var normalizedFormat = format.toLowerCase();
switch (normalizedFormat) {
case "none":
formatKind = 0;
break;
case "minimal":
formatKind = 1;
break;
case "full":
formatKind = 2;
break;
default:
break;
}
return formatKind;
}
exports.inBrowser = inBrowser;
exports.activeXObject = activeXObject;
exports.assigned = assigned;
exports.contains = contains;
exports.defined = defined;
exports.delay = delay;
exports.djsassert = djsassert;
exports.extend = extend;
exports.find = find;
exports.getURIInfo = getURIInfo;
exports.isArray = isArray;
exports.isDate = isDate;
exports.isObject = isObject;
exports.normalizeURI = normalizeURI;
exports.normalizeURICase = normalizeURICase;
exports.parseInt10 = parseInt10;
exports.renameProperty = renameProperty;
exports.throwErrorCallback = throwErrorCallback;
exports.trimString = trimString;
exports.undefinedDefault = undefinedDefault;
exports.decodeBase64 = decodeBase64;
exports.convertByteArrayToHexString = convertByteArrayToHexString;
exports.getJsonValueArraryLength = getJsonValueArraryLength;
exports.sliceJsonValueArray = sliceJsonValueArray;
exports.concatJsonValueArray = concatJsonValueArray;
exports.startsWith = startsWith;
exports.endsWith = endsWith;
exports.getFormatKind = getFormatKind;}, "xml" : function(exports, module, require) {
'use strict';
/** @module odatajs/xml */
var utils = require('./utils.js');
var activeXObject = utils.activeXObject;
var djsassert = utils.djsassert;
var extend = utils.extend;
var isArray = utils.isArray;
var normalizeURI = utils.normalizeURI;
// URI prefixes to generate smaller code.
var http = "http://";
var w3org = http + "www.w3.org/"; // http://www.w3.org/
var xhtmlNS = w3org + "1999/xhtml"; // http://www.w3.org/1999/xhtml
var xmlnsNS = w3org + "2000/xmlns/"; // http://www.w3.org/2000/xmlns/
var xmlNS = w3org + "XML/1998/namespace"; // http://www.w3.org/XML/1998/namespace
var mozillaParserErroNS = http + "www.mozilla.org/newlayout/xml/parsererror.xml";
/** Checks whether the specified string has leading or trailing spaces.
* @param {String} text - String to check.
* @returns {Boolean} true if text has any leading or trailing whitespace; false otherwise.
*/
function hasLeadingOrTrailingWhitespace(text) {
var re = /(^\s)|(\s$)/;
return re.test(text);
}
/** Determines whether the specified text is empty or whitespace.
* @param {String} text - Value to inspect.
* @returns {Boolean} true if the text value is empty or all whitespace; false otherwise.
*/
function isWhitespace(text) {
var ws = /^\s*$/;
return text === null || ws.test(text);
}
/** Determines whether the specified element has xml:space='preserve' applied.
* @param domElement - Element to inspect.
* @returns {Boolean} Whether xml:space='preserve' is in effect.
*/
function isWhitespacePreserveContext(domElement) {
while (domElement !== null && domElement.nodeType === 1) {
var val = xmlAttributeValue(domElement, "space", xmlNS);
if (val === "preserve") {
return true;
} else if (val === "default") {
break;
} else {
domElement = domElement.parentNode;
}
}
return false;
}
/** Determines whether the attribute is a XML namespace declaration.
* @param domAttribute - Element to inspect.
* @return {Boolean} True if the attribute is a namespace declaration (its name is 'xmlns' or starts with 'xmlns:'; false otherwise.
*/
function isXmlNSDeclaration(domAttribute) {
var nodeName = domAttribute.nodeName;
return nodeName == "xmlns" || nodeName.indexOf("xmlns:") === 0;
}
/** Safely set as property in an object by invoking obj.setProperty.
* @param obj - Object that exposes a setProperty method.
* @param {String} name - Property name
* @param value - Property value.
*/
function safeSetProperty(obj, name, value) {
try {
obj.setProperty(name, value);
} catch (_) { }
}
/** Creates an configures new MSXML 3.0 ActiveX object.
* @returns {Object} New MSXML 3.0 ActiveX object.
* This function throws any exception that occurs during the creation
* of the MSXML 3.0 ActiveX object.
*/
function msXmlDom3() {
var msxml3 = activeXObject("Msxml2.DOMDocument.3.0");
if (msxml3) {
safeSetProperty(msxml3, "ProhibitDTD", true);
safeSetProperty(msxml3, "MaxElementDepth", 256);
safeSetProperty(msxml3, "AllowDocumentFunction", false);
safeSetProperty(msxml3, "AllowXsltScript", false);
}
return msxml3;
}
/** Creates an configures new MSXML 6.0 or MSXML 3.0 ActiveX object.
* @returns {Object} New MSXML 3.0 ActiveX object.
* This function will try to create a new MSXML 6.0 ActiveX object. If it fails then
* it will fallback to create a new MSXML 3.0 ActiveX object. Any exception that
* happens during the creation of the MSXML 6.0 will be handled by the function while
* the ones that happend during the creation of the MSXML 3.0 will be thrown.
*/
function msXmlDom() {
try {
var msxml = activeXObject("Msxml2.DOMDocument.6.0");
if (msxml) {
msxml.async = true;
}
return msxml;
} catch (_) {
return msXmlDom3();
}
}
/** Parses an XML string using the MSXML DOM.
* @returns {Object} New MSXML DOMDocument node representing the parsed XML string.
* This function throws any exception that occurs during the creation
* of the MSXML ActiveX object. It also will throw an exception
* in case of a parsing error.
*/
function msXmlParse(text) {
var dom = msXmlDom();
if (!dom) {
return null;
}
dom.loadXML(text);
var parseError = dom.parseError;
if (parseError.errorCode !== 0) {
xmlThrowParserError(parseError.reason, parseError.srcText, text);
}
return dom;
}
/** Throws a new exception containing XML parsing error information.
* @param exceptionOrReason - String indicating the reason of the parsing failure or Object detailing the parsing error.
* @param {String} srcText - String indicating the part of the XML string that caused the parsing error.
* @param {String} errorXmlText - XML string for wich the parsing failed.
*/
function xmlThrowParserError(exceptionOrReason, srcText, errorXmlText) {
if (typeof exceptionOrReason === "string") {
exceptionOrReason = { message: exceptionOrReason };
}
throw extend(exceptionOrReason, { srcText: srcText || "", errorXmlText: errorXmlText || "" });
}
/** Returns an XML DOM document from the specified text.
* @param {String} text - Document text.
* @returns XML DOM document.
* This function will throw an exception in case of a parse error
*/
function xmlParse(text) {
var domParser = undefined;
if (utils.inBrowser()) {
domParser = window.DOMParser && new window.DOMParser();
} else {
domParser = new (require('xmldom').DOMParser)();
}
var dom;
if (!domParser) {
dom = msXmlParse(text);
if (!dom) {
xmlThrowParserError("XML DOM parser not supported");
}
return dom;
}
try {
dom = domParser.parseFromString(text, "text/xml");
} catch (e) {
xmlThrowParserError(e, "", text);
}
var element = dom.documentElement;
var nsURI = element.namespaceURI;
var localName = xmlLocalName(element);
// Firefox reports errors by returing the DOM for an xml document describing the problem.
if (localName === "parsererror" && nsURI === mozillaParserErroNS) {
var srcTextElement = xmlFirstChildElement(element, mozillaParserErroNS, "sourcetext");
var srcText = srcTextElement ? xmlNodeValue(srcTextElement) : "";
xmlThrowParserError(xmlInnerText(element) || "", srcText, text);
}
// Chrome (and maybe other webkit based browsers) report errors by injecting a header with an error message.
// The error may be localized, so instead we simply check for a header as the
// top element or descendant child of the document.
if (localName === "h3" && nsURI === xhtmlNS || xmlFirstDescendantElement(element, xhtmlNS, "h3")) {
var reason = "";
var siblings = [];
var cursor = element.firstChild;
while (cursor) {
if (cursor.nodeType === 1) {
reason += xmlInnerText(cursor) || "";
}
siblings.push(cursor.nextSibling);
cursor = cursor.firstChild || siblings.shift();
}
reason += xmlInnerText(element) || "";
xmlThrowParserError(reason, "", text);
}
return dom;
}
/** Builds a XML qualified name string in the form of "prefix:name".
* @param {String} prefix - Prefix string (may be null)
* @param {String} name - Name string to qualify with the prefix.
* @returns {String} Qualified name.
*/
function xmlQualifiedName(prefix, name) {
return prefix ? prefix + ":" + name : name;
}
/** Appends a text node into the specified DOM element node.
* @param domNode - DOM node for the element.
* @param {String} textNode - Text to append as a child of element.
*/
function xmlAppendText(domNode, textNode) {
if (hasLeadingOrTrailingWhitespace(textNode.data)) {
var attr = xmlAttributeNode(domNode, xmlNS, "space");
if (!attr) {
attr = xmlNewAttribute(domNode.ownerDocument, xmlNS, xmlQualifiedName("xml", "space"));
xmlAppendChild(domNode, attr);
}
attr.value = "preserve";
}
domNode.appendChild(textNode);
return domNode;
}
/** Iterates through the XML element's attributes and invokes the callback function for each one.
* @param element - Wrapped element to iterate over.
* @param {Function} onAttributeCallback - Callback function to invoke with wrapped attribute nodes.
*/
function xmlAttributes(element, onAttributeCallback) {
var attributes = element.attributes;
var i, len;
for (i = 0, len = attributes.length; i < len; i++) {
onAttributeCallback(attributes.item(i));
}
}
/** Returns the value of a DOM element's attribute.
* @param domNode - DOM node for the owning element.
* @param {String} localName - Local name of the attribute.
* @param {String} nsURI - Namespace URI of the attribute.
* @returns {String} - The attribute value, null if not found (may be null)
*/
function xmlAttributeValue(domNode, localName, nsURI) {
var attribute = xmlAttributeNode(domNode, localName, nsURI);
return attribute ? xmlNodeValue(attribute) : null;
}
/** Gets an attribute node from a DOM element.
* @param domNode - DOM node for the owning element.
* @param {String} localName - Local name of the attribute.
* @param {String} nsURI - Namespace URI of the attribute.
* @returns The attribute node, null if not found.
*/
function xmlAttributeNode(domNode, localName, nsURI) {
var attributes = domNode.attributes;
if (attributes.getNamedItemNS) {
return attributes.getNamedItemNS(nsURI || null, localName);
}
return attributes.getQualifiedItem(localName, nsURI) || null;
}
/** Gets the value of the xml:base attribute on the specified element.
* @param domNode - Element to get xml:base attribute value from.
* @param [baseURI] - Base URI used to normalize the value of the xml:base attribute ( may be null)
* @returns {String} Value of the xml:base attribute if found; the baseURI or null otherwise.
*/
function xmlBaseURI(domNode, baseURI) {
var base = xmlAttributeNode(domNode, "base", xmlNS);
return (base ? normalizeURI(base.value, baseURI) : baseURI) || null;
}
/** Iterates through the XML element's child DOM elements and invokes the callback function for each one.
* @param domNode - DOM Node containing the DOM elements to iterate over.
* @param {Function} onElementCallback - Callback function to invoke for each child DOM element.
*/
function xmlChildElements(domNode, onElementCallback) {
xmlTraverse(domNode, /*recursive*/false, function (child) {
if (child.nodeType === 1) {
onElementCallback(child);
}
// continue traversing.
return true;
});
}
/** Gets the descendant element under root that corresponds to the specified path and namespace URI.
* @param root - DOM element node from which to get the descendant element.
* @param {String} namespaceURI - The namespace URI of the element to match.
* @param {String} path - Path to the desired descendant element.
* @return The element specified by path and namespace URI.
* All the elements in the path are matched against namespaceURI.
* The function will stop searching on the first element that doesn't match the namespace and the path.
*/
function xmlFindElementByPath(root, namespaceURI, path) {
var parts = path.split("/");
var i, len;
for (i = 0, len = parts.length; i < len; i++) {
root = root && xmlFirstChildElement(root, namespaceURI, parts[i]);
}
return root || null;
}
/** Gets the DOM element or DOM attribute node under root that corresponds to the specified path and namespace URI.
* @param root - DOM element node from which to get the descendant node.
* @param {String} namespaceURI - The namespace URI of the node to match.
* @param {String} path - Path to the desired descendant node.
* @return The node specified by path and namespace URI.
* This function will traverse the path and match each node associated to a path segement against the namespace URI.
* The traversal stops when the whole path has been exahusted or a node that doesn't belogong the specified namespace is encountered.
* The last segment of the path may be decorated with a starting @ character to indicate that the desired node is a DOM attribute.
*/
function xmlFindNodeByPath(root, namespaceURI, path) {
var lastSegmentStart = path.lastIndexOf("/");
var nodePath = path.substring(lastSegmentStart + 1);
var parentPath = path.substring(0, lastSegmentStart);
var node = parentPath ? xmlFindElementByPath(root, namespaceURI, parentPath) : root;
if (node) {
if (nodePath.charAt(0) === "@") {
return xmlAttributeNode(node, nodePath.substring(1), namespaceURI);
}
return xmlFirstChildElement(node, namespaceURI, nodePath);
}
return null;
}
/** Returns the first child DOM element under the specified DOM node that matches the specified namespace URI and local name.
* @param domNode - DOM node from which the child DOM element is going to be retrieved.
* @param {String} [namespaceURI] -
* @param {String} [localName] -
* @return The node's first child DOM element that matches the specified namespace URI and local name; null otherwise.
*/
function xmlFirstChildElement(domNode, namespaceURI, localName) {
return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/false);
}
/** Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.
* @param domNode - DOM node from which the descendant DOM element is going to be retrieved.
* @param {String} [namespaceURI] -
* @param {String} [localName] -
* @return The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.
*/
function xmlFirstDescendantElement(domNode, namespaceURI, localName) {
if (domNode.getElementsByTagNameNS) {
var result = domNode.getElementsByTagNameNS(namespaceURI, localName);
return result.length > 0 ? result[0] : null;
}
return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/true);
}
/** Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.
* @param domNode - DOM node from which the descendant DOM element is going to be retrieved.
* @param {String} [namespaceURI] -
* @param {String} [localName] -
* @param {Boolean} recursive
* - True if the search should include all the descendants of the DOM node.
* - False if the search should be scoped only to the direct children of the DOM node.
* @return The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.
*/
function xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, recursive) {
var firstElement = null;
xmlTraverse(domNode, recursive, function (child) {
if (child.nodeType === 1) {
var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(child) === namespaceURI;
var isExpectedNodeName = !localName || xmlLocalName(child) === localName;
if (isExpectedNamespace && isExpectedNodeName) {
firstElement = child;
}
}
return firstElement === null;
});
return firstElement;
}
/** Gets the concatenated value of all immediate child text and CDATA nodes for the specified element.
* @param xmlElement - Element to get values for.
* @returns {String} Text for all direct children.
*/
function xmlInnerText(xmlElement) {
var result = null;
var root = (xmlElement.nodeType === 9 && xmlElement.documentElement) ? xmlElement.documentElement : xmlElement;
var whitespaceAlreadyRemoved = root.ownerDocument.preserveWhiteSpace === false;
var whitespacePreserveContext;
xmlTraverse(root, false, function (child) {
if (child.nodeType === 3 || child.nodeType === 4) {
// isElementContentWhitespace indicates that this is 'ignorable whitespace',
// but it's not defined by all browsers, and does not honor xml:space='preserve'
// in some implementations.
//
// If we can't tell either way, we walk up the tree to figure out whether
// xml:space is set to preserve; otherwise we discard pure-whitespace.
//
// For example <a> <b>1</b></a>. The space between <a> and <b> is usually 'ignorable'.
var text = xmlNodeValue(child);
var shouldInclude = whitespaceAlreadyRemoved || !isWhitespace(text);
if (!shouldInclude) {
// Walk up the tree to figure out whether we are in xml:space='preserve' context
// for the cursor (needs to happen only once).
if (whitespacePreserveContext === undefined) {
whitespacePreserveContext = isWhitespacePreserveContext(root);
}
shouldInclude = whitespacePreserveContext;
}
if (shouldInclude) {
if (!result) {
result = text;
} else {
result += text;
}
}
}
// Continue traversing?
return true;
});
return result;
}
/** Returns the localName of a XML node.
* @param domNode - DOM node to get the value from.
* @returns {String} localName of domNode.
*/
function xmlLocalName(domNode) {
return domNode.localName || domNode.baseName;
}
/** Returns the namespace URI of a XML node.
* @param domNode - DOM node to get the value from.
* @returns {String} Namespace URI of domNode.
*/
function xmlNamespaceURI(domNode) {
return domNode.namespaceURI || null;
}
/** Returns the value or the inner text of a XML node.
* @param domNode - DOM node to get the value from.
* @return Value of the domNode or the inner text if domNode represents a DOM element node.
*/
function xmlNodeValue(domNode) {
if (domNode.nodeType === 1) {
return xmlInnerText(domNode);
}
return domNode.nodeValue;
}
/** Walks through the descendants of the domNode and invokes a callback for each node.
* @param domNode - DOM node whose descendants are going to be traversed.
* @param {Boolean} recursive
* - True if the traversal should include all the descenants of the DOM node.
* - False if the traversal should be scoped only to the direct children of the DOM node.
* @param {Boolean} onChildCallback - Called for each child
* @returns {String} Namespace URI of node.
*/
function xmlTraverse(domNode, recursive, onChildCallback) {
var subtrees = [];
var child = domNode.firstChild;
var proceed = true;
while (child && proceed) {
proceed = onChildCallback(child);
if (proceed) {
if (recursive && child.firstChild) {
subtrees.push(child.firstChild);
}
child = child.nextSibling || subtrees.shift();
}
}
}
/** Returns the next sibling DOM element of the specified DOM node.
* @param domNode - DOM node from which the next sibling is going to be retrieved.
* @param {String} [namespaceURI] -
* @param {String} [localName] -
* @return The node's next sibling DOM element, null if there is none.
*/
function xmlSiblingElement(domNode, namespaceURI, localName) {
var sibling = domNode.nextSibling;
while (sibling) {
if (sibling.nodeType === 1) {
var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(sibling) === namespaceURI;
var isExpectedNodeName = !localName || xmlLocalName(sibling) === localName;
if (isExpectedNamespace && isExpectedNodeName) {
return sibling;
}
}
sibling = sibling.nextSibling;
}
return null;
}
/** Creates a new empty DOM document node.
* @return New DOM document node.
*
* This function will first try to create a native DOM document using
* the browsers createDocument function. If the browser doesn't
* support this but supports ActiveXObject, then an attempt to create
* an MSXML 6.0 DOM will be made. If this attempt fails too, then an attempt
* for creating an MXSML 3.0 DOM will be made. If this last attemp fails or
* the browser doesn't support ActiveXObject then an exception will be thrown.
*/
function xmlDom() {
var implementation = window.document.implementation;
return (implementation && implementation.createDocument) ?
implementation.createDocument(null, null, null) :
msXmlDom();
}
/** Appends a collection of child nodes or string values to a parent DOM node.
* @param parent - DOM node to which the children will be appended.
* @param {Array} children - Array containing DOM nodes or string values that will be appended to the parent.
* @return The parent with the appended children or string values.
* If a value in the children collection is a string, then a new DOM text node is going to be created
* for it and then appended to the parent.
*/
function xmlAppendChildren(parent, children) {
if (!isArray(children)) {
return xmlAppendChild(parent, children);
}
var i, len;
for (i = 0, len = children.length; i < len; i++) {
children[i] && xmlAppendChild(parent, children[i]);
}
return parent;
}
/** Appends a child node or a string value to a parent DOM node.
* @param parent - DOM node to which the child will be appended.
* @param child - Child DOM node or string value to append to the parent.
* @return The parent with the appended child or string value.
* If child is a string value, then a new DOM text node is going to be created
* for it and then appended to the parent.
*/
function xmlAppendChild(parent, child) {
djsassert(parent !== child, "xmlAppendChild() - parent and child are one and the same!");
if (child) {
if (typeof child === "string") {
return xmlAppendText(parent, xmlNewText(parent.ownerDocument, child));
}
if (child.nodeType === 2) {
parent.setAttributeNodeNS ? parent.setAttributeNodeNS(child) : parent.setAttributeNode(child);
} else {
parent.appendChild(child);
}
}
return parent;
}
/** Creates a new DOM attribute node.
* @param dom - DOM document used to create the attribute.
* @param {String} namespaceURI - Namespace URI.
* @param {String} qualifiedName - Qualified OData name
* @param {String} value - Value of the new attribute
* @return DOM attribute node for the namespace declaration.
*/
function xmlNewAttribute(dom, namespaceURI, qualifiedName, value) {
var attribute =
dom.createAttributeNS && dom.createAttributeNS(namespaceURI, qualifiedName) ||
dom.createNode(2, qualifiedName, namespaceURI || undefined);
attribute.value = value || "";
return attribute;
}
/** Creates a new DOM element node.
* @param dom - DOM document used to create the DOM element.
* @param {String} namespaceURI - Namespace URI of the new DOM element.
* @param {String} qualifiedName - Qualified name in the form of "prefix:name" of the new DOM element.
* @param {Array} [children] Collection of child DOM nodes or string values that are going to be appended to the new DOM element.
* @return New DOM element.
* If a value in the children collection is a string, then a new DOM text node is going to be created
* for it and then appended to the new DOM element.
*/
function xmlNewElement(dom, namespaceURI, qualifiedName, children) {
var element =
dom.createElementNS && dom.createElementNS(nampespaceURI, qualifiedName) ||
dom.createNode(1, qualifiedName, nampespaceURI || undefined);
return xmlAppendChildren(element, children || []);
}
/** Creates a namespace declaration attribute.
* @param dom - DOM document used to create the attribute.
* @param {String} namespaceURI - Namespace URI.
* @param {String} prefix - Namespace prefix.
* @return DOM attribute node for the namespace declaration.
*/
function xmlNewNSDeclaration(dom, namespaceURI, prefix) {
return xmlNewAttribute(dom, xmlnsNS, xmlQualifiedName("xmlns", prefix), namespaceURI);
}
/** Creates a new DOM document fragment node for the specified xml text.
* @param dom - DOM document from which the fragment node is going to be created.
* @param {String} text XML text to be represented by the XmlFragment.
* @return New DOM document fragment object.
*/
function xmlNewFragment(dom, text) {
var value = "<c>" + text + "</c>";
var tempDom = xmlParse(value);
var tempRoot = tempDom.documentElement;
var imported = ("importNode" in dom) ? dom.importNode(tempRoot, true) : tempRoot;
var fragment = dom.createDocumentFragment();
var importedChild = imported.firstChild;
while (importedChild) {
fragment.appendChild(importedChild);
importedChild = importedChild.nextSibling;
}
return fragment;
}
/** Creates new DOM text node.
* @param dom - DOM document used to create the text node.
* @param {String} text - Text value for the DOM text node.
* @return DOM text node.
*/
function xmlNewText(dom, text) {
return dom.createTextNode(text);
}
/** Creates a new DOM element or DOM attribute node as specified by path and appends it to the DOM tree pointed by root.
* @param dom - DOM document used to create the new node.
* @param root - DOM element node used as root of the subtree on which the new nodes are going to be created.
* @param {String} namespaceURI - Namespace URI of the new DOM element or attribute.
* @param {String} prefix - Prefix used to qualify the name of the new DOM element or attribute.
* @param {String} path - Path string describing the location of the new DOM element or attribute from the root element.
* @return DOM element or attribute node for the last segment of the path.
* This function will traverse the path and will create a new DOM element with the specified namespace URI and prefix
* for each segment that doesn't have a matching element under root.
* The last segment of the path may be decorated with a starting @ character. In this case a new DOM attribute node
* will be created.
*/
function xmlNewNodeByPath(dom, root, namespaceURI, prefix, path) {
var name = "";
var parts = path.split("/");
var xmlFindNode = xmlFirstChildElement;
var xmlNewNode = xmlNewElement;
var xmlNode = root;
var i, len;
for (i = 0, len = parts.length; i < len; i++) {
name = parts[i];
if (name.charAt(0) === "@") {
name = name.substring(1);
xmlFindNode = xmlAttributeNode;
xmlNewNode = xmlNewAttribute;
}
var childNode = xmlFindNode(xmlNode, namespaceURI, name);
if (!childNode) {
childNode = xmlNewNode(dom, namespaceURI, xmlQualifiedName(prefix, name));
xmlAppendChild(xmlNode, childNode);
}
xmlNode = childNode;
}
return xmlNode;
}
/** Returns the text representation of the document to which the specified node belongs.
* @param domNode - Wrapped element in the document to serialize.
* @returns {String} Serialized document.
*/
function xmlSerialize(domNode) {
var xmlSerializer = window.XMLSerializer;
if (xmlSerializer) {
var serializer = new xmlSerializer();
return serializer.serializeToString(domNode);
}
if (domNode.xml) {
return domNode.xml;
}
throw { message: "XML serialization unsupported" };
}
/** Returns the XML representation of the all the descendants of the node.
* @param domNode - Node to serialize.
* @returns {String} The XML representation of all the descendants of the node.
*/
function xmlSerializeDescendants(domNode) {
var children = domNode.childNodes;
var i, len = children.length;
if (len === 0) {
return "";
}
// Some implementations of the XMLSerializer don't deal very well with fragments that
// don't have a DOMElement as their first child. The work around is to wrap all the
// nodes in a dummy root node named "c", serialize it and then just extract the text between
// the <c> and the </c> substrings.
var dom = domNode.ownerDocument;
var fragment = dom.createDocumentFragment();
var fragmentRoot = dom.createElement("c");
fragment.appendChild(fragmentRoot);
// Move the children to the fragment tree.
for (i = 0; i < len; i++) {
fragmentRoot.appendChild(children[i]);
}
var xml = xmlSerialize(fragment);
xml = xml.substr(3, xml.length - 7);
// Move the children back to the original dom tree.
for (i = 0; i < len; i++) {
domNode.appendChild(fragmentRoot.childNodes[i]);
}
return xml;
}
/** Returns the XML representation of the node and all its descendants.
* @param domNode - Node to serialize
* @returns {String} The XML representation of the node and all its descendants.
*/
function xmlSerializeNode(domNode) {
var xml = domNode.xml;
if (xml !== undefined) {
return xml;
}
if (window.XMLSerializer) {
var serializer = new window.XMLSerializer();
return serializer.serializeToString(domNode);
}
throw { message: "XML serialization unsupported" };
}
exports.http = http;
exports.w3org = w3org;
exports.xmlNS = xmlNS;
exports.xmlnsNS = xmlnsNS;
exports.hasLeadingOrTrailingWhitespace = hasLeadingOrTrailingWhitespace;
exports.isXmlNSDeclaration = isXmlNSDeclaration;
exports.xmlAppendChild = xmlAppendChild;
exports.xmlAppendChildren = xmlAppendChildren;
exports.xmlAttributeNode = xmlAttributeNode;
exports.xmlAttributes = xmlAttributes;
exports.xmlAttributeValue = xmlAttributeValue;
exports.xmlBaseURI = xmlBaseURI;
exports.xmlChildElements = xmlChildElements;
exports.xmlFindElementByPath = xmlFindElementByPath;
exports.xmlFindNodeByPath = xmlFindNodeByPath;
exports.xmlFirstChildElement = xmlFirstChildElement;
exports.xmlFirstDescendantElement = xmlFirstDescendantElement;
exports.xmlInnerText = xmlInnerText;
exports.xmlLocalName = xmlLocalName;
exports.xmlNamespaceURI = xmlNamespaceURI;
exports.xmlNodeValue = xmlNodeValue;
exports.xmlDom = xmlDom;
exports.xmlNewAttribute = xmlNewAttribute;
exports.xmlNewElement = xmlNewElement;
exports.xmlNewFragment = xmlNewFragment;
exports.xmlNewNodeByPath = xmlNewNodeByPath;
exports.xmlNewNSDeclaration = xmlNewNSDeclaration;
exports.xmlNewText = xmlNewText;
exports.xmlParse = xmlParse;
exports.xmlQualifiedName = xmlQualifiedName;
exports.xmlSerialize = xmlSerialize;
exports.xmlSerializeDescendants = xmlSerializeDescendants;
exports.xmlSiblingElement = xmlSiblingElement;
}};
var modules = {};
var require = function (path) {
var name = path.substring(path.lastIndexOf('/') + 1, path.length - 3);
if (modules[name]) { return modules[name].exports; }
modules[name] = { exports: {} };
datas[name].call(this, modules[name].exports, modules[name], require);
return modules[name].exports;
};
window.odatajs = {};
init.call(this, window.odatajs, window.odatajs, require);
})();