comparison 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
comparison
equal deleted inserted replaced
639:2eaea1afd6b3 640:901803e1305f
1 /**********************************************************
2 Copyright (c) 2006, 2007
3 Lee Feigenbaum ( lee AT thefigtrees DOT net )
4 Elias Torres ( elias AT torrez DOT us )
5 Wing Yung ( wingerz AT gmail DOT com )
6 All rights reserved.
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25 **********************************************************/
26
27 /**
28 * Example client interactions
29 *
30
31 var sparqler = new SPARQL.Service("http://sparql.org/sparql");
32 sparqler.addDefaultGraph("http://thefigtrees.net/lee/ldf-card"); // inherited by all (future) queries
33 sparqler.addNamedGraph("http://torrez.us/elias/foaf.rdf");
34 sparqler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); // inherited by all (future) queries
35 sparqler.setPrefix("rdf", "http://xmlns.com/foaf/0.1/");
36
37 sparqler.setRequestHeader("Authentication", "Basic: " + basicAuthString);
38
39 //sparqler.wantOutputAs("application/json"); // for now we only do JSON
40
41 var query = sparqler.createQuery();
42 query.addDefualtGraph(...) query.addNamedGraph(...) query.setPrefix(...) query.setRequestHeader(...) // this query only
43
44 // query forms:
45
46 // passes standard JSON results object to success callback
47 query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#");
48 query.query("SELECT ?who ?mbox WHERE { ldf:LDF foaf:knows ?who . ?who foaf:mbox ?mbox }",
49 {failure: onFailure, success: function(json) { for (var x in json.head.vars) { ... } ...}}
50 );
51
52 // passes boolean value to success callback
53 query.ask("ASK ?person WHERE { ?person foaf:knows [ foaf:name "Dan Connolly" ] }",
54 {failure: onFailure, success: function(bool) { if (bool) ... }}
55 );
56
57 // passes a single vector (array) of values representing a single column of results to success callback
58 query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#");
59 var addresses = query.selectValues("SELECT ?mbox WHERE { _:someone foaf:mbox ?mbox }",
60 {failure: onFailure, success: function(values) { for (var i = 0; i < values.length; i++) { ... values[i] ...} } }
61 );
62
63 // passes a single value representing a single row of a single column (variable) to success callback
64 query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#");
65 var myAddress = query.selectSingleValue("SELECT ?mbox WHERE {ldf:LDF foaf:mbox ?mbox }",
66 {failure: onFailure, success: function(value) { alert("value is: " + value); } }
67 );
68
69 // shortcuts for all of the above (w/o ability to set any query-specific graphs or prefixes)
70 sparqler.query(...) sparqler.ask(...) sparqler.selectValues(...) sparqler.selectSingleValue(...)
71
72
73 */
74
75 var SPARQL = {}; // SPARQL namespace
76
77
78 /**
79 * Both SPARQL service objects and SPARQL query objects receive one query utility method
80 * per entry in this dictionary. The key is the name of the method, and the value is a function
81 * that transforms the standard JSON output into a more useful form. The return value of a
82 * transformation function is passed into any 'success' callback function provided when the query
83 * is issued. The following transformations are included:
84 * + query -- identity transform; returns the JSON structure unchanged
85 * + ask -- for ASK queries; returns a boolean value indicating the answer to the query
86 * + selectValues -- for SELECT queries with a single variable; returns an array containing
87 * the answers to the query
88 * + selectSingleValue -- for SELECT queries returning one column with one row; returns the
89 * value in the first (and presumably, only) cell in the resultset
90 * + selectValueArrays -- for SELECT queries returning independent columns; returns a hash
91 * keyed on variable name with values as arrays of answers for that variable. Useful
92 * for UNION queries.
93 * Note that all of the transformations that return values directly lose any type information
94 * and the ability to distinguish between URIs, blank nodes, and literals.
95 */
96 SPARQL._query_transformations = {
97 query: function (o) { return o; },
98 ask: function (o) { return o["boolean"]; },
99 selectValues: function (o) {
100 var v = o.head.vars[0]; // assume one variable
101 var values = [];
102 for (var i = 0; i < o.results.bindings.length; i++)
103 values.push(o.results.bindings[i][v].value);
104 return values;
105 },
106 selectSingleValue: function(o) { return o.results.bindings[0][o.head.vars[0]].value; },
107 selectValueArrays: function(o) {
108 // factor by value (useful for UNION queries)
109 var ret = {};
110 for (var i = 0; i < o.head.vars.length; i++)
111 ret[o.head.vars[i]] = [];
112 for (var i = 0; i < o.results.bindings.length; i++)
113 for (var v in o.results.bindings[i])
114 if (ret[v] instanceof Array) ret[v].push(o.results.bindings[i][v].value);
115 return ret;
116 },
117 selectValueHashes: function(o) {
118 var hashes = [];
119 for (var i = 0; i < o.results.bindings.length; i++) {
120 var hash = {};
121 for (var v in o.results.bindings[i])
122 hash[v] = o.results.bindings[i][v].value;
123 hashes.push(hash);
124 }
125 return hashes;
126 }
127 };
128
129 SPARQL.statistics = {
130 queries_sent : 0,
131 successes : 0,
132 failures : 0
133 };
134
135 // A SPARQL service represents a single endpoint which implements the HTTP (GET or POST)
136 // bindings of the SPARQL Protocol. It provides convenience methods to set dataset and
137 // prefix options for all queries created for this endpoint.
138 SPARQL.Service = function(endpoint) {
139 //---------------
140 // private fields
141 var _endpoint = endpoint;
142 var _default_graphs = [];
143 var _named_graphs = [];
144 var _prefix_map = {};
145 var _method = 'POST';
146 var _output = 'json';
147 var _max_simultaneous = 0;
148 var _request_headers = {};
149
150 //----------
151 // accessors
152 this.endpoint = function() { return _endpoint; };
153 this.defaultGraphs = function() { return _default_graphs; };
154 this.namedGraphs = function() { return _named_graphs; };
155 this.prefixes = function() { return _prefix_map; };
156 this.method = function() { return _method; };
157 this.output = function() { return _output; };
158 this.maxSimultaneousQueries = function() { return _max_simultaneous; };
159 this.requestHeaders = function() { return _request_headers; };
160
161 //---------
162 // mutators
163 function _add_graphs(toAdd, arr) {
164 if (toAdd instanceof Array)
165 for (var i = 0; i < toAdd.length; i++) arr.push(toAdd[i]);
166 else
167 arr.push(toAdd);
168 }
169 this.addDefaultGraph = function(g) { _add_graphs(g, this.defaultGraphs()); };
170 this.addNamedGraph = function(g) { _add_graphs(g, this.namedGraphs()); };
171 this.setPrefix = function(p, u) { this.prefixes()[p] = u; };
172 this.createQuery = function(p) { return new SPARQL.Query(this, p); };
173 this.setMethod = function(m) {
174 if (m != 'GET' && m != 'POST') throw("HTTP methods other than GET and POST are not supported.");
175 _method = m;
176 };
177 this.setOutput = function(o) { _output = o; };
178 this.setMaxSimultaneousQueries = function(m) { _max_simultaneous = m; };
179 this.setRequestHeader = function(h, v) { _request_headers[h] = v; };
180
181 //---------------
182 // protected methods (should only be called within this module
183 this._active_queries = 0;
184 this._queued_queries = [];
185 this._next_in_queue = 0;
186 this._canRun = function() { return this.maxSimultaneousQueries() <= 0 || this._active_queries < this.maxSimultaneousQueries();};
187 this._queue = function(q,f, p) {
188 if (!p) p = 0;
189 if (p > 0) {
190 for (var i = 0; i < this._queued_queries.length; i++) {
191 if (this._queued_queries[i] != null && this._queued_queries[i][2] < p) {
192 this._queued_queries.splice(i, 0, [q, f, p]);
193 return;
194 }
195 }
196 }
197 this._queued_queries.push([q,f,p]);
198 };
199 this._markRunning = function(q) { this._active_queries++; };
200 this._markDone = function(q) {
201 this._active_queries--;
202 //document.getElementById('log').innerHTML+="query done. " + this._active_queries + " queries still active.<br>";
203 if (this._queued_queries[this._next_in_queue] != null && this._canRun()) {
204 var a = this._queued_queries[this._next_in_queue];
205 this._queued_queries[this._next_in_queue++] = null;
206 // a[0] is query object, a[1] is function to run query
207 //document.getElementById('log').innerHTML += "running query from Q<br>";
208 a[1]();
209 }
210 };
211
212 //---------------
213 // public methods
214
215 // use our varied transformations to create the various shortcut methods of actually
216 // issuing queries without explicitly creating a query object
217 for (var query_form in SPARQL._query_transformations) {
218 // need the extra function to properly scope query_form (qf)
219 this[query_form] = (function(qf) {
220 return function(queryString, callback) {
221 var q = this.createQuery();
222 q._doQuery(queryString, callback, SPARQL._query_transformations[qf]);
223 };
224 })(query_form);
225 }
226
227 //------------
228 // constructor
229
230 if (!_endpoint)
231 return null;
232
233 return this;
234 }
235
236 /**
237 * A SPARQL query object should be created using the createQuery method of a SPARQL
238 * service object. It allows prefixes and datasets to be defined specifically for
239 * a single query, and provides introspective methods to see the query string and the
240 * full (HTTP GET) URL of the query.
241 */
242 SPARQL.Query = function(service, priority) {
243 //---------------
244 // private fields
245 var _conn = null;
246 var _service = service;
247 var _default_graphs = clone_obj(service.defaultGraphs()); // prevent future updates from affecting us
248 var _named_graphs = clone_obj(service.namedGraphs());
249 var _prefix_map = clone_obj(service.prefixes());
250 var _user_query = ''; // doesn't include auto-generated prefix declarations
251 var _method = service.method();
252 var _output = service.output();
253 var _priority = priority || 0;
254 var _request_headers = clone_obj(service.requestHeaders());
255
256 //------------------
257 // private functions
258 function _create_json(text) {
259 if (!text)
260 return null;
261 // make sure this is safe JSON
262 // see: http://www.ietf.org/internet-drafts/draft-crockford-jsonorg-json-03.txt
263
264 // (1) strip out quoted strings
265 var no_strings = text.replace(/"(\\.|[^"\\])*"/g, '');
266 // (2) make sure that all the characters are explicitly part of the JSON grammar
267 // (in particular, note as discussed in the IETF submission, there are no assignments
268 // or function invocations allowed by this reg. exp.)
269 var hasBadCharacter = /[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(no_strings);
270 // (3) evaluate the JSON string, returning its contents
271 if (!hasBadCharacter) {
272 try {
273 return eval('(' + text + ')');
274 } catch (e) {
275 return null;
276 }
277 }
278 return null;
279 }
280
281 function clone_obj(o) {
282 var o2 = o instanceof Array ? [] : {};
283 for(var x in o) {o2[x] = o[x];}
284 return o2;
285 }
286
287 //----------------
288 // private methods
289 this._doCallback = function(cb, which, arg) {
290 //document.getElementById('log').innerHTML += "_doCallback ... <br>";
291 var user_data = "argument" in cb ? cb.argument : null;
292 if (which in cb) {
293 if (cb.scope) {
294 cb[which].apply(cb.scope, [arg, user_data]);
295 } else {
296 cb[which](arg, user_data);
297 }
298 }
299 }
300
301 this._queryFailure = function(xhr, arg) {
302 SPARQL.statistics.failures++;
303 _service._markDone(this);
304 this._doCallback(arg.callback, 'failure', xhr /* just pass through the connection response object */);
305 };
306 this._querySuccess = function(xhr, arg) {
307 //alert(xhr.responseText);
308 SPARQL.statistics.successes++;
309 _service._markDone(this);
310 this._doCallback(arg.callback, 'success', arg.transformer(
311 _output == 'json' ? _create_json(xhr.responseText) : xhr.responseText
312 ));
313 };
314
315 function getXmlHttpRequest(url) {
316 // right now, this only does Firefox (Opera? Safari?)
317 return new XMLHttpRequest();
318 }
319
320 this._doQuery = function(queryString, callback, transformer) {
321 _user_query = queryString;
322 if (_service._canRun()) {
323 try {
324 if (_method != 'POST' && _method != 'GET')
325 throw("HTTP methods other than GET and POST are not supported.");
326
327 var url = _method == 'GET' ? this.queryUrl() : this.service().endpoint();
328 var xhr = getXmlHttpRequest(url);
329 var content = null;
330
331 try {
332 if (!document.domain || (url.match(/^https?:\/\//) && url.slice(7, document.domain.length + 7) != document.domain && window.netscape && netscape.security && netscape.security.PrivilegeManager)) {
333 netscape.security.PrivilegeManager.enablePrivilege( "UniversalBrowserRead");
334 netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect");
335 }
336 } catch(e) {
337 alert("Cross-site requests prohibited. You will only be able to SPARQL the origin site: " + e);
338 return;
339 }
340
341 xhr.open(_method, url, true /* async */);
342
343 // set the headers, including the content-type for POSTed queries
344 for (var header in this.requestHeaders())
345 if (typeof(this.requestHeaders()[header]) != "function")
346 xhr.setRequestHeader(header, this.requestHeaders()[header]);
347 if (_method == 'POST') {
348 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
349 content = this.queryParameters();
350 }
351
352 SPARQL.statistics.queries_sent++;
353 _service._markRunning(this);
354
355 var callbackData = {
356 scope:this,
357 success: this._querySuccess,
358 failure: this._queryFailure,
359 argument: {
360 transformer: transformer,
361 callback: callback
362 }
363 };
364
365 // I've seen some strange race-condition behavior (strange since normally
366 // JS is single-threaded, so synchronization conditions don't occur barring
367 // reentrancy) with onreadystatechange. Instead, we poll asynchronously to
368 // determine when the request is done.
369 var token = window.setInterval(
370 function () {
371 if (xhr.readyState == 4) { // ready!
372 // clear this handler
373 window.clearInterval(token);
374 // we check the status to know which callback to call
375 if (xhr.status >= 200 && xhr.status < 300)
376 callbackData.success.apply(callbackData.scope, [xhr, callbackData.argument]);
377 else
378 callbackData.failure.apply(callbackData.scope, [xhr, callbackData.argument]);
379 }
380 },
381 200 /* maybe this should be customizable */
382 );
383
384 xhr.send(content);
385 } catch (e) {
386 alert("Error sending SPARQL query: " + e);
387 }
388 } else {
389 var self = this;
390 _service._queue(self, function() { self._doQuery(queryString, callback, transformer); }, _priority);
391 }
392 };
393
394
395 //----------
396 // accessors
397 this.request = function() { return _conn; };
398 this.service = function() { return _service; };
399 this.defaultGraphs = function() { return _default_graphs; };
400 this.namedGraphs = function() { return _named_graphs; };
401 this.prefixes = function() { return _prefix_map; };
402 this.method = function() { return _method; };
403 this.requestHeaders = function() { return _request_headers; };
404
405
406 /**
407 * Returns the SPARQL query represented by this object. The parameter, which can
408 * be omitted, determines whether or not auto-generated PREFIX clauses are included
409 * in the returned query string.
410 */
411 this.queryString = function(excludePrefixes) {
412 var preamble = '';
413 if (!excludePrefixes) {
414 for (var prefix in this.prefixes()) {
415 if(typeof(this.prefixes()[prefix]) != 'string') continue;
416 preamble += 'PREFIX ' + prefix + ': <' + this.prefixes()[prefix] + '> ';
417 }
418 }
419 return preamble + _user_query;
420 };
421
422 /**
423 * Returns the HTTP query parameters to invoke this query. This includes entries for
424 * all of the default graphs, the named graphs, the SPARQL query itself, and an
425 * output parameter to specify JSON (or other) output is desired.
426 */
427 this.queryParameters = function () {
428 var urlQueryString = '';
429 var i;
430
431 // add default and named graphs to the protocol invocation
432 for (i = 0; i < this.defaultGraphs().length; i++) urlQueryString += 'default-graph-uri=' + encodeURIComponent(this.defaultGraphs()[i]) + '&';
433 for (i = 0; i < this.namedGraphs().length; i++) urlQueryString += 'named-graph-uri=' + encodeURIComponent(this.namedGraphs()[i]) + '&';
434
435 // specify JSON output (currently output= supported by latest Joseki) (or other output)
436 urlQueryString += 'output=' + _output + '&soft-limit=-1&';
437 return urlQueryString + 'query=' + encodeURIComponent(this.queryString());
438 }
439
440 /**
441 * Returns the HTTP GET URL to invoke this query. (Note that this returns a full HTTP GET URL
442 * even if this query is set to actually use POST.)
443 */
444 this.queryUrl = function() {
445 var url = this.service().endpoint() + '?';
446 return url + this.queryParameters();
447 };
448
449 //---------
450 // mutators
451 function _add_graphs(toAdd, arr) {
452 if (toAdd instanceof Array)
453 for (var i = 0; i < toAdd.length; i++) arr.push(toAdd[i]);
454 else
455 arr.push(toAdd);
456 }
457 this.addDefaultGraph = function(g) { _add_graphs(g, this.defaultGraphs()); };
458 this.addNamedGraph = function(g) { _add_graphs(g, this.namedGraphs()); };
459 this.setPrefix = function(p, u) { this.prefixes()[p] = u; };
460 this.setMethod = function(m) {
461 if (m != 'GET' && m != 'POST') throw("HTTP methods other than GET and POST are not supported.");
462 _method = m;
463 };
464 this.setRequestHeader = function(h, v) { _request_headers[h] = v; };
465
466 //---------------
467 // public methods
468
469 // use our varied transformations to create the various methods of actually issuing
470 // queries
471 for (var query_form in SPARQL._query_transformations) {
472 // need the extra function to properly scope query_form (qf)
473 this[query_form] = (function(qf) {
474 return function(queryString, callback) {
475 this._doQuery(queryString, callback, SPARQL._query_transformations[qf]);
476 };
477 })(query_form);
478 }
479
480
481 //------------
482 // constructor
483
484 return this;
485 }
486
487 // Nothing to see here, yet.
488 SPARQL.QueryUtilities = {
489 };
490