├── _config.yml ├── package.json ├── LICENSE ├── fiddles.ttl ├── index.html ├── README.md └── sparql-fiddle.js /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparql-fiddle", 3 | "version": "0.1.0", 4 | "description": "run SPARQL queries online and off using rdflib.js", 5 | "main": "sparql-fiddle.js", 6 | "dependencies": { 7 | "rdflib": "^0.19.1" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [ 14 | "solid", 15 | "linked-data" 16 | ], 17 | "author": "Jeff Zucker", 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jeff Zucker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /fiddles.ttl: -------------------------------------------------------------------------------- 1 | @prefix : <#>. 2 | @prefix schema: . 3 | 4 | <> a :FiddleLibrary. 5 | [] 6 | a :Fiddle; 7 | :name "hello world"; 8 | :dataFormat "text/turtle"; 9 | :data """@prefix s: . <> s:name "hello world"."""; 10 | :query "PREFIX s: SELECT ?msg WHERE {?x s:name ?msg.}" 11 | . 12 | [] 13 | a :Fiddle; 14 | :name "show all examples"; 15 | :dataFormat "text/turtle"; 16 | :data "http://localhost/solid/sparql-fiddle/fiddles.ttl"; 17 | :query """ 18 | PREFIX : 19 | SELECT ?name ?format ?data ?query WHERE { 20 | ?x :name ?name; :dataFormat ?format; :data ?data; :query ?query . 21 | } 22 | """ 23 | . 24 | [] 25 | a :Fiddle; 26 | :name "show all triples"; 27 | :query "SELECT * WHERE { ?subject ?predicate ?object . }"; 28 | :dataFormat "text/turtle"; 29 | :data """ 30 | @prefix s: . 31 | <> a s:MusicPlaylist; 32 | s:name "Jazz"; 33 | s:track 34 | [s:name "A Love Supreme"; s:creator "John Coltrane"], 35 | [s:name "Take Five"; s:creator "Dave Brubeck"] 36 | . 37 | """ 38 | . 39 | [] 40 | a :Fiddle; 41 | :name "no ontology"; 42 | :dataFormat "text/turtle"; 43 | :data """ 44 | @prefix : <#>. 45 | <> :isA :Foo; :hasBar 46 | [:madeUpProperty "Look, Ma - no ontology!"] 47 | . 48 | """; 49 | :query """ 50 | # this prefix declaration is invalid SPARQL but 51 | # lets us refer to the in-memory RDF and work without 52 | # an ontology - bad practice in general but good for 53 | # rapid prototyping. 54 | 55 | PREFIX : <#> 56 | SELECT ?message WHERE { 57 | ?thing :isA :Foo; :hasBar ?x . 58 | ?x :madeUpProperty ?message . 59 | } 60 | """ 61 | . 62 | [] 63 | a :Fiddle; 64 | :name "find tracks in a playlist"; 65 | :dataFormat "text/turtle"; 66 | :data """ 67 | @prefix s: . 68 | <> a s:MusicPlaylist; 69 | s:name "Jazz"; 70 | s:track 71 | [s:name "A Love Supreme"; s:creator "John Coltrane"], 72 | [s:name "Take Five"; s:creator "Dave Brubeck"] 73 | . 74 | """; 75 | :query """ 76 | PREFIX s: 77 | SELECT ?artist ?trackName WHERE { 78 | ?x s:track ?y . 79 | ?y s:name ?trackName; s:creator ?artist . 80 | } 81 | """; 82 | . 83 | 84 | 85 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | SPARQL Fiddle 3 | 4 | 5 | 6 | 7 |
8 |
9 | RDF format : 10 | Turtle 11 | N3 12 | RDF/XML 13 |
14 |
15 | 16 | 17 | 24 | visit the code repo 25 |
26 |
27 |
28 |
29 | Endpoint : enter RDF text or 30 |
31 | 32 |
33 | 39 |

 40 | 
100 | 
149 | 
150 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # sparql-fiddle
  2 | **a JavaScript SPARQL API and online fiddle**
  3 | 
  4 | An online sparql-fiddle app allows you to load from URL, create, or cut and paste RDF and SPARQL into a form and then run the query, edit and re-run. It is one example of an app created with this library. Please take a look at it to see the general operation.   Below is an explanation of the library that can be used in other apps. These notes are preliminary. I would REALLY like to hear comments about the API and ontology I talk about below.
  5 | 
  6 | ## Overview
  7 | 
  8 | A fiddle, in the context of using sparql-fiddle as a coding library, is a data
  9 | structure which specifies an RDF data source, a SPARQL  query, and a 
 10 | results format.  The sparql-fiddle run() method runs the query against
 11 | the data source and displays or returns the results in the specified format.
 12 | The sparql-fiddle runFromLibrary() behaves similarly but retrieves the
 13 | fiddle from a Turtle file library of fiddles.
 14 | 
 15 | 
 16 | ## Data and Query Sources
 17 | 
 18 | The RDF data source may be a string containing Turtle or other rdflib
 19 | parseable serialization, or it may be a URL pointing to a resource containing 
 20 | the serialization.
 21 | 
 22 | Likewise, the SPARQL query may be specified as a string containing the 
 23 | query or a URL pointing to such a string.
 24 | 
 25 | ## Results as HTML or Text
 26 | 
 27 | The format for results is stored in the "wanted" key and may be one of "HTML",  "Text", "Array", "Hash", "Value"
 28 | 
 29 | An example:
 30 | 
 31 | ```javascript
 32 |       const sf = require('sparql-fiddle') // or browser equivalent
 33 |       let fiddle = {
 34 |           data  : "http://example.com/myRDF",
 35 |           query : "http://example.com/mySPARQL",
 36 |          wanted : "HTML"
 37 |        }
 38 |        sf.run(fiddle).then( results => {
 39 |            console.log(results)
 40 |        }, err => console.log(err) )
 41 |        // 
 42 |        // output : an HTML table containing the results of the query
 43 | ```       
 44 | The HTML format shows an HTML table of results.  Text shows fields
 45 | one per line with a space between records.  If neither of these
 46 | suits your purposes, you can ask for results as an Array or a Hash
 47 | and format or process them any way you'd like by iterating over the structure.
 48 | 
 49 | ## Results as an Array
 50 | 
 51 | The "Array" format returns an array of hashes (associative arrays). Given 
 52 | "SELECT ?name ?addOn ...", the results would be something like
 53 | 
 54 | ```javascript
 55 |       [ 
 56 |         {"name":"Alu Gobi","addOn":"chutney" },
 57 |         {"name":"Reuben Sandwich","addOn":"dill pickle" }
 58 |       ]
 59 | ```
 60 | ## Results as a Hash
 61 | 
 62 | The Hash format returns a hash of hashes (associateve arrays). You need to
 63 | specify a key before calling run().  If you specify fiddle.key="name" 
 64 | the results would be something like this:
 65 | 
 66 | ```javascript
 67 |       { 
 68 |          "Alu Gobi"        : {"name":"Alu Gobi","addOn":"chutney" },
 69 |          "Reuben Sandwich" : {"name":"Reuben Sandwich","addOn":"dill pickle" }
 70 |       }
 71 | ```      
 72 | The "Hash" format makes it very easy to read a Turtle config file into a
 73 | hash object in a script.
 74 | 
 75 | ## Retrieving a Single Value
 76 | 
 77 | The "Value" format returns a single value.  It is meant to work with a
 78 | query that returns a single field in a single row.  If more than one row
 79 | is retrieved, only the first will be examined.  If more than one field is
 80 | retrieved, an arbitrary key will be returned.   Here's an example:
 81 | 
 82 | ```javascript
 83 |       const sf = require('sparql-fiddle')
 84 |       let fiddle = {
 85 |             data:`@prefix : . <> :name "hello world".`,
 86 |             query:`PREFIX :  SELECT ?y WHERE {?x :name ?y .}`,
 87 |             wanted:'Value'
 88 |       }
 89 |       sf.run(fiddle).then( res =>{ console.log(res) }, err => console.log(err) )
 90 |       //
 91 |       //  output : hello world
 92 | ```
 93 | ## Default Results Format
 94 | 
 95 | If no "wanted" key is supplied, the results format will default to "HTML" 
 96 | in a browser context and "Text" in a node context.
 97 | 
 98 | ## Using N3 or RDF/XML 
 99 | 
100 | If the data source is not Turtle, the fiddle should specify the dataType
101 | as a mime content-type.
102 | ```javascript
103 |     fiddle.dataType = "application/rdf+xml"
104 | ```
105 | ## Row Handlers
106 | 
107 | If you wish to munge the data before the results are returned, 
108 | specify a rowHandler function which will be applied to each row
109 | during processing
110 | ```javascript
111 |     fiddle.rowHandler = function(row){
112 |         for(r in row){ row[r] = row[r].toUpperCase() }
113 |         return row
114 |     }
115 |     // values will be upper cased in results
116 | ```
117 | By default, run() accumulates all rows and then returns the accumulated
118 | results.  For very large results, this can eat up memory.  You can, instead,
119 | handle each row as it is processed without accumulating anything.  To do
120 | this, define a rowHandler which does what you need with each row but does
121 | not return anything.
122 | ```javascript
123 |     fiddle.rowHandler = function(row){
124 |         for(r in row){ console.log(r + " : " + row[r] + "\n" }
125 |     }
126 |     // displays rows one at a time, as they are processed, accumulates nothing
127 | ```
128 | ## Re-using a Store
129 | 
130 | If you want to run multiple queries against the same data, specify the data as
131 | usual in the first run and set data to be an empty string in the following
132 | runs.  In this case, the rdflib store object will be reused, thus avoiding
133 | unnessary fetching and parsing which was already accomplished in the first
134 | run.
135 | 
136 | ## An Ontology for shareable Fiddle Libraries
137 | 
138 | Although I have not yet finalized the ontology, sparql-fiddle includes
139 | a way to store and share fiddles between Solid sources. So let's say we
140 | have a file "myLibrary.ttl" like this (ontology expressly omitted until 
141 | I finalized it):
142 | ```turtle
143 |  @prefix : <#>
144 |  <> a :FiddleLibrary.
145 |  [] 
146 |   a :Fiddle;
147 |   :name "hello world";
148 |   :data """@prefix s: . <> s:name "hello Solid world".""";
149 |   :query "PREFIX s:  SELECT ?msg WHERE {?x s:name ?msg.}"
150 | .
151 | ```
152 | We can now do this:
153 | ```javascript
154 |     let fiddleLibrary = "http://example.org/myLibrary.ttl"
155 |     let options = {wanted:"Value"}
156 |     sf.runFiddle( fiddleLibrary, "hello world", options ).then( results => {
157 |         if( results === "hello Solid world" ) console.log("ok")
158 |     }, err => console.log(err) )
159 | ```
160 | The examples in the online app are all stored and retrieved from a
161 | fiddle library.  Once an ontology is finalized, anyone will be able
162 | to create and share fiddle libraries and the examples can grow with
163 | community contributions. And other uses for fiddle libraries will
164 | hopefully appear. They can be a kind of shared stored procedure library
165 | for the community.  To make these most useful, I propose some
166 | additional fields:
167 | ```turtle
168 |     :contributor   # name/email 
169 |     :level         # beginner,intermediate,advance
170 |     
171 |     ... others? advice sought!
172 | ```    
173 | ## Caveats & Plans
174 | 
175 |   * all SPARQL processing is based on rdflib.js which is not full SPARQL
176 | 
177 |   * I have not yet implemented a secondary fetcher.  This means that 
178 |     the data endpoint is only the original URL and its fragments, the
179 |     library will not (yet) go on to fetch additional URLs listed in the 
180 |     original URL
181 | 
182 | Enjoy!
183 | 
184 | copyright (c) Jeff Zucker, 2018, released under MIT open source license
185 | 
186 | 
187 | 
188 | 
189 | 
190 | 
191 | 
192 | 


--------------------------------------------------------------------------------
/sparql-fiddle.js:
--------------------------------------------------------------------------------
  1 | if (typeof(module)!="undefined" && typeof($rdf)==="undefined")  $rdf = require('rdflib')
  2 | 
  3 | let SparqlFiddle = function(){
  4 |     let self = this;
  5 |     this.setRdfType = function(type){ this.rdfType = type }
  6 | 
  7 |     this.do = function(fiddle){
  8 |       return new Promise((resolve, reject)=>{
  9 |         this.parseRdf(fiddle).then( response => {
 10 |             this.prepare(fiddle).then( preparedQuery => {
 11 |                 this.execute(fiddle,preparedQuery).then( results => {
 12 |                     self.store = fiddle.store 
 13 |                     if(fiddle.wanted==="Array"){
 14 |                         resolve(results)
 15 |                     }
 16 |                     else if(fiddle.wanted==="Hash") {
 17 |                         resolve( self.ary2hash(fiddle,results) )
 18 |                     }
 19 |                     else if(results.length < 1) {
 20 |                         resolve( "No results!"  )
 21 |                     }
 22 |                     else if(fiddle.wanted==="Value") {
 23 |                         let key = ( Object.keys(results[0])[0]  )
 24 |                         resolve( results[0][key] )
 25 |                     }
 26 |                     else { 
 27 |                         let formatted = self.displayHandler(fiddle,results)
 28 |                         resolve(formatted)
 29 |                     }  
 30 |                 },err=>reject(err))
 31 |             },err=>reject(err))
 32 |         },err=>reject(err))
 33 |       })
 34 |     }
 35 |     this.ary2hash = function(fiddle,ary){
 36 |         let hash = {}
 37 |         for(a in ary){
 38 |             a = ary[a]
 39 |             hash[a[fiddle.key]] = a
 40 |         }
 41 |         return hash
 42 |     }
 43 |     this.parseRdf = function(fiddle){ return new Promise((resolve, reject)=>{
 44 |         if(!fiddle.data){
 45 |               return resolve();
 46 |         }
 47 |         let type = fiddle.dataType
 48 |         if(typeof(document)!="undefined") type = self.rdfType
 49 |         if(!type)  type = "text/turtle"
 50 |         let endpointUrl = "http://example.org/inMemory"
 51 |         try {
 52 |             $rdf.parse(
 53 |                 fiddle.data, fiddle.store, $rdf.sym(endpointUrl).uri, type
 54 |             )
 55 |             resolve()
 56 |         }
 57 |         catch(err) { reject(err) }
 58 |     })}
 59 |     this.prepare = function(fiddle){
 60 |         return new Promise((resolve, reject)=>{
 61 |             try {
 62 |               let query = $rdf.SPARQLToQuery(fiddle.query,false,fiddle.store)
 63 |               resolve(query)
 64 |             }
 65 |             catch(err) { reject(err) }
 66 |         })
 67 |     }
 68 |     this.execute =  function(fiddle,preparedQuery){
 69 |         let rowHandler = fiddle.rowHandler || self.rowHandler
 70 |         return new Promise((resolve, reject)=>{
 71 |             let wanted = preparedQuery.vars
 72 |             let resultAry = []
 73 |             fiddle.store.query(preparedQuery, results =>  {
 74 |                 if(typeof(results)==="undefined") { reject("No results.") }
 75 |                 else { 
 76 |                     let row = rowHandler(fiddle,wanted,results) 
 77 |                     if(row) resultAry.push(row)
 78 |                 }
 79 |             }, {} , function(){resolve(resultAry)} )
 80 |         })
 81 |     }
 82 |     this.rowHandler = function(fiddle,wanted,results){
 83 |         row = {}
 84 |         for(r in results){
 85 |             let found = false
 86 |             let got = r.replace(/^\?/,'')
 87 |             if(wanted.length){
 88 |                 for(w in wanted){
 89 |                     if(got===wanted[w].label){ found=true; continue }
 90 |                 }
 91 |                 if(!found) continue
 92 |             } 
 93 |             row[got]=results[r].value
 94 |         }
 95 |         if(fiddle.rowHandler){
 96 |             row = fiddle.rowHandler(row)
 97 |         }
 98 |         return(row)
 99 |     }
100 | /*
101 |   DATA DISPLAY
102 | */
103 |     this.displayHandler = function(fiddle,results){
104 |         let type = (fiddle.wanted)
105 |                  ? fiddle.wanted
106 |                  : (typeof(document)===undefined)
107 |                    ? "Text"
108 |                    : "HTML"
109 |         if(type==="Text") return self.showText(results)
110 |         if(type==="HTML") return self.showHtml(results)
111 |     }
112 |     this.showText = function(results){
113 |         let columnHeads = Object.keys(results[0]).reverse()
114 |         let str = "\n"
115 |         for(r in results){
116 |             let row = ""
117 |             for(k in columnHeads){
118 |                 str += `${columnHeads[k]} : ${results[r][columnHeads[k]]}\n`
119 |             }
120 |             str += "\n"
121 |         }
122 |         return(str)
123 |     }
124 |     /* TBD : refactor to build a DOM object rather than a string
125 |     */
126 |     this.showHtml = function(results){
127 |         let columnHeads = Object.keys(results[0]).reverse()
128 |         let table = ""
129 |         let topRow = ""
130 |         for(c in columnHeads){
131 |             topRow += ``
132 |         }
133 |         table += `${topRow}`
134 |         for(r in results){
135 |             let row = ""
136 |             for(k in columnHeads){
137 |                 row += ``
138 |             }
139 |             table += `${row}`
140 |         }
141 |         table += "
${columnHeads[c]}
${results[r][columnHeads[k]]}
" 142 | return(table) 143 | } 144 | /* 145 | USER FUNCTIONS 146 | */ 147 | /* 148 | PREFIX : 149 | SELECT ?name ?format ?data ?query WHERE { 150 | ?x :name ?name; :dataFormat ?format; :data ?data; :query ?query . 151 | } 152 | */ 153 | this.runFromLibrary = function( fiddleLibrary, fiddleName, options){ 154 | return new Promise((resolve, reject)=>{ 155 | let fiddle = { 156 | wanted : "Array", 157 | data : fiddleLibrary, 158 | query :` 159 | PREFIX : 160 | SELECT ?type ?data ?query WHERE { 161 | ?x :name "${fiddleName}"; :dataFormat ?type; :data ?data; :query ?query . 162 | } 163 | `, 164 | } 165 | self.run( fiddle ).then( fiddle => { 166 | let newFiddle = { 167 | wanted : options.wanted, 168 | data : fiddle[0].data, 169 | query : fiddle[0].query, 170 | dataType : fiddle[0].type 171 | } 172 | self.run( newFiddle ).then( results => { 173 | resolve(results) 174 | }, err => reject(err) ) 175 | }, err => reject(err) ) 176 | }, err => reject(err) ) 177 | } 178 | this.loadLibrary = function( fiddleLibrary){ 179 | return new Promise((resolve, reject)=>{ 180 | let fiddle = { 181 | wanted : "Hash", 182 | key : "name", 183 | data : fiddleLibrary, 184 | query :` 185 | PREFIX : 186 | SELECT ?name ?type ?data ?query WHERE { 187 | ?x :name ?name :dataFormat ?type; :data ?data; :query ?query . 188 | } 189 | `, 190 | } 191 | self.run( fiddle ).then( results => { 192 | resolve(results) 193 | }, err => reject(err) ) 194 | }, err => reject(err) ) 195 | } 196 | this.run = function(fiddle) { 197 | return new Promise((resolve, reject)=>{ 198 | fiddle.store = (fiddle.data.length>0) 199 | ? $rdf.graph() 200 | : self.store 201 | if( fiddle.data.match(/^http/ ) ){ 202 | self.loadFromUrl(fiddle,"data").then( fiddle => { 203 | self.loadSparqlAndDo( fiddle ).then( results => { 204 | resolve(results) 205 | }, err => reject(err) ) 206 | }, err => reject(err) ) 207 | } 208 | else { 209 | self.loadSparqlAndDo( fiddle ).then( results => { 210 | resolve(results) 211 | }, err => reject(err) ) 212 | } 213 | }) 214 | } 215 | /* 216 | DATA LOADING 217 | */ 218 | this.loadSparqlAndDo = function( fiddle ) { 219 | return new Promise((resolve, reject)=>{ 220 | if( fiddle.query.match( /^http/ ) ){ 221 | this.loadFromUrl(fiddle,"query").then( fiddle => { 222 | self.do(fiddle).then( results => { 223 | resolve(results) 224 | }, err => reject(err) ) 225 | }, err => reject(err) ) 226 | } 227 | else { 228 | this.do(fiddle).then( results => { 229 | resolve(results) 230 | }, err => reject(err) ) 231 | } 232 | }) 233 | } 234 | this.loadFromUrl = function(fiddle,type){ 235 | let url = fiddle[type] 236 | return new Promise((resolve, reject)=>{ 237 | let fetcher = new $rdf.fetcher( $rdf.graph() ); 238 | try { 239 | fetcher.load(url).then( response => { 240 | // replace the url with it's content 241 | fiddle[type] = response.responseText 242 | resolve( fiddle ) 243 | }) 244 | } catch(err) { reject(err) } 245 | }) 246 | } 247 | this.loadFromUrlPlain = function(url){ 248 | return new Promise((resolve, reject)=>{ 249 | let fetcher = new $rdf.fetcher( $rdf.graph() ); 250 | try { 251 | fetcher.load(url).then( response => { 252 | resolve( response.responseText ) 253 | }) 254 | } catch(err) { reject(err) } 255 | }) 256 | } 257 | return this; 258 | } 259 | if (typeof(module)!="undefined" ) module.exports = SparqlFiddle() 260 | --------------------------------------------------------------------------------