Mercurial > hg > audiodb
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 |