Skip to content

Commit 3a54aea

Browse files
committed
improve get VoID description query to get Linksets. Add metanetx demo. Add the example filename as field when saving a query
1 parent 20b8c3d commit 3a54aea

File tree

5 files changed

+149
-38
lines changed

5 files changed

+149
-38
lines changed

README.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,21 @@ The editor retrieves metadata about the endpoint by directly querying the SPARQL
4848
PREFIX void-ext: <http://ldf.fi/void-ext#>
4949
SELECT DISTINCT ?subjectClass ?prop ?objectClass ?objectDatatype
5050
WHERE {
51-
?cp void:class ?subjectClass ;
52-
void:propertyPartition ?pp .
53-
?pp void:property ?prop .
54-
OPTIONAL {
55-
{
56-
?pp void:classPartition [ void:class ?objectClass ] .
57-
} UNION {
58-
?pp void-ext:datatypePartition [ void-ext:datatype ?objectDatatype ] .
51+
{
52+
?cp void:class ?subjectClass ;
53+
void:propertyPartition ?pp .
54+
?pp void:property ?prop .
55+
OPTIONAL {
56+
{
57+
?pp void:classPartition [ void:class ?objectClass ] .
58+
} UNION {
59+
?pp void-ext:datatypePartition [ void-ext:datatype ?objectDatatype ] .
60+
}
5961
}
62+
} UNION {
63+
?linkset void:subjectsTarget ?subjectClass ;
64+
void:linkPredicate ?prop ;
65+
void:objectsTarget ?objectClass .
6066
}
6167
}
6268
```

demo/check.html

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<!-- https://sparql.uniprot.org/sparql/ -->
1212
<!-- https://biosoda.unil.ch/graphdb/repositories/emi-dbgi -->
1313
<!-- https://rdf.metanetx.org/sparql/ -->
14+
<!-- CORS error: https://beta.metanetx.org/sparql/ -->
1415

1516
<body>
1617
<div id="container">
@@ -92,6 +93,7 @@ <h2>SPARQL Endpoint Metadata Check</h2>
9293
PREFIX void-ext: <http://ldf.fi/void-ext#>
9394
SELECT DISTINCT ?subjectClass
9495
WHERE {
96+
{
9597
?cp void:class ?subjectClass ;
9698
void:propertyPartition ?pp .
9799
?pp void:property ?prop .
@@ -102,6 +104,11 @@ <h2>SPARQL Endpoint Metadata Check</h2>
102104
?pp void-ext:datatypePartition [ void-ext:datatype ?objectDatatype ] .
103105
}
104106
}
107+
} UNION {
108+
?ls void:subjectsTarget ?subjectClass ;
109+
void:linkPredicate ?prop ;
110+
void:objectsTarget ?objectClass .
111+
}
105112
}`,
106113
);
107114

@@ -116,6 +123,7 @@ <h2>SPARQL Endpoint Metadata Check</h2>
116123
PREFIX void-ext: &lt;http://ldf.fi/void-ext#&gt;
117124
SELECT DISTINCT ?subjectClass ?prop ?objectClass ?objectDatatype
118125
WHERE {
126+
{
119127
?cp void:class ?subjectClass ;
120128
void:propertyPartition ?pp .
121129
?pp void:property ?prop .
@@ -126,14 +134,21 @@ <h2>SPARQL Endpoint Metadata Check</h2>
126134
?pp void-ext:datatypePartition [ void-ext:datatype ?objectDatatype ] .
127135
}
128136
}
137+
} UNION {
138+
?linkset void:subjectsTarget ?subjectClass ;
139+
void:linkPredicate ?prop ;
140+
void:objectsTarget ?objectClass .
141+
}
129142
}</code></pre>`;
130143
return;
131144
} else {
132145
resultsDiv.innerHTML += `<h4>✅ Found VoID description for ${voidResults.length} classes</h4>`;
133146
}
134147
} catch (error) {
135148
console.log("Error querying the endpoint", error);
136-
resultsDiv.innerHTML += `<h4>❌ Error querying the endpoint: ${error.message}</h4>`;
149+
resultsDiv.innerHTML += `<h4>❌ Error querying the endpoint: ${error.message}</h4>
150+
<p>It might be that CORS is not enabled for your endpoint, open the inspect panel and checkout the console to find out
151+
(CORS is poorly designed and the JavaScript is not able to catch the CORS error message even if it is send to the console).</p>`;
137152
return;
138153
}
139154
}

demo/index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ <h1>💫 SPARQL editors for SIB endpoints</h1>
2525
>
2626
</li>
2727
<li><a href="hamap">HAMAP - High-quality Automated and Manual Annotation of Proteins</a></li>
28+
<li>
29+
<a href="metanetx"
30+
>MetaNetX - Automated Model Construction and Genome Annotation for Large-Scale Metabolic Networks</a
31+
>
32+
</li>
2833
<li><a href="dbgi">DBGI - The Digital Botanical Garden Initiative</a></li>
2934
</ul>
3035
<p>

demo/metanetx.html

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>MetaNetX SPARQL editor</title>
7+
<meta name="description" content="SPARQL editor for the MetaNetX endpoint at the SIB" />
8+
<link rel="icon" type="image/png" href="sib-logo.png" />
9+
<link rel="stylesheet" href="styles.css" />
10+
11+
<script type="module" src="https://unpkg.com/@sib-swiss/sparql-editor"></script>
12+
</head>
13+
14+
<body>
15+
<div>
16+
<sparql-editor
17+
endpoint="https://rdf.metanetx.org/sparql/"
18+
examples-repo-add-url="https://github.com/sib-swiss/sparql-examples/new/master/examples/MetaNetX"
19+
>
20+
<h4>Other SPARQL editors for SIB endpoints</h4>
21+
<ul>
22+
<li><a href="/sparql-editor/uniprot">UniProt - protein knowledgebase</a></li>
23+
<li><a href="/sparql-editor/bgee">Bgee - gene expression</a></li>
24+
<li><a href="/sparql-editor/oma">OMA - Ortholog MAtrix</a></li>
25+
<li>
26+
<a href="/sparql-editor/swisslipids">SwissLipids - A knowledge resource for lipids and their biology</a>
27+
</li>
28+
<li>
29+
<a href="/sparql-editor/rhea"
30+
>Rhea - expert-curated knowledgebase of chemical and transport reactions of biological interest</a
31+
>
32+
</li>
33+
<li><a href="/sparql-editor/hamap">HAMAP - High-quality Automated and Manual Annotation of Proteins</a></li>
34+
<li>
35+
<a href="/sparql-editor/metanetx"
36+
>MetaNetX - Automated Model Construction and Genome Annotation for Large-Scale Metabolic Networks</a
37+
>
38+
</li>
39+
<li><a href="/sparql-editor/dbgi">DBGI - The Digital Botanical Garden Initiative</a></li>
40+
</ul>
41+
<p>You can check if an endpoint contains the <a href="/sparql-editor/check">necessary metadata here</a>.</p>
42+
</sparql-editor>
43+
</div>
44+
</body>
45+
</html>

src/sparql-editor.ts

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import hljs from "highlight.js/lib/core";
33

44
import {hljsDefineTurtle, hljsDefineSparql} from "./highlight-sparql";
55
import {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

811
type ExampleQuery = {
@@ -18,12 +21,15 @@ interface SparqlResultBindings {
1821
}
1922

2023
interface VoidDict {
24+
// [key: string]: {
2125
[key: string]: {
2226
[key: string]: string[];
2327
};
28+
// }
2429
}
2530

2631
const 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

Comments
 (0)