├── urlEncode.js ├── LICENSE ├── README.md ├── example.html └── jquery.sparql.js /urlEncode.js: -------------------------------------------------------------------------------- 1 | $.extend({URLEncode:function(c){var o='';var x=0;c=c.toString();var r=/(^[a-zA-Z0-9_.]*)/; 2 | while(x1 && m[1]!=''){o+=m[1];x+=m[1].length; 4 | }else{if(c[x]==' ')o+='+';else{var d=c.charCodeAt(x);var h=d.toString(16); 5 | o+='%'+(h.length<2?'0':'')+h.toUpperCase();}x++;}}return o;}, 6 | URLDecode:function(s){var o=s;var binVal,t;var r=/(%[^%]{2})/; 7 | while((m=r.exec(o))!=null && m.length>1 && m[1]!=''){b=parseInt(m[1].substr(1),16); 8 | t=String.fromCharCode(b);o=o.replace(m[1],t);}return o;} 9 | }); 10 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2010, Joe Geldart 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery SPARQL 2 | ## An idiomatic DSL for SPARQL queries 3 | 4 | [SPARQL] is to linked data as SQL is to relational databases. It is a query 5 | language for semi-structured, open-ended data. SPARQL is also an 6 | increasingly popular way of exposing large datasets, such as those provided 7 | by the [UK government]. 8 | 9 | jQuery SPARQL provides a DSL for programmatically constructing SPARQL queries 10 | against some endpoint. It uses the same 'monadic' style that is used by [jQuery], 11 | [Raphael], [Protovis] and other popular Javascript libraries to allow queries to 12 | be manipulated as first-class objects. The main aim of the library is to 13 | eliminate messy string manipulations to produce and execute well-formed 14 | queries. 15 | 16 | Currently, the only substantial bit of jQuery used is its abstraction for 17 | JSON-P. This may change as we look to concrete application use-cases. Beyond 18 | jQuery, the library uses Yahoo!'s [YQL] service, specifically [Dave Beckett]'s 19 | Triplr table, to wrap around arbitrary SPARQL endpoints (which often don't 20 | support either CORS or JSON-P). It is hoped that this part of the library can 21 | be abstracted out soon. 22 | 23 | ## To Do 24 | 25 | There are many things left to do with this library, including but not limited to: 26 | 27 | - Abstracting out the execution system to support different endpoints and (maybe) 28 | any local SPARQL implementations. 29 | - Support for more sorts of query than just SELECT (including update operators). 30 | - Support for automatic coercion of RDF data types to Javascript ones. 31 | - Integration with follow-on processing, such as DOM population, data analysis, 32 | summarisation and visualisation. 33 | - Production of detailed documentation. 34 | - Lots and lots of tests. 35 | 36 | If you have any ideas about these things, or need to solve them for your work, 37 | please get in touch and contribute to the project! 38 | 39 | [SPARQL]:http://www.w3.org/TR/rdf-sparql-query/ 40 | [jQuery]:http://jquery.com/ 41 | [Raphael]:http://raphaeljs.com/ 42 | [Protovis]:http://vis.stanford.edu/protovis/ 43 | [Dave Beckett]:http://www.dajobe.org/ 44 | [YQL]:http://developer.yahoo.com/yql/ 45 | [UK government]:http://data.gov.uk/ 46 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Examples of usage - jQuery SPARQL 5 | 6 | 7 | 8 | 47 | 48 | 49 |

Examples of usage

50 |

51 | These examples are drawn from the Data.gov.uk education 52 | dataset. They were first described in a blog post 53 | which was chosen for the sake of familiarity and comparison. 54 |

55 |

56 | Each query is shown in standard SPARQL syntax and as expressed in this DSL. The queries may be run by 57 | clicking the "Run Me" link below the two blocks. The results are then shown in the text area. 58 |

59 | 60 |

Results

61 | 64 | 65 |

First 10 schools in the Bristol area ordered by name

66 |

Original SPARQL

67 |
68 |
PREFIX sch-ont: <http://education.data.gov.uk/def/school/>
 69 | SELECT ?name WHERE {
 70 | 	?school a sch-ont:School;
 71 |     sch-ont:establishmentName ?name;
 72 |     sch-ont:districtAdministrative	<http://statistics.data.gov.uk/id/local-authority-district/00HB>;
 73 | }
 74 | ORDER BY ?name
 75 | LIMIT 10
76 |
77 |

Javascript

78 |
79 |
$.sparql("http://gov.tso.co.uk/education/sparql")
 80 |   .prefix("sch-ont","http://education.data.gov.uk/def/school/")
 81 |   .select(["?name"])
 82 |     .where("?school","a","sch-ont:School")
 83 |       .where("sch-ont:establishmentName", "?name")
 84 |       .where("sch-ont:districtAdministrative", "<http://statistics.data.gov.uk/id/local-authority-district/00HB>")
 85 |   .orderby("?name")
 86 |   .limit(10)
 87 |   .execute(cbfunc);
88 |
89 |

Run Me

90 | 91 |

Schools in the Bristol area (with addresses) ordered by name

92 |

Original SPARQL

93 |
94 |
PREFIX sch-ont: <http://education.data.gov.uk/def/school/>
 95 | SELECT ?name ?address1 ?address2 ?postcode ?town WHERE {
 96 |   ?school a sch-ont:School;
 97 |     sch-ont:establishmentName ?name;
 98 |     sch-ont:districtAdministrative <http://statistics.data.gov.uk/id/local-authority-district/00HB>; .
 99 |   OPTIONAL {
100 |     ?school sch-ont:address ?address .
101 |   
102 |     ?address sch-ont:address1 ?address1;
103 |       sch-ont:address2 ?address2 ;
104 |       sch-ont:postcode ?postcode ;
105 |       sch-ont:town ?town .
106 |   }
107 | }
108 | ORDER BY ?name
109 |
110 |

Javascript

111 |
112 |
$.sparql("http://gov.tso.co.uk/education/sparql")
113 |  .prefix("sch-ont", "http://education.data.gov.uk/def/school/")
114 |  .select(["?name", "?address1", "?address2", "?postcode", "?town"])
115 |  .where("?school", "a", "sch-ont:School")
116 |    .where("sch-ont:establishmentName", "?name")
117 |    .where("sch-ont:districtAdministrative", "<http://statistics.data.gov.uk/id/local-authority-district/00HB>")
118 |    .optional()
119 |      .where("?school", "sch-ont:address", "?address")
120 |      .where("?address", "sch-ont:address1", "?address1")
121 |        .where("sch-ont:address2", "?address2")
122 |        .where("sch-ont:postcode", "?postcode")
123 |        .where("sch-ont:town", "?town")
124 |    .end()
125 |  .orderby("?name")
126 |  .execute(cbfunc);
127 |
128 |

Run Me

129 | 130 | 131 | -------------------------------------------------------------------------------- /jquery.sparql.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery SPARQL 3 | * 4 | * Provides an idiomatic jQuery-like interface to SPARQL endpoints. 5 | * Queries are built through method-chaining, before being compiled into a 6 | * string query which can be sent to the endpoint. 7 | * 8 | * $.sparql("http://www.example.com/sparql/").prefix("foaf","http://xmlns.com/0.1/foaf/") 9 | * .select() 10 | * .where("?p", "a", "foaf:Person") 11 | * .where("foaf:name", "?name") 12 | * .where("foaf:homepage", "?page") 13 | * .orderby("?name") 14 | * .distinct() 15 | * .execute(cbfunc); 16 | */ 17 | (function($){ 18 | 19 | $.yql = function(query, callback) { 20 | var url = "http://query.yahooapis.com/v1/public/yql?format=json&q=" + $.URLEncode(query); 21 | $.ajax({ url: url, dataType: "jsonp", success: callback }); 22 | }; 23 | 24 | var URI = function(uri) { 25 | this.uri = uri; 26 | }; 27 | 28 | var Query = function(endpoint, options, parentQuery) { 29 | this.config = { 30 | "endpoint" : endpoint, 31 | "method" : "GET", 32 | "output" : "json" 33 | }; 34 | 35 | this._parentQuery = parentQuery; 36 | this.queryType = "SELECT"; 37 | this.prefixes = []; 38 | this.defaultGraphs = []; 39 | this.namedGraphs = []; 40 | this.variables = []; 41 | this.patterns = []; 42 | this.filters = []; 43 | this.combiner = ""; 44 | this.orders = []; 45 | this.limitCount = -1; 46 | this.offsetCount = 0; 47 | this._prevSubj = null; 48 | this._prevProp = null; 49 | this._storedQuery = ""; 50 | 51 | // Override the defaults with anything interesting 52 | if (options) $.extend(this.config, options); 53 | }; 54 | 55 | Query.prototype.end = function() { 56 | return this._parentQuery; 57 | }; 58 | 59 | Query.prototype.query = function(qstring) { 60 | this._storedQuery = qstring; 61 | return this; 62 | }; 63 | 64 | Query.prototype.execute = function(callback) { 65 | var endpoint = this.config.endpoint; 66 | var method = this.config.method; 67 | var output = this.config.output; 68 | var queryString = this._storedQuery; 69 | 70 | var _clean = function(val) { 71 | if(val.type == "literal") { 72 | return val.value; 73 | } 74 | else if(val.type == "uri") { 75 | return new URI(val.value); 76 | } 77 | else { 78 | return val.value; 79 | } 80 | }; 81 | 82 | var _preproc = function(data) { 83 | var results = data.query.results.sparql.result; 84 | var cleaned_results = []; 85 | for(var r in results) { 86 | var result = results[r]; 87 | var cleaned_obj = {}; 88 | for(var k in result) { 89 | cleaned_obj[k] = _clean(result[k]); 90 | } 91 | cleaned_results.push(cleaned_obj); 92 | } 93 | callback(cleaned_results); 94 | }; 95 | 96 | if (queryString == "") queryString = this.serialiseQuery(); 97 | if(method == "GET") { 98 | var yqlQuery = 'use "http://triplr.org/sparyql/sparql.xml" as sparql; select * from sparql where query="' + queryString + '" and service="' + endpoint + '"'; 99 | $.yql(yqlQuery, _preproc); 100 | 101 | return this; 102 | } 103 | throw "Only GET method supported at this time."; 104 | }; 105 | 106 | Query.prototype.serialiseQuery = function() { 107 | var queryString = []; 108 | 109 | // Prefixes 110 | for(var i = 0; i < this.prefixes.length; i++) { 111 | var pfx = this.prefixes[i]; 112 | queryString.push( "PREFIX " + pfx.prefix + ": <" + pfx.uri + ">"); 113 | } 114 | 115 | // Type and projection 116 | queryString.push(this.queryType); 117 | if(this.combiner != "") { 118 | queryString.push(this.combiner); 119 | } 120 | if(this.queryType == "SELECT" && this.variables.length == 0) { 121 | queryString.push("*"); // No variables is seen as an implicit projection over ALL variables 122 | } 123 | else { 124 | for(var i = 0; i < this.variables.length; i++) { 125 | var v = this.variables[i]; 126 | queryString.push(v); 127 | } 128 | } 129 | 130 | // Add the default graphs 131 | for(var i = 0; i < this.defaultGraphs.length; i++) { 132 | var defaultGraph = this.defaultGraphs[i]; 133 | queryString.push("FROM <" + defaultGraph + ">"); 134 | } 135 | 136 | // Add the named graphs 137 | for(var i = 0; i < this.namedGraphs.length; i++) { 138 | var namedGrph = this.namedGraphs[i]; 139 | queryString.push("FROM NAMED <" + namedGrph + ">"); 140 | } 141 | 142 | // Start WHERE block 143 | queryString.push("WHERE {"); 144 | 145 | // Basic triple patterns and more exotic blocks 146 | for(var i = 0; i < this.patterns.length; i++) { 147 | var pat = this.patterns[i]; 148 | 149 | // Basic triple 150 | if(pat._sort == "triple") { 151 | queryString.push(pat.s + " " + pat.p + " " + pat.o + "."); 152 | } 153 | // Optionals 154 | else if(pat._sort == "optional") { 155 | queryString.push("OPTIONAL"); 156 | queryString.push(pat.subquery.serialiseBlock()); 157 | } 158 | // Graph blocks 159 | else if(pat._sort == "graph") { 160 | queryString.push("GRAPH"); 161 | queryString.push(pat.graphName); 162 | queryString.push(pat.subquery.serialiseBlock()); 163 | } 164 | // Service blocks 165 | else if (pat._sort == "service") { 166 | queryString.push("SERVICE"); 167 | queryString.push("<" + pat.serviceEndpoint + ">"); 168 | queryString.push(pat.subquery.serialiseBlock()); 169 | } 170 | // Just blocks 171 | else if(pat._sort == "block") { 172 | queryString.push(pat.subquery.serialiseBlock()); 173 | } 174 | } 175 | 176 | // Filters 177 | for(var i = 0; i < this.filters.length; i++) { 178 | var flt = this.filters[i]; 179 | queryString.push("FILTER ( " + flt + " )"); 180 | } 181 | 182 | // End WHERE block 183 | queryString.push("}"); 184 | 185 | if(this.orders.length > 0) { 186 | queryString.push("ORDER BY"); 187 | for(var i = 0; i < this.orders.length; i++) { 188 | var odr = this.orders[i]; 189 | queryString.push(odr); 190 | } 191 | } 192 | 193 | if(this.limitCount > -1) { 194 | queryString.push("LIMIT " + this.limitCount); 195 | } 196 | 197 | if(this.offsetCount > 0) { 198 | queryString.push("OFFSET " + this.offsetCount); 199 | } 200 | 201 | return queryString.join(" "); 202 | }; 203 | 204 | Query.prototype.serialiseBlock = function() { 205 | var queryString = []; 206 | 207 | // Start block 208 | queryString.push("{"); 209 | 210 | // Basic triple patterns and more exotic blocks 211 | for(var i = 0; i < this.patterns.length; i++) { 212 | var pat = this.patterns[i]; 213 | 214 | // Basic triple 215 | if(pat._sort == "triple") { 216 | queryString.push(pat.s + " " + pat.p + " " + pat.o + "."); 217 | } 218 | // Optionals 219 | else if(pat._sort == "optional") { 220 | queryString.push("OPTIONAL"); 221 | queryString.push(pat.subquery.serialiseBlock()); 222 | } 223 | // Graph blocks 224 | else if(pat._sort == "graph") { 225 | queryString.push("GRAPH"); 226 | queryString.push(pat.graphName); 227 | queryString.push(pat.subquery.serialiseBlock()); 228 | } 229 | // Service blocks 230 | else if (pat._sort == "service") { 231 | queryString.push("SERVICE"); 232 | queryString.push("<" + pat.serviceEndpoint + ">"); 233 | queryString.push(pat.subquery.serialiseBlock()); 234 | } 235 | // Just blocks 236 | else if(pat._sort == "block") { 237 | queryString.push(pat.subquery.serialiseBlock()); 238 | } 239 | } 240 | 241 | // Filters 242 | for(var i = 0; i < this.filters.length; i++) { 243 | var flt = this.filters[i]; 244 | queryString.push("FILTER ( " + flt + " )"); 245 | } 246 | 247 | // End block 248 | queryString.push("} ."); 249 | 250 | return queryString.join(" "); 251 | 252 | }; 253 | 254 | Query.prototype.distinct = function() { 255 | this.combiner = "DISTINCT"; 256 | return this; 257 | }; 258 | 259 | Query.prototype.reduced = function() { 260 | this.combiner = "REDUCED"; 261 | return this; 262 | }; 263 | 264 | Query.prototype.select = function(variables) { 265 | this.queryType = "SELECT"; 266 | if (variables) this.variables = variables; 267 | return this; 268 | }; 269 | 270 | Query.prototype.describe = function(variables) { 271 | this.queryType = "DESCRIBE"; 272 | if (variables) this.variables = variables; 273 | return this; 274 | }; 275 | 276 | Query.prototype.prefix = function(prefix, uri) { 277 | this.prefixes.push({ "prefix" : prefix, "uri" : uri}); 278 | return this; 279 | }; 280 | 281 | Query.prototype.from = function(graph, isNamed) { 282 | if(isNamed) { 283 | this.namedGraphs.push(graph); 284 | } 285 | else { 286 | this.defaultGraphs.push(graph); 287 | } 288 | return this; 289 | }; 290 | 291 | Query.prototype.where = function(subj, prop, obj) { 292 | if (!obj && !prop) { 293 | // We're in a subj-prop repeating section, use previous subj and prop 294 | return this.where(this._prevSubj, this._prevProp, subj); 295 | } 296 | else if (!obj) { 297 | // We're in a subj repeating section, use previous subj 298 | this._prevProp = subj; 299 | return this.where(this._prevSubj, subj, prop); 300 | } 301 | else { 302 | // We have a full triple 303 | this._prevSubj = subj; 304 | this._prevProp = prop; 305 | this.patterns.push({ "_sort" : "triple", "s" : subj, "p" : prop, "o" : obj }); 306 | return this; 307 | } 308 | }; 309 | 310 | Query.prototype.filter = function(filter) { 311 | this.filters.push(filter); 312 | return this; 313 | }; 314 | 315 | Query.prototype.orderby = function(order) { 316 | this.orders.push(order); 317 | return this; 318 | }; 319 | 320 | Query.prototype.limit = function(limit) { 321 | this.limitCount = limit; 322 | return this; 323 | }; 324 | 325 | Query.prototype.offset = function(offset) { 326 | this.offsetCount = offset; 327 | return this; 328 | }; 329 | 330 | Query.prototype.optional = function() { 331 | var opt = new Query(this.config.endpoint, this.config, this); 332 | this.patterns.push({ "_sort" : "optional", "subquery" : opt }); 333 | return opt; 334 | }; 335 | 336 | Query.prototype.graph = function(name) { 337 | var grph = new Query(this.config.endpoint, this.config, this); 338 | this.patterns.push({ "_sort" : "graph", "graphName" : name, "subquery" : grph }); 339 | return grph; 340 | }; 341 | 342 | Query.prototype.service = function(endpoint) { 343 | var srvc = new Query(this.config.endpoint, this.config, this); 344 | this.patterns.push({ "_sort" : "service", "serviceEndpoint" : endpoint, "subquery" : srvc }); 345 | return srvc; 346 | }; 347 | 348 | Query.prototype.block = function() { 349 | var blk = new Query(this.config.endpoint, this.config, this); 350 | this.patterns.push({ "_sort" : "block", "subquery" : blk }); 351 | return blk; 352 | }; 353 | 354 | $.sparql = function(endpoint, options) { 355 | return new Query(endpoint, options); 356 | }; 357 | 358 | })(jQuery); 359 | --------------------------------------------------------------------------------