@@ -3,6 +3,9 @@ import hljs from "highlight.js/lib/core";
33
44import { hljsDefineTurtle , hljsDefineSparql } from "./highlight-sparql" ;
55import { editorCss , yasguiCss , yasguiGripInlineCss , highlightjsCss } from "./styles" ;
6+ // import { drawSvgStringAsElement } from "./utils";
7+ // import tooltip from "@zazuko/yasqe/src/tooltip";
8+ // import {warning} from "@zazuko/yasqe/src/imgs";
69// import {Parser} from "sparqljs";
710
811type ExampleQuery = {
@@ -18,12 +21,15 @@ interface SparqlResultBindings {
1821}
1922
2023interface VoidDict {
24+ // [key: string]: {
2125 [ key : string ] : {
2226 [ key : string ] : string [ ] ;
2327 } ;
28+ // }
2429}
2530
2631const addSlashAtEnd = ( str : any ) => ( str . endsWith ( "/" ) ? str : `${ str } /` ) ;
32+ const capitalize = ( str : any ) => str . charAt ( 0 ) . toUpperCase ( ) + str . slice ( 1 ) . toLowerCase ( ) ;
2733
2834/**
2935 * Custom element to create a SPARQL editor for a given endpoint using YASGUI
@@ -151,6 +157,7 @@ export class SparqlEditor extends HTMLElement {
151157 this . yasgui ?. getTab ( ) ?. getYasqe ( ) . collapsePrefixes ( true ) ;
152158 } ) ;
153159
160+ // TODO: Detect errors in the query and show them in the editor
154161 // const parser = new Parser();
155162 // // @ts -ignore TS complains for nothing about args of the event, but the tab is properly passed
156163 // this.yasgui.getTab()?.getYasqe().on("change", (tab: any) => {
@@ -180,6 +187,24 @@ export class SparqlEditor extends HTMLElement {
180187 const ye = tab . getYasqe ( ) ;
181188 tab . getYasr ( ) . config . prefixes = { ...Yasgui . Yasr . defaults . prefixes , ...ye . getPrefixesFromQuery ( ) } ;
182189
190+ // for (let l = 0; l < ye.getDoc().lineCount(); ++l) {
191+ // const token = ye.getTokenAt(
192+ // {
193+ // line: l,
194+ // ch: ye.getDoc().getLine(l).length,
195+ // },
196+ // true
197+ // );
198+ // console.log(l, token);
199+ // }
200+
201+ // // TODO: here is how we can show an error at a specific line
202+ // const warningEl = drawSvgStringAsElement(warning);
203+ // warningEl.className = "parseErrorIcon";
204+ // // @ts -ignore TS is not smart enough to understand that Yasqe and Yasqe are same type...
205+ // tooltip(ye, warningEl, "Big error!");
206+ // ye.setGutterMarker(13, "gutterErrorBar", warningEl);
207+
183208 // // Add limit to query if not provided
184209 // const limitPattern = /LIMIT\s+\d+\s*$/i;
185210 // const trimmedQuery = ye.getValue().trim();
@@ -211,8 +236,8 @@ export class SparqlEditor extends HTMLElement {
211236 } ) ;
212237
213238 // Button to pop a dialog to save the query as an example in a turtle file
239+ const exampleNumberForId = ( this . exampleQueries . length + 1 ) . toString ( ) . padStart ( 3 , "0" ) ;
214240 const addExampleBtnEl = this . shadowRoot ?. getElementById ( "sparql-save-example-btn" ) ;
215- const capitalize = ( str : any ) => str . charAt ( 0 ) . toUpperCase ( ) + str . slice ( 1 ) . toLowerCase ( ) ;
216241 addExampleBtnEl ?. addEventListener ( "click" , ( ) => {
217242 const dialog = document . createElement ( "dialog" ) ;
218243 dialog . style . width = "400px" ;
@@ -231,6 +256,10 @@ export class SparqlEditor extends HTMLElement {
231256 <p>Download the current query as an example in a turtle file that you can then submit to the ${ exampleRepoLink } where all examples are stored.</p>
232257 <label for="description">Description:</label><br>
233258 <input type="text" id="description" name="description" required style="width: 100%;" maxlength="200"><br><br>
259+ <label for="query-uri">Query example filename/URI (no spaces):</label><br>
260+ <input type="text" id="example-uri" name="example-uri" required pattern="^[a-zA-Z0-9_-]+$"
261+ title="Only alphanumeric characters, underscores, or hyphens are allowed."
262+ style="width: 100%;" placeholder="Enter a valid filename" value="${ exampleNumberForId } "><br><br>
234263 <label for="keywords">Keywords (optional, comma separated):</label><br>
235264 <input type="text" id="keywords" name="keywords" style="width: 100%;"><br><br>
236265 <button type="submit" class="btn">Download example file</button>
@@ -243,51 +272,52 @@ export class SparqlEditor extends HTMLElement {
243272 const descriptionInput = dialog . querySelector ( "#description" ) as HTMLInputElement ;
244273 descriptionInput . focus ( ) ;
245274
246- const exampleNumberForId = ( this . exampleQueries . length + 1 ) . toString ( ) . padStart ( 3 , "0" ) ;
247-
248275 const generateShacl = ( ) => {
249276 const description = ( dialog . querySelector ( "#description" ) as HTMLTextAreaElement ) . value ;
250277 const keywordsStr = ( dialog . querySelector ( "#keywords" ) as HTMLInputElement ) . value
251278 . split ( "," )
252279 . map ( ( kw : string ) => `"${ kw . trim ( ) } "` )
253280 . join ( ", " ) ;
254281 const queryType = capitalize ( this . yasgui ?. getTab ( ) ?. getYasqe ( ) . getQueryType ( ) ) ;
255- const exampleNumberForId = ( this . exampleQueries . length + 1 ) . toString ( ) . padStart ( 3 , "0" ) ;
256282 const keywordsBit = keywordsStr . length > 2 ? `schema:keyword ${ keywordsStr } ;\n ` : "" ;
257- return `@prefix ex: <${ this . examplesNamespace } > .
283+ const exampleUri = ( dialog . querySelector ( "#example-uri" ) as HTMLInputElement ) . value ;
284+ return [
285+ `@prefix ex: <${ this . examplesNamespace } > .
258286@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
259287@prefix schema: <https://schema.org/> .
260288@prefix sh: <http://www.w3.org/ns/shacl#> .
261289
262- ex:${ exampleNumberForId } a sh:SPARQLExecutable${
263- [ "Select" , "Construct" , "Ask" ] . includes ( queryType )
264- ? `,
290+ ex:${ exampleUri } a sh:SPARQLExecutable${
291+ [ "Select" , "Construct" , "Ask" ] . includes ( queryType )
292+ ? `,
265293 sh:SPARQL${ queryType } Executable`
266- : ""
267- } ;
294+ : ""
295+ } ;
268296 rdfs:comment "${ description } "@en ;
269297 sh:prefixes _:sparql_examples_prefixes ;
270298 sh:${ queryType . toLowerCase ( ) } """${ this . yasgui ?. getTab ( ) ?. getYasqe ( ) . getValue ( ) } """ ;
271- ${ keywordsBit } schema:target <${ this . endpointUrl } > .` ;
299+ ${ keywordsBit } schema:target <${ this . endpointUrl } > .` ,
300+ exampleUri ,
301+ ] ;
272302 } ;
273303
274304 const formEl = dialog . querySelector ( "#example-form" ) as HTMLFormElement ;
275305 formEl . addEventListener ( "submit" , e => {
276306 e . preventDefault ( ) ;
277- const shaclStr = generateShacl ( ) ;
307+ const [ shaclStr , exampleUri ] = generateShacl ( ) ;
278308 const dataStr = `data:text/turtle;charset=utf-8,${ encodeURIComponent ( shaclStr ) } ` ;
279309 const downloadAnchor = document . createElement ( "a" ) ;
280310 downloadAnchor . setAttribute ( "href" , dataStr ) ;
281- downloadAnchor . setAttribute ( "download" , `${ exampleNumberForId } .ttl` ) ;
311+ downloadAnchor . setAttribute ( "download" , `${ exampleUri } .ttl` ) ;
282312 downloadAnchor . click ( ) ;
283313 dialog . close ( ) ;
284314 } ) ;
285315
286316 if ( this . examplesRepoAddUrl ) {
287317 dialog . querySelector ( "#add-to-repo-btn" ) ?. addEventListener ( "click" , ( ) => {
288318 if ( formEl . checkValidity ( ) ) {
289- const shaclStr = generateShacl ( ) ;
290- const uploadExampleUrl = `${ this . examplesRepoAddUrl } ?filename=${ exampleNumberForId } .ttl&value=${ encodeURIComponent ( shaclStr ) } ` ;
319+ const [ shaclStr , exampleUri ] = generateShacl ( ) ;
320+ const uploadExampleUrl = `${ this . examplesRepoAddUrl } ?filename=${ exampleUri } .ttl&value=${ encodeURIComponent ( shaclStr ) } ` ;
291321 window . open ( uploadExampleUrl , "_blank" ) ;
292322 // navigator.clipboard.writeText(shaclStr).then(() => {
293323 // window.open(uploadExampleUrl, "_blank");
@@ -350,15 +380,18 @@ ex:${exampleNumberForId} a sh:SPARQLExecutable${
350380 get : async ( yasqe : any ) => {
351381 const cursor = yasqe . getCursor ( ) ;
352382 const subj = getSubjectForCursorPosition ( yasqe . getValue ( ) , cursor . line , cursor . ch ) ;
383+ // TODO: get the URL of the endpoint for SERVICE calls
353384 const subjTypes = extractAllSubjectsAndTypes ( yasqe . getValue ( ) ) ;
354385 // console.log("subj, subjTypes, unfiltered hints", subj, subjTypes, hints)
355- if ( subj && subjTypes . has ( subj ) && Object . keys ( this . voidDescription ) . length > 0 ) {
386+ if ( subj && subjTypes . has ( subj ) && Object . keys ( this . voidDescription [ this . endpointUrl ] ) . length > 0 ) {
356387 const types = subjTypes . get ( subj ) ;
357388 // console.log("types", types)
358389 if ( types ) {
359390 const suggestPreds = new Set < string > ( ) ;
360391 types . forEach ( typeCurie => {
361- Object . keys ( this . voidDescription [ this . curieToUri ( typeCurie ) ] ) . forEach ( prop => suggestPreds . add ( prop ) ) ;
392+ Object . keys ( this . voidDescription [ this . endpointUrl ] [ this . curieToUri ( typeCurie ) ] ) . forEach ( prop =>
393+ suggestPreds . add ( prop ) ,
394+ ) ;
362395 } ) ;
363396 // console.log("suggestPreds", suggestPreds)
364397 return Array . from ( suggestPreds ) . sort ( ) ;
@@ -395,32 +428,39 @@ ex:${exampleNumberForId} a sh:SPARQLExecutable${
395428 const queryResults = await this . queryEndpoint ( `PREFIX up: <http://purl.uniprot.org/core/>
396429 PREFIX void: <http://rdfs.org/ns/void#>
397430 PREFIX void-ext: <http://ldf.fi/void-ext#>
398- SELECT DISTINCT ?class1 ?prop ?class2 ?datatype
431+ SELECT DISTINCT ?subjectClass ?prop ?objectClass ?objectDatatype
399432 WHERE {
400- ?cp void:class ?class1 ;
433+ {
434+ ?cp void:class ?subjectClass ;
401435 void:propertyPartition ?pp .
402436 ?pp void:property ?prop .
403437 OPTIONAL {
404438 {
405- ?pp void:classPartition [ void:class ?class2 ] .
439+ ?pp void:classPartition [ void:class ?objectClass ] .
406440 } UNION {
407- ?pp void-ext:datatypePartition [ void-ext:datatype ?datatype ] .
441+ ?pp void-ext:datatypePartition [ void-ext:datatype ?objectDatatype ] .
408442 }
409443 }
444+ } UNION {
445+ ?ls void:subjectsTarget ?subjectClass ;
446+ void:linkPredicate ?prop ;
447+ void:objectsTarget ?objectClass .
448+ }
410449 }` ) ;
411450 const clsSet = new Set < string > ( ) ;
412451 const predSet = new Set < string > ( ) ;
413452 queryResults . forEach ( b => {
414- clsSet . add ( b . class1 . value ) ;
453+ clsSet . add ( b . subjectClass . value ) ;
415454 predSet . add ( b . prop . value ) ;
416- if ( ! ( b . class1 . value in this . voidDescription ) ) this . voidDescription [ b . class1 . value ] = { } ;
417- if ( ! ( b . prop . value in this . voidDescription [ b . class1 . value ] ) )
418- this . voidDescription [ b . class1 . value ] [ b . prop . value ] = [ ] ;
419- if ( "class2 " in b ) {
420- this . voidDescription [ b . class1 . value ] [ b . prop . value ] . push ( b . class2 . value ) ;
421- clsSet . add ( b . class2 . value ) ;
455+ if ( ! ( b . subjectClass . value in this . voidDescription ) ) this . voidDescription [ b . subjectClass . value ] = { } ;
456+ if ( ! ( b . prop . value in this . voidDescription [ b . subjectClass . value ] ) )
457+ this . voidDescription [ b . subjectClass . value ] [ b . prop . value ] = [ ] ;
458+ if ( "objectClass " in b ) {
459+ this . voidDescription [ b . subjectClass . value ] [ b . prop . value ] . push ( b . objectClass . value ) ;
460+ clsSet . add ( b . objectClass . value ) ;
422461 }
423- if ( "datatype" in b ) this . voidDescription [ b . class1 . value ] [ b . prop . value ] . push ( b . datatype . value ) ;
462+ if ( "objectDatatype" in b )
463+ this . voidDescription [ b . subjectClass . value ] [ b . prop . value ] . push ( b . objectDatatype . value ) ;
424464 } ) ;
425465 this . classesList = Array . from ( clsSet ) . sort ( ) ;
426466 this . predicatesList = Array . from ( predSet ) . sort ( ) ;
0 commit comments