@igea/oac_backend 1.0.34 → 1.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igea/oac_backend",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
4
4
  "description": "Backend service for the OAC project",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "scripts": {
10
10
  "start": "cross-env NODE_ENV=production node src/index.js",
11
11
  "dev": "cross-env NODE_ENV=development nodemon src/index.js",
12
+ "rdf2shacl": "cross-env NODE_ENV=development node src/tools/rdfToShacl.js",
12
13
  "test": "mocha \"test/**/*.js\""
13
14
  },
14
15
  "repository": {
@@ -27,11 +28,12 @@
27
28
  "cookie-parser": "1.4.7",
28
29
  "crypto": "1.0.1",
29
30
  "express": "5.1.0",
30
- "express-rate-limit": "^8.1.0",
31
+ "express-rate-limit": "8.1.0",
31
32
  "get-port": "7.1.0",
32
33
  "knex": "3.1.0",
33
34
  "libxmljs2": "0.37.0",
34
35
  "multer": "2.0.2",
36
+ "n3": "1.26.0",
35
37
  "nodemailer": "7.0.6",
36
38
  "pg": "8.16.3",
37
39
  "strip-bom": "5.0.0"
@@ -0,0 +1,43 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const path = require('path');
4
+ const ONTO_FOLDER = path.join(__dirname, '..', 'ontology'); ;
5
+
6
+ router.get('/schema/:format', (req, res) => {
7
+ console.log(`Requesting SHACL schema in format: ${req.params.format}`);
8
+ let format = req.params.format || 'ttl';
9
+ let filePath = null;
10
+ switch(format){
11
+ case 'ttl':
12
+ filePath = 'config.shacl.ttl'; //'schema_v1.shacl.ttl';
13
+ res.setHeader('Content-Type', 'text/turtle');
14
+ break;
15
+ case 'jsonld':
16
+ filePath = 'schema_v1.shacl.jsonld';
17
+ res.setHeader('Content-Type', 'application/ld+json');
18
+ break;
19
+ case 'xml':
20
+ filePath = 'schema_v1.shacl.rdf';
21
+ res.setHeader('Content-Type', 'application/rdf+xml');
22
+ break;
23
+ default:
24
+ res.status(400).json({
25
+ success: false,
26
+ data: null,
27
+ message: `Unsupported format: ${format}`
28
+ });
29
+ return;
30
+ }
31
+ res.sendFile(path.join(ONTO_FOLDER, filePath), (err) => {
32
+ if (err) {
33
+ res.status(500).json({
34
+ success: false,
35
+ data: null,
36
+ message: `Error sending file: ${err}`
37
+ });
38
+ }
39
+ });
40
+ });
41
+
42
+
43
+ module.exports = router
package/src/index.js CHANGED
@@ -57,6 +57,9 @@ getPort.default({
57
57
  const fusekiRouter = require('./controllers/fuseki.js')
58
58
  app.use(`/${serviceName}/fuseki`, fusekiRouter)
59
59
 
60
+ const ontologyRouter = require('./controllers/ontology.js')
61
+ app.use(`/${serviceName}/ontology`, ontologyRouter)
62
+
60
63
  app.listen(port, async () => {
61
64
  console.log(`${serviceName} listening on port ${port}`)
62
65
  await register()
@@ -0,0 +1,173 @@
1
+ const fs = require('fs');
2
+ const { Parser } = require('n3');
3
+ const { DataFactory, Store } = require('n3');
4
+
5
+ class Converter {
6
+
7
+ // Funzione per generare messaggi di errore multilingua
8
+ static generateErrorMessages(propertyName, datatype, isRelation) {
9
+ const messages = {
10
+ string: {
11
+ it: `Il campo ${propertyName} deve essere una stringa e non può essere vuoto.`,
12
+ en: `The field ${propertyName} must be a string and cannot be empty.`
13
+ },
14
+ integer: {
15
+ it: `Il campo ${propertyName} deve essere un numero intero.`,
16
+ en: `The field ${propertyName} must be an integer.`
17
+ },
18
+ relation: {
19
+ it: `Il campo ${propertyName} deve essere un'istanza valida.`,
20
+ en: `The field ${propertyName} must be a valid instance.`
21
+ }
22
+ };
23
+
24
+ if (isRelation) {
25
+ return messages.relation;
26
+ } else if (datatype.includes('integer')) {
27
+ return messages.integer;
28
+ } else {
29
+ return messages.string;
30
+ }
31
+ }
32
+
33
+ // Funzione per generare lo schema SHACL da un file RDF
34
+ static async generateShaclFromRdf(rdfFilePath, shaclFilePath) {
35
+ // Leggi il file RDF
36
+ const rdfData = fs.readFileSync(rdfFilePath, 'utf8');
37
+
38
+ // Crea un parser N3 e un store RDF
39
+ const parser = new Parser();
40
+ const quads = parser.parse(rdfData);
41
+ const store = new Store(quads);
42
+
43
+ // Estrai le classi, le proprietà e i tipi di dato
44
+ const classes = new Set();
45
+ const properties = new Map(); // Mappa: URI proprietà → { datatypes: Set, targetClasses: Set }
46
+ const classInstances = new Map(); // Mappa: URI classe → Set di istanze
47
+
48
+ // Analizza i tripli per trovare classi, proprietà e relazioni
49
+ for (const quad of store.getQuads(null, null, null, null)) {
50
+ const { subject, predicate, object } = quad;
51
+
52
+ // Trova le classi
53
+ if (predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') {
54
+ classes.add(object.value);
55
+ if (!classInstances.has(object.value)) {
56
+ classInstances.set(object.value, new Set());
57
+ }
58
+ classInstances.get(object.value).add(subject.value);
59
+ }
60
+
61
+ // Trova proprietà e tipi di dato
62
+ if (object.termType === 'Literal') {
63
+ const propertyUri = predicate.value;
64
+ const datatype = object.datatype ? object.datatype.value : 'http://www.w3.org/2001/XMLSchema#string';
65
+
66
+ if (!properties.has(propertyUri)) {
67
+ properties.set(propertyUri, { datatypes: new Set(), targetClasses: new Set() });
68
+ }
69
+ properties.get(propertyUri).datatypes.add(datatype);
70
+ } else if (object.termType === 'NamedNode') {
71
+ const propertyUri = predicate.value;
72
+
73
+ if (!properties.has(propertyUri)) {
74
+ properties.set(propertyUri, { datatypes: new Set(), targetClasses: new Set() });
75
+ }
76
+
77
+ // Trova la classe dell'oggetto (se esiste)
78
+ for (const objQuad of store.getQuads(object, DataFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), null, null)) {
79
+ properties.get(propertyUri).targetClasses.add(objQuad.object.value);
80
+ }
81
+ }
82
+ }
83
+
84
+ // Genera lo schema SHACL
85
+ let shacl = `@prefix sh: <http://www.w3.org/ns/shacl#> .
86
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
87
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
88
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
89
+ @prefix ex: <http://example.org/ns#> .
90
+
91
+ `;
92
+
93
+ // Aggiungi una shape per ogni classe
94
+ classes.forEach((classUri) => {
95
+ const className = classUri.split('#')[1] || classUri.split('/').pop();
96
+ const shapeName = `ex:${className}Shape`;
97
+
98
+ shacl += `${shapeName}
99
+ a sh:NodeShape ;
100
+ sh:targetClass <${classUri}> ;
101
+ `;
102
+
103
+ // Aggiungi le proprietà per questa classe
104
+ properties.forEach((propertyInfo, propertyUri) => {
105
+ // Verifica se questa proprietà è usata da istanze di questa classe
106
+ let usedByClass = false;
107
+ const propertyName = propertyUri.split('#')[1] || propertyUri.split('/').pop();
108
+
109
+ // Controlla se almeno un'istanza di questa classe ha questa proprietà
110
+ const classInstancesSet = classInstances.get(classUri) || new Set();
111
+ for (const instance of classInstancesSet) {
112
+ for (const quad of store.getQuads(
113
+ DataFactory.namedNode(instance),
114
+ DataFactory.namedNode(propertyUri),
115
+ null,
116
+ null
117
+ )) {
118
+ usedByClass = true;
119
+ break;
120
+ }
121
+ }
122
+
123
+ if (usedByClass) {
124
+ shacl += ` sh:property [
125
+ sh:name "${propertyName}" ;
126
+ sh:description "${className}.${propertyName}" ;
127
+ sh:path <${propertyUri}> ;
128
+ `;
129
+
130
+ // Aggiungi vincoli per tipi di dato
131
+ if (propertyInfo.datatypes.size > 0) {
132
+ const datatype = Array.from(propertyInfo.datatypes)[0]; // Prendi il primo tipo di dato
133
+ shacl += ` sh:datatype <${datatype}> ;
134
+ `;
135
+ // Aggiungi messaggi di errore per il tipo di dato
136
+ const messages = Converter.generateErrorMessages(propertyName, datatype, false);
137
+ shacl += ` sh:message "${messages.it}"@it ;
138
+ sh:message "${messages.en}"@en ;
139
+ `;
140
+ }
141
+
142
+ // Aggiungi vincoli per classi target (relazioni)
143
+ if (propertyInfo.targetClasses.size > 0) {
144
+ const targetClasses = Array.from(propertyInfo.targetClasses).map(c => `<${c}>`).join(' ');
145
+ shacl += ` sh:class [ sh:in (${targetClasses}) ] ;
146
+ `;
147
+ // Aggiungi messaggi di errore per le relazioni
148
+ const messages = Converter.generateErrorMessages(propertyName, '', true);
149
+ shacl += ` sh:message "${messages.it}"@it ;
150
+ sh:message "${messages.en}"@en ;
151
+ `;
152
+ }
153
+
154
+ // Aggiungi vincoli di cardinalità
155
+ shacl += ` sh:minCount 1 ;
156
+ sh:maxCount 1 ;
157
+ ] ;
158
+ `;
159
+ }
160
+ });
161
+
162
+ shacl += ` .
163
+ `;
164
+ });
165
+
166
+ // Salva lo schema SHACL in un file
167
+ fs.writeFileSync(shaclFilePath, shacl);
168
+ console.log(`Schema SHACL generato e salvato in ${shaclFilePath}`);
169
+ }
170
+
171
+ }
172
+
173
+ module.exports = Converter
@@ -0,0 +1,251 @@
1
+ @prefix schema: <http://schema.org/> .
2
+ @prefix volipi: <http://data.sparna.fr/ontologies/volipi#> .
3
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
4
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
5
+ @prefix skosthes: <http://purl.org/iso25964/skos-thes#> .
6
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
7
+ @prefix geo: <http://www.opengis.net/ont/geosparql#> .
8
+ @prefix qb: <http://purl.org/linked-data/cube#> .
9
+ @prefix dct: <http://purl.org/dc/terms/> .
10
+ @prefix doap: <http://usefulinc.com/ns/doap#> .
11
+ @prefix sh: <http://www.w3.org/ns/shacl#> .
12
+ @prefix dcat: <http://www.w3.org/ns/dcat#> .
13
+ @prefix euvoc: <http://publications.europa.eu/ontology/euvoc#> .
14
+ @prefix prov: <http://www.w3.org/ns/prov#> .
15
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
16
+ @prefix adms: <http://www.w3.org/ns/adms#> .
17
+ @prefix org: <http://www.w3.org/ns/org#> .
18
+ @prefix xls2rdf: <https://xls2rdf.sparna.fr/vocabulary#> .
19
+ @prefix this: <https://data.example.com/ontologies/sparnatural-config/> .
20
+ @prefix dbpedia: <http://dbpedia.org/ontology/> .
21
+ @prefix odb: <http://example.com/ontology/odb#> .
22
+ @prefix core: <http://data.sparna.fr/ontologies/sparnatural-config-core#> .
23
+ @prefix datasources: <http://data.sparna.fr/ontologies/sparnatural-config-datasources#> .
24
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
25
+ @prefix dash: <http://datashapes.org/dash#> .
26
+ @prefix dc: <http://purl.org/dc/elements/1.1/> .
27
+ @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
28
+ @prefix skosxl: <http://www.w3.org/2008/05/skos-xl#> .
29
+
30
+ <https://data.mydomain.com/ontologies/sparnatural-config> a owl:Ontology .
31
+
32
+ this:Vehicle a sh:NodeShape;
33
+ sh:order "1"^^xsd:integer;
34
+ volipi:iconName "fa-solid fa-car";
35
+ sh:targetClass odb:Vehicle;
36
+ rdfs:label "Vehicle"@en, "Véhicule"@fr;
37
+ sh:description "A vehicle is a car model for a specific brand."@en, "Un véhicule est un modèle de voiture pour une marque spécifique."@fr;
38
+ sh:property this:Vehicle_VIN, this:Vehicle_hasManufacturer .
39
+
40
+ this:Museum a sh:NodeShape;
41
+ sh:order "1"^^xsd:integer;
42
+ volipi:iconName "fas fa-university";
43
+ sh:targetClass dbpedia:Museum;
44
+ rdfs:label "Museum"@en, "Musée"@fr;
45
+ sh:description "A DBPedia Museum"@en, "Un Musée DBPedia"@fr;
46
+ sh:property this:Museum_country .
47
+
48
+ this:Country a sh:NodeShape;
49
+ sh:order "2"^^xsd:integer;
50
+ volipi:iconName "fas fa-globe-africa";
51
+ sh:targetClass dbpedia:Country;
52
+ rdfs:label "Country"@en, "Pays"@fr;
53
+ sh:description "A DBPedia Country"@en, "Un Pays DBPedia"@fr;
54
+ sh:property this:Country_countryOf, this:country_label .
55
+
56
+ this:Vehicle_VIN sh:path odb:VIN;
57
+ sh:order "1";
58
+ sh:name "has VIN"@en, "a pour VIN"@fr;
59
+ sh:description "Specifies the Vehicle Identification Number (VIN) of the vehicle."@en,
60
+ "Spécifie le numéro d'identification du véhicule (VIN)."@fr;
61
+ sh:minCount "1"^^xsd:integer;
62
+ sh:maxCount "1"^^xsd:integer;
63
+ sh:nodeKind sh:Literal;
64
+ sh:datatype xsd:string;
65
+ dash:searchWidget core:AutocompleteProperty;
66
+ dash:propertyRole dash:LabelRole .
67
+
68
+ this:Vehicle_hasManufacturer sh:path odb:hasManufacturer;
69
+ sh:order "2";
70
+ sh:name "has manufacturer"@en, "a pour constructeur"@fr;
71
+ sh:description "Specifies the manufacturer of the vehicle."@en, "Spécifie le constructeur d'un véhicule."@fr;
72
+ sh:minCount "1"^^xsd:integer;
73
+ sh:maxCount "1"^^xsd:integer;
74
+ sh:nodeKind sh:IRI;
75
+ sh:class odb:Manufacturer;
76
+ dash:searchWidget core:ListProperty;
77
+ core:enableNegation "true"^^xsd:boolean .
78
+
79
+ this:Museum_country sh:path dbpedia:country;
80
+ sh:order "1";
81
+ sh:name "country"@en, "pays"@fr;
82
+ sh:description "Specifies the country where the museum is located."@en, "Spécifie le pays où se trouve le musée."@fr;
83
+ sh:minCount "1"^^xsd:integer;
84
+ sh:nodeKind sh:IRI;
85
+ sh:class dbpedia:Country;
86
+ dash:searchWidget core:ListProperty;
87
+ core:enableOptional "true"^^xsd:boolean;
88
+ core:enableNegation "true"^^xsd:boolean .
89
+
90
+ this:Country_countryOf sh:path dbpedia:countryOf;
91
+ sh:order "1";
92
+ sh:name "country of"@en, "pays de"@fr;
93
+ sh:description "Specifies the museums located in this country."@en, "Spécifie les musées situés dans ce pays."@fr;
94
+ sh:minCount "1"^^xsd:integer;
95
+ sh:nodeKind sh:IRI;
96
+ sh:class dbpedia:Museum;
97
+ dash:searchWidget core:AutocompleteProperty;
98
+ core:enableOptional "true"^^xsd:boolean;
99
+ core:enableNegation "true"^^xsd:boolean .
100
+
101
+ this:country_label sh:path rdfs:label;
102
+ sh:name "label"@en, "libellé"@fr;
103
+ sh:minCount "1"^^xsd:integer;
104
+ sh:nodeKind sh:Literal;
105
+ sh:datatype rdf:langString;
106
+ dash:propertyRole dash:LabelRole .
107
+
108
+ this:search_VIN_strstarts a datasources:SparqlDatasource;
109
+ datasources:queryTemplate datasources:query_search_label_strstarts;
110
+ datasources:labelProperty odb:VIN .
111
+
112
+ this:list_componentCode_alpha a datasources:SparqlDatasource;
113
+ datasources:queryString """PREFIX odb: <http://example.com/ontology/odb#>
114
+ SELECT DISTINCT ?uri ?label
115
+ WHERE {
116
+ ?domain $type $domain .
117
+ ?domain $property ?uri .
118
+ # Note how the range criteria is not used in this query
119
+ FILTER(isIRI(?uri))
120
+ ?uri rdfs:label ?libelleComposant .
121
+ FILTER(lang(?libelleComposant) = \"\" || lang(?libelleComposant) = $lang)
122
+ ?uri odb:componentCode ?codeComposant .
123
+ # Concat component code + component label
124
+ BIND(CONCAT(STR(?codeComposant),\" - \",STR(?libelleComposant)) AS ?label)
125
+ }
126
+ ORDER BY UCASE(?label)
127
+ LIMIT 500""" .
128
+
129
+ this:tree_root_Component a datasources:SparqlDatasource;
130
+ datasources:queryString """PREFIX odb: <http://example.com/ontology/odb#>
131
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#label>
132
+ SELECT ?uri ?label ?hasChildren (COUNT(?x) AS ?count) WHERE {
133
+ ?uri a odb:Component .
134
+ # Keep only roots, that do not have any parent
135
+ FILTER NOT EXISTS {
136
+ ?uri odb:parentComponent ?parent .
137
+ }
138
+ ?uri rdfs:label ?libelleComposant .
139
+ FILTER(lang(?libelleComposant) = \"\" || lang(?libelleComposant) = $lang)
140
+ ?uri odb:componentCode ?codeComposant .
141
+ # Concat component code + component label
142
+ BIND(CONCAT(STR(?codeComposant),\" - \",STR(?libelleComposant)) AS ?label)
143
+ OPTIONAL { ?uri ^odb:parentComponent ?children }
144
+ BIND(IF(bound(?children),true,false) AS ?hasChildren)
145
+ OPTIONAL {
146
+ ?x a $domain .
147
+ ?x $property ?uri .
148
+ }
149
+ }
150
+ GROUP BY ?uri ?label ?hasChildren
151
+ ORDER BY ?label""" .
152
+
153
+ this:tree_children_Component a datasources:SparqlDatasource;
154
+ datasources:queryString """PREFIX odb: <http://example.com/ontology/odb#>
155
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#label>
156
+ SELECT DISTINCT ?uri ?label ?hasChildren (COUNT(?x) AS ?count) WHERE {
157
+
158
+ $node ^odb:parentComponent ?uri .
159
+
160
+ ?uri rdfs:label ?libelleComposant .
161
+ FILTER(lang(?libelleComposant) = \"\" || lang(?libelleComposant) = $lang)
162
+ ?uri odb:componentCode ?codeComposant .
163
+ # Concat component code + component label
164
+ BIND(CONCAT(STR(?codeComposant),\" - \",STR(?libelleComposant)) AS ?label)
165
+
166
+ OPTIONAL { ?uri ^odb:parentComponent ?children }
167
+ BIND(IF(bound(?children),true,false) AS ?hasChildren)
168
+
169
+ OPTIONAL {
170
+ ?x a $domain .
171
+ ?x $property ?uri .
172
+ }
173
+ }
174
+ GROUP BY ?uri ?label ?hasChildren
175
+ ORDER BY ?label""" .
176
+
177
+ this:query_list_label_alpha_with_count_langfr a datasources:SPARQLQuery;
178
+ datasources:queryString """SELECT DISTINCT ?uri ?count (CONCAT(STR(?theLabel), ' (', STR(?count), ')') AS ?label)
179
+ WHERE {
180
+ {
181
+ SELECT DISTINCT ?uri (COUNT(?domain) AS ?count)
182
+ WHERE {
183
+ ?domain a $domain .
184
+ ?domain $property ?uri .
185
+ FILTER(isIRI(?uri))
186
+ # Note how the range criteria is not used in this query
187
+ }
188
+ GROUP BY ?uri
189
+ }
190
+ OPTIONAL { ?uri $labelPath ?theLabelLang . FILTER(lang(?theLabelLang) = $lang) }
191
+ OPTIONAL { ?uri $labelPath ?theLabelNone . FILTER(lang(?theLabelNone) = \"\") }
192
+ OPTIONAL { ?uri $labelPath ?theLabelFr . FILTER(lang(?theLabelFr) = \"fr\") }
193
+ BIND(COALESCE(?theLabelLang, ?theLabelNone, ?theLabelFr, STR(?uri)) AS ?theLabel)
194
+ }
195
+ ORDER BY UCASE(?label)
196
+ LIMIT 500""";
197
+ rdfs:comment "A query that will list entries alphabetically with number of occurrences in parenthesis by fetching first in the user language but will default to French"@en .
198
+
199
+ this:query_list_label_count_langfr a datasources:SPARQLQuery;
200
+ datasources:queryString """SELECT ?uri ?count (CONCAT(STR(?theLabel), ' (', STR(?count), ')') AS ?label)
201
+ WHERE {
202
+ {
203
+ SELECT DISTINCT ?uri (COUNT(?domain) AS ?count)
204
+ WHERE {
205
+ ?domain a $domain .
206
+ ?domain $property ?uri .
207
+ FILTER(isIRI(?uri))
208
+ # Note how the range criteria is not used in this query
209
+ }
210
+ GROUP BY ?uri
211
+ }
212
+ OPTIONAL { ?uri $labelPath ?theLabelLang . FILTER(lang(?theLabelLang) = $lang) }
213
+ OPTIONAL { ?uri $labelPath ?theLabelNone . FILTER(lang(?theLabelNone) = \"\") }
214
+ OPTIONAL { ?uri $labelPath ?theLabelFr . FILTER(lang(?theLabelFr) = \"fr\") }
215
+ BIND(COALESCE(?theLabelLang, ?theLabelNone, ?theLabelFr) AS ?theLabel)
216
+ }
217
+ ORDER BY DESC(?count) UCASE(?label)
218
+ LIMIT 500""";
219
+ rdfs:comment "A query that will list entries by descending number of occurrences by fetching first in the user langauge but will default to French"@en .
220
+
221
+ this:query_list_label_alpha_langfr a datasources:SPARQLQuery;
222
+ datasources:queryString """SELECT DISTINCT ?uri ?label
223
+ WHERE {
224
+ ?domain a $domain .
225
+ ?domain $property ?uri .
226
+ # Note how the range criteria is not used in this query
227
+ FILTER(isIRI(?uri))
228
+
229
+ OPTIONAL { ?uri $labelPath ?theLabelLang . FILTER(lang(?theLabelLang) = $lang) }
230
+ OPTIONAL { ?uri $labelPath ?theLabelNone . FILTER(lang(?theLabelNone) = \"\") }
231
+ OPTIONAL { ?uri $labelPath ?theLabelFr . FILTER(lang(?theLabelFr) = \"fr\") }
232
+ BIND(COALESCE(?theLabelLang, ?theLabelNone, ?theLabelFr) AS ?label)
233
+ }
234
+ ORDER BY UCASE(?label)
235
+ LIMIT 500""";
236
+ rdfs:comment "A query that will list entries alphabetically by fetching first in the user language but will default to French"@en .
237
+
238
+ this:query_search_label_contains_langfr a datasources:SPARQLQuery;
239
+ datasources:queryString """SELECT DISTINCT ?uri ?label
240
+ WHERE {
241
+ ?domain a $domain .
242
+ ?domain $property ?uri .
243
+ ?uri a $range .
244
+ ?uri $labelPath ?label .
245
+ FILTER(isIRI(?uri))
246
+ FILTER(CONTAINS(LCASE(STR(?label)), LCASE(\"$key\")))
247
+ FILTER(lang(?label) = '' || lang(?label) = \"fr\")
248
+ }
249
+ ORDER BY UCASE(?label)
250
+ LIMIT 50""";
251
+ rdfs:comment "A query that will search in labels using contains function, first in the user language but will default to French."@en .