/********************************************************** * * COEUS * Semantic Web Application Framework * * Javascript API * * * Example interactions var sparqler = new SPARQL.Service("http://bioinformatics.ua.pt/coeus/sparql"); sparqler.setPrefix("dc", "http://purl.org/dc/elements/1.1/"); sparqler.setPrefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#"); sparqler.setPrefix("coeus", "http://bioinformatics.ua.pt/coeus/resource/"); sparqler.setRequestHeader("Authentication", "Basic: " + basicAuthString); //sparqler.wantOutputAs("application/json"); // for now we only do JSON var query = sparqler.createQuery();: // passes standard JSON results object to success callback 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 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 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.
"; 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
"; 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 ...
"; 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 + '&'; 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; }; /********************************************************** Original 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. **********************************************************/