Mercurial > hg > audiodb
diff examples/browser/web/js/sparql.js @ 640:901803e1305f
First instance of audioDB browser code.
author | mas01mj |
---|---|
date | Thu, 08 Oct 2009 11:19:11 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/browser/web/js/sparql.js Thu Oct 08 11:19:11 2009 +0000 @@ -0,0 +1,490 @@ +/********************************************************** + Copyright (c) 2006, 2007 + Lee Feigenbaum ( lee AT thefigtrees DOT net ) + Elias Torres ( elias AT torrez DOT us ) + Wing Yung ( wingerz AT gmail DOT com ) + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**********************************************************/ + +/** + * Example client interactions + * + + var sparqler = new SPARQL.Service("http://sparql.org/sparql"); + sparqler.addDefaultGraph("http://thefigtrees.net/lee/ldf-card"); // inherited by all (future) queries + sparqler.addNamedGraph("http://torrez.us/elias/foaf.rdf"); + sparqler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); // inherited by all (future) queries + sparqler.setPrefix("rdf", "http://xmlns.com/foaf/0.1/"); + + sparqler.setRequestHeader("Authentication", "Basic: " + basicAuthString); + + //sparqler.wantOutputAs("application/json"); // for now we only do JSON + + var query = sparqler.createQuery(); + query.addDefualtGraph(...) query.addNamedGraph(...) query.setPrefix(...) query.setRequestHeader(...) // this query only + + // query forms: + + // passes standard JSON results object to success callback + query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#"); + query.query("SELECT ?who ?mbox WHERE { ldf:LDF foaf:knows ?who . ?who foaf:mbox ?mbox }", + {failure: onFailure, success: function(json) { for (var x in json.head.vars) { ... } ...}} + ); + + // passes boolean value to success callback + query.ask("ASK ?person WHERE { ?person foaf:knows [ foaf:name "Dan Connolly" ] }", + {failure: onFailure, success: function(bool) { if (bool) ... }} + ); + + // passes a single vector (array) of values representing a single column of results to success callback + query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#"); + var addresses = query.selectValues("SELECT ?mbox WHERE { _:someone foaf:mbox ?mbox }", + {failure: onFailure, success: function(values) { for (var i = 0; i < values.length; i++) { ... values[i] ...} } } + ); + + // passes a single value representing a single row of a single column (variable) to success callback + query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#"); + var myAddress = query.selectSingleValue("SELECT ?mbox WHERE {ldf:LDF foaf:mbox ?mbox }", + {failure: onFailure, success: function(value) { alert("value is: " + value); } } + ); + + // shortcuts for all of the above (w/o ability to set any query-specific graphs or prefixes) + sparqler.query(...) sparqler.ask(...) sparqler.selectValues(...) sparqler.selectSingleValue(...) + + + */ + +var SPARQL = {}; // SPARQL namespace + + +/** + * Both SPARQL service objects and SPARQL query objects receive one query utility method + * per entry in this dictionary. The key is the name of the method, and the value is a function + * that transforms the standard JSON output into a more useful form. The return value of a + * transformation function is passed into any 'success' callback function provided when the query + * is issued. The following transformations are included: + * + query -- identity transform; returns the JSON structure unchanged + * + ask -- for ASK queries; returns a boolean value indicating the answer to the query + * + selectValues -- for SELECT queries with a single variable; returns an array containing + * the answers to the query + * + selectSingleValue -- for SELECT queries returning one column with one row; returns the + * value in the first (and presumably, only) cell in the resultset + * + selectValueArrays -- for SELECT queries returning independent columns; returns a hash + * keyed on variable name with values as arrays of answers for that variable. Useful + * for UNION queries. + * Note that all of the transformations that return values directly lose any type information + * and the ability to distinguish between URIs, blank nodes, and literals. + */ +SPARQL._query_transformations = { + query: function (o) { return o; }, + ask: function (o) { return o["boolean"]; }, + selectValues: function (o) { + var v = o.head.vars[0]; // assume one variable + var values = []; + for (var i = 0; i < o.results.bindings.length; i++) + values.push(o.results.bindings[i][v].value); + return values; + }, + selectSingleValue: function(o) { return o.results.bindings[0][o.head.vars[0]].value; }, + selectValueArrays: function(o) { + // factor by value (useful for UNION queries) + var ret = {}; + for (var i = 0; i < o.head.vars.length; i++) + ret[o.head.vars[i]] = []; + for (var i = 0; i < o.results.bindings.length; i++) + for (var v in o.results.bindings[i]) + if (ret[v] instanceof Array) ret[v].push(o.results.bindings[i][v].value); + return ret; + }, + selectValueHashes: function(o) { + var hashes = []; + for (var i = 0; i < o.results.bindings.length; i++) { + var hash = {}; + for (var v in o.results.bindings[i]) + hash[v] = o.results.bindings[i][v].value; + hashes.push(hash); + } + return hashes; + } +}; + +SPARQL.statistics = { + queries_sent : 0, + successes : 0, + failures : 0 +}; + +// A SPARQL service represents a single endpoint which implements the HTTP (GET or POST) +// bindings of the SPARQL Protocol. It provides convenience methods to set dataset and +// prefix options for all queries created for this endpoint. +SPARQL.Service = function(endpoint) { + //--------------- + // private fields + var _endpoint = endpoint; + var _default_graphs = []; + var _named_graphs = []; + var _prefix_map = {}; + var _method = 'POST'; + var _output = 'json'; + var _max_simultaneous = 0; + var _request_headers = {}; + + //---------- + // accessors + this.endpoint = function() { return _endpoint; }; + this.defaultGraphs = function() { return _default_graphs; }; + this.namedGraphs = function() { return _named_graphs; }; + this.prefixes = function() { return _prefix_map; }; + this.method = function() { return _method; }; + this.output = function() { return _output; }; + this.maxSimultaneousQueries = function() { return _max_simultaneous; }; + this.requestHeaders = function() { return _request_headers; }; + + //--------- + // mutators + function _add_graphs(toAdd, arr) { + if (toAdd instanceof Array) + for (var i = 0; i < toAdd.length; i++) arr.push(toAdd[i]); + else + arr.push(toAdd); + } + this.addDefaultGraph = function(g) { _add_graphs(g, this.defaultGraphs()); }; + this.addNamedGraph = function(g) { _add_graphs(g, this.namedGraphs()); }; + this.setPrefix = function(p, u) { this.prefixes()[p] = u; }; + this.createQuery = function(p) { return new SPARQL.Query(this, p); }; + this.setMethod = function(m) { + if (m != 'GET' && m != 'POST') throw("HTTP methods other than GET and POST are not supported."); + _method = m; + }; + this.setOutput = function(o) { _output = o; }; + this.setMaxSimultaneousQueries = function(m) { _max_simultaneous = m; }; + this.setRequestHeader = function(h, v) { _request_headers[h] = v; }; + + //--------------- + // protected methods (should only be called within this module + this._active_queries = 0; + this._queued_queries = []; + this._next_in_queue = 0; + this._canRun = function() { return this.maxSimultaneousQueries() <= 0 || this._active_queries < this.maxSimultaneousQueries();}; + this._queue = function(q,f, p) { + if (!p) p = 0; + if (p > 0) { + for (var i = 0; i < this._queued_queries.length; i++) { + if (this._queued_queries[i] != null && this._queued_queries[i][2] < p) { + this._queued_queries.splice(i, 0, [q, f, p]); + return; + } + } + } + this._queued_queries.push([q,f,p]); + }; + this._markRunning = function(q) { this._active_queries++; }; + this._markDone = function(q) { + this._active_queries--; + //document.getElementById('log').innerHTML+="query done. " + this._active_queries + " queries still active.<br>"; + if (this._queued_queries[this._next_in_queue] != null && this._canRun()) { + var a = this._queued_queries[this._next_in_queue]; + this._queued_queries[this._next_in_queue++] = null; + // a[0] is query object, a[1] is function to run query + //document.getElementById('log').innerHTML += "running query from Q<br>"; + a[1](); + } + }; + + //--------------- + // public methods + + // use our varied transformations to create the various shortcut methods of actually + // issuing queries without explicitly creating a query object + for (var query_form in SPARQL._query_transformations) { + // need the extra function to properly scope query_form (qf) + this[query_form] = (function(qf) { + return function(queryString, callback) { + var q = this.createQuery(); + q._doQuery(queryString, callback, SPARQL._query_transformations[qf]); + }; + })(query_form); + } + + //------------ + // constructor + + if (!_endpoint) + return null; + + return this; +} + +/** + * A SPARQL query object should be created using the createQuery method of a SPARQL + * service object. It allows prefixes and datasets to be defined specifically for + * a single query, and provides introspective methods to see the query string and the + * full (HTTP GET) URL of the query. + */ +SPARQL.Query = function(service, priority) { + //--------------- + // private fields + var _conn = null; + var _service = service; + var _default_graphs = clone_obj(service.defaultGraphs()); // prevent future updates from affecting us + var _named_graphs = clone_obj(service.namedGraphs()); + var _prefix_map = clone_obj(service.prefixes()); + var _user_query = ''; // doesn't include auto-generated prefix declarations + var _method = service.method(); + var _output = service.output(); + var _priority = priority || 0; + var _request_headers = clone_obj(service.requestHeaders()); + + //------------------ + // private functions + function _create_json(text) { + if (!text) + return null; + // make sure this is safe JSON + // see: http://www.ietf.org/internet-drafts/draft-crockford-jsonorg-json-03.txt + + // (1) strip out quoted strings + var no_strings = text.replace(/"(\\.|[^"\\])*"/g, ''); + // (2) make sure that all the characters are explicitly part of the JSON grammar + // (in particular, note as discussed in the IETF submission, there are no assignments + // or function invocations allowed by this reg. exp.) + var hasBadCharacter = /[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(no_strings); + // (3) evaluate the JSON string, returning its contents + if (!hasBadCharacter) { + try { + return eval('(' + text + ')'); + } catch (e) { + return null; + } + } + return null; + } + + function clone_obj(o) { + var o2 = o instanceof Array ? [] : {}; + for(var x in o) {o2[x] = o[x];} + return o2; + } + + //---------------- + // private methods + this._doCallback = function(cb, which, arg) { + //document.getElementById('log').innerHTML += "_doCallback ... <br>"; + var user_data = "argument" in cb ? cb.argument : null; + if (which in cb) { + if (cb.scope) { + cb[which].apply(cb.scope, [arg, user_data]); + } else { + cb[which](arg, user_data); + } + } + } + + this._queryFailure = function(xhr, arg) { + SPARQL.statistics.failures++; + _service._markDone(this); + this._doCallback(arg.callback, 'failure', xhr /* just pass through the connection response object */); + }; + this._querySuccess = function(xhr, arg) { + //alert(xhr.responseText); + SPARQL.statistics.successes++; + _service._markDone(this); + this._doCallback(arg.callback, 'success', arg.transformer( + _output == 'json' ? _create_json(xhr.responseText) : xhr.responseText + )); + }; + + function getXmlHttpRequest(url) { + // right now, this only does Firefox (Opera? Safari?) + return new XMLHttpRequest(); + } + + this._doQuery = function(queryString, callback, transformer) { + _user_query = queryString; + if (_service._canRun()) { + try { + if (_method != 'POST' && _method != 'GET') + throw("HTTP methods other than GET and POST are not supported."); + + var url = _method == 'GET' ? this.queryUrl() : this.service().endpoint(); + var xhr = getXmlHttpRequest(url); + var content = null; + + try { + if (!document.domain || (url.match(/^https?:\/\//) && url.slice(7, document.domain.length + 7) != document.domain && window.netscape && netscape.security && netscape.security.PrivilegeManager)) { + netscape.security.PrivilegeManager.enablePrivilege( "UniversalBrowserRead"); + netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect"); + } + } catch(e) { + alert("Cross-site requests prohibited. You will only be able to SPARQL the origin site: " + e); + return; + } + + xhr.open(_method, url, true /* async */); + + // set the headers, including the content-type for POSTed queries + for (var header in this.requestHeaders()) + if (typeof(this.requestHeaders()[header]) != "function") + xhr.setRequestHeader(header, this.requestHeaders()[header]); + if (_method == 'POST') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + content = this.queryParameters(); + } + + SPARQL.statistics.queries_sent++; + _service._markRunning(this); + + var callbackData = { + scope:this, + success: this._querySuccess, + failure: this._queryFailure, + argument: { + transformer: transformer, + callback: callback + } + }; + + // I've seen some strange race-condition behavior (strange since normally + // JS is single-threaded, so synchronization conditions don't occur barring + // reentrancy) with onreadystatechange. Instead, we poll asynchronously to + // determine when the request is done. + var token = window.setInterval( + function () { + if (xhr.readyState == 4) { // ready! + // clear this handler + window.clearInterval(token); + // we check the status to know which callback to call + if (xhr.status >= 200 && xhr.status < 300) + callbackData.success.apply(callbackData.scope, [xhr, callbackData.argument]); + else + callbackData.failure.apply(callbackData.scope, [xhr, callbackData.argument]); + } + }, + 200 /* maybe this should be customizable */ + ); + + xhr.send(content); + } catch (e) { + alert("Error sending SPARQL query: " + e); + } + } else { + var self = this; + _service._queue(self, function() { self._doQuery(queryString, callback, transformer); }, _priority); + } + }; + + + //---------- + // accessors + this.request = function() { return _conn; }; + this.service = function() { return _service; }; + this.defaultGraphs = function() { return _default_graphs; }; + this.namedGraphs = function() { return _named_graphs; }; + this.prefixes = function() { return _prefix_map; }; + this.method = function() { return _method; }; + this.requestHeaders = function() { return _request_headers; }; + + + /** + * Returns the SPARQL query represented by this object. The parameter, which can + * be omitted, determines whether or not auto-generated PREFIX clauses are included + * in the returned query string. + */ + this.queryString = function(excludePrefixes) { + var preamble = ''; + if (!excludePrefixes) { + for (var prefix in this.prefixes()) { + if(typeof(this.prefixes()[prefix]) != 'string') continue; + preamble += 'PREFIX ' + prefix + ': <' + this.prefixes()[prefix] + '> '; + } + } + return preamble + _user_query; + }; + + /** + * Returns the HTTP query parameters to invoke this query. This includes entries for + * all of the default graphs, the named graphs, the SPARQL query itself, and an + * output parameter to specify JSON (or other) output is desired. + */ + this.queryParameters = function () { + var urlQueryString = ''; + var i; + + // add default and named graphs to the protocol invocation + for (i = 0; i < this.defaultGraphs().length; i++) urlQueryString += 'default-graph-uri=' + encodeURIComponent(this.defaultGraphs()[i]) + '&'; + for (i = 0; i < this.namedGraphs().length; i++) urlQueryString += 'named-graph-uri=' + encodeURIComponent(this.namedGraphs()[i]) + '&'; + + // specify JSON output (currently output= supported by latest Joseki) (or other output) + urlQueryString += 'output=' + _output + '&soft-limit=-1&'; + return urlQueryString + 'query=' + encodeURIComponent(this.queryString()); + } + + /** + * Returns the HTTP GET URL to invoke this query. (Note that this returns a full HTTP GET URL + * even if this query is set to actually use POST.) + */ + this.queryUrl = function() { + var url = this.service().endpoint() + '?'; + return url + this.queryParameters(); + }; + + //--------- + // mutators + function _add_graphs(toAdd, arr) { + if (toAdd instanceof Array) + for (var i = 0; i < toAdd.length; i++) arr.push(toAdd[i]); + else + arr.push(toAdd); + } + this.addDefaultGraph = function(g) { _add_graphs(g, this.defaultGraphs()); }; + this.addNamedGraph = function(g) { _add_graphs(g, this.namedGraphs()); }; + this.setPrefix = function(p, u) { this.prefixes()[p] = u; }; + this.setMethod = function(m) { + if (m != 'GET' && m != 'POST') throw("HTTP methods other than GET and POST are not supported."); + _method = m; + }; + this.setRequestHeader = function(h, v) { _request_headers[h] = v; }; + + //--------------- + // public methods + + // use our varied transformations to create the various methods of actually issuing + // queries + for (var query_form in SPARQL._query_transformations) { + // need the extra function to properly scope query_form (qf) + this[query_form] = (function(qf) { + return function(queryString, callback) { + this._doQuery(queryString, callback, SPARQL._query_transformations[qf]); + }; + })(query_form); + } + + + //------------ + // constructor + + return this; +} + +// Nothing to see here, yet. +SPARQL.QueryUtilities = { +}; +