http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/store/dom.js ---------------------------------------------------------------------- diff --git a/lib/store/dom.js b/lib/store/dom.js new file mode 100644 index 0000000..e14bb6b --- /dev/null +++ b/lib/store/dom.js @@ -0,0 +1,332 @@ +/* + * 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. + */ +'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; \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/store/indexeddb.js ---------------------------------------------------------------------- diff --git a/lib/store/indexeddb.js b/lib/store/indexeddb.js new file mode 100644 index 0000000..d7527c1 --- /dev/null +++ b/lib/store/indexeddb.js @@ -0,0 +1,447 @@ +/* + * 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. + */ +'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; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/store/memory.js ---------------------------------------------------------------------- diff --git a/lib/store/memory.js b/lib/store/memory.js new file mode 100644 index 0000000..a9c69d4 --- /dev/null +++ b/lib/store/memory.js @@ -0,0 +1,247 @@ +/* + * 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. + */ +'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; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/utils.js ---------------------------------------------------------------------- diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..429a56a --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,581 @@ +/* + * 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. + */ +'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; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/xml.js ---------------------------------------------------------------------- diff --git a/lib/xml.js b/lib/xml.js new file mode 100644 index 0000000..194b4ba --- /dev/null +++ b/lib/xml.js @@ -0,0 +1,824 @@ +/* + * 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. + */ +'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; http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/package.json ---------------------------------------------------------------------- diff --git a/package.json b/package.json index e48ba0a..044cb81 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "odatajs", "version": "4.0.0", - "postfix": "beta-01", + "postfix": "", "releaseCandidate": "", "title": "Olingo OData Client for JavaScript", "description": "the Olingo OData Client for JavaScript library is a new cross-browser JavaScript 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 easy to use.",
