diff --git a/Dockerfile b/Dockerfile
index 0a548fec..90a2f907 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,10 +7,10 @@ RUN set -ex \
&& /env/bin/pip install --upgrade pip \
&& /env/bin/pip install --no-cache-dir -r /app/requirements.txt \
&& runDeps="$(scanelf --needed --nobanner --recursive /env \
- | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
- | sort -u \
- | xargs -r apk info --installed \
- | sort -u)" \
+ | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
+ | sort -u \
+ | xargs -r apk info --installed \
+ | sort -u)" \
&& apk add --virtual rundeps $runDeps \
&& apk add bash
diff --git a/backend/admin.py b/backend/admin.py
index 39f53568..b77c6c65 100644
--- a/backend/admin.py
+++ b/backend/admin.py
@@ -3,11 +3,12 @@
from django.contrib import admin
from django.contrib.auth.models import Group
-from django.forms import TextInput, Textarea
+from django.forms import TextInput
from django.db import models
from import_export.admin import ImportExportModelAdmin
from .models import *
+from .forms import Adaptingtextarea
admin.site.site_header = "QLever UI Administration"
admin.site.site_title = "QLever UI Administration"
@@ -16,6 +17,7 @@
class BackendAdmin(ImportExportModelAdmin):
formfield_overrides = {
models.CharField: {'widget': TextInput(attrs={'size': '140'})},
+ models.TextField: {'widget': Adaptingtextarea()},
}
fieldsets = (
("General", {
@@ -25,13 +27,13 @@ class BackendAdmin(ImportExportModelAdmin):
'fields': ('ntFilePath',),
}),
('UI Suggestions', {
- 'fields': ('maxDefault', 'fillPrefixes', 'filterEntities', 'filteredLanguage', 'supportedKeywords', 'supportedFunctions', 'suggestPrefixnamesForPredicates', 'supportedPredicateSuggestions'),
+ 'fields': ('maxDefault', 'fillPrefixes', 'filterEntities', 'filteredLanguage', 'supportedKeywords', 'supportedFunctions', 'suggestPrefixnamesForPredicates', 'suggestSubjectsInEmptyLine', 'supportedPredicateSuggestions'),
}),
('Backend Suggestions', {
- 'fields': ('suggestSubjects', 'suggestObjects', 'dynamicSuggestions', 'replacePredicates'),
+ 'fields': ('suggestSubjects', 'suggestPredicates', 'suggestObjects', 'dynamicSuggestions', 'replacePredicates'),
}),
('Showing names', {
- 'fields': ('subjectName', 'alternativeSubjectName', 'predicateName', 'alternativePredicateName', 'objectName', 'alternativeObjectName'),
+ 'fields': ('subjectName', 'predicateName', 'objectName'),
}),
)
diff --git a/backend/forms.py b/backend/forms.py
new file mode 100644
index 00000000..469664a5
--- /dev/null
+++ b/backend/forms.py
@@ -0,0 +1,12 @@
+from django.forms import Widget
+
+
+class Adaptingtextarea(Widget):
+ template_name = 'forms/adaptingtextarea.html'
+
+ def __init__(self, attrs=None):
+ default_attrs = {'cols': '140', 'rows': False}
+ if attrs:
+ default_attrs.update(attrs)
+
+ super().__init__(default_attrs)
diff --git a/backend/migrations/0052_backend_suggestpredicates.py b/backend/migrations/0052_backend_suggestpredicates.py
new file mode 100644
index 00000000..7186a8e9
--- /dev/null
+++ b/backend/migrations/0052_backend_suggestpredicates.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.2 on 2020-02-01 12:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0051_auto_20200119_1452'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='backend',
+ name='suggestPredicates',
+ field=models.TextField(blank=True, default='', help_text='Need help?
Clause that tells QLever UI which subjects to suggest from (without prefixes). Leave blank if you don\'t want subject suggestions.
Qlever UI expects the following variables to be used:
- ?qleverui_entity: The subjects that we want to suggest from
Your clause will be used as following:
SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
suggest subjects clause
}
GROUP BY ?qleverui_entity
ORDER BY DESC(?qleverui_count)
', verbose_name='Suggest subjects clause'),
+ ),
+ ]
diff --git a/backend/migrations/0053_auto_20200215_1526.py b/backend/migrations/0053_auto_20200215_1526.py
new file mode 100644
index 00000000..69252e5d
--- /dev/null
+++ b/backend/migrations/0053_auto_20200215_1526.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.0.2 on 2020-02-15 14:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0052_backend_suggestpredicates'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='backend',
+ name='suggestObjects',
+ field=models.TextField(blank=True, default='', help_text='Need help?Clause that tells QLever UI which objects to suggest from.
', verbose_name='Suggest objects clause'),
+ ),
+ migrations.AlterField(
+ model_name='backend',
+ name='suggestPredicates',
+ field=models.TextField(blank=True, default='', help_text='Need help?Clause that tells QLever UI which predicates to suggest from.
', verbose_name='Suggest predicates clause'),
+ ),
+ migrations.AlterField(
+ model_name='backend',
+ name='suggestSubjects',
+ field=models.TextField(blank=True, default='', help_text='Need help?Clause that tells QLever UI which subjects to suggest from. Leave blank if you don\'t want subject suggestions.
', verbose_name='Suggest subjects clause'),
+ ),
+ ]
diff --git a/backend/migrations/0054_merge_20200424_1016.py b/backend/migrations/0054_merge_20200424_1016.py
new file mode 100644
index 00000000..775e82f9
--- /dev/null
+++ b/backend/migrations/0054_merge_20200424_1016.py
@@ -0,0 +1,14 @@
+# Generated by Django 3.0.2 on 2020-04-24 08:16
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0052_backend_supportedpredicatesuggestions'),
+ ('backend', '0053_auto_20200215_1526'),
+ ]
+
+ operations = [
+ ]
diff --git a/backend/migrations/0055_merge_20200425_1057.py b/backend/migrations/0055_merge_20200425_1057.py
new file mode 100644
index 00000000..1f3c2e89
--- /dev/null
+++ b/backend/migrations/0055_merge_20200425_1057.py
@@ -0,0 +1,14 @@
+# Generated by Django 3.0.2 on 2020-04-25 08:57
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0054_merge_20200424_1016'),
+ ('backend', '0053_backend_suggestprefixnamesforpredicates'),
+ ]
+
+ operations = [
+ ]
diff --git a/backend/migrations/0056_backend_suggestsubjectsinemptyline.py b/backend/migrations/0056_backend_suggestsubjectsinemptyline.py
new file mode 100644
index 00000000..8b0548e0
--- /dev/null
+++ b/backend/migrations/0056_backend_suggestsubjectsinemptyline.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.0.2 on 2020-05-22 09:23
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0055_merge_20200425_1057'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='backend',
+ name='suggestSubjectsInEmptyLine',
+ field=models.BooleanField(
+ default=False, help_text='Suggest subjects when no character has been typed yet.', verbose_name='Suggest subject in empty lines.'),
+ ),
+ ]
diff --git a/backend/migrations/0057_auto_20200522_1134.py b/backend/migrations/0057_auto_20200522_1134.py
new file mode 100644
index 00000000..557b59ac
--- /dev/null
+++ b/backend/migrations/0057_auto_20200522_1134.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.0.2 on 2020-05-22 09:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('backend', '0056_backend_suggestsubjectsinemptyline'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='backend',
+ name='alternativeObjectName',
+ ),
+ migrations.RemoveField(
+ model_name='backend',
+ name='alternativePredicateName',
+ ),
+ migrations.RemoveField(
+ model_name='backend',
+ name='alternativeSubjectName',
+ ),
+ migrations.AlterField(
+ model_name='backend',
+ name='suggestSubjectsInEmptyLine',
+ field=models.BooleanField(default=False, help_text='Suggest subjects when no character has been typed yet.', verbose_name='Suggest subjects in empty lines.'),
+ ),
+ ]
diff --git a/backend/models.py b/backend/models.py
index 59f95726..4f7f3db6 100644
--- a/backend/models.py
+++ b/backend/models.py
@@ -54,13 +54,19 @@ class Backend(models.Model):
suggestSubjects = models.TextField(
default='',
blank=True,
- help_text="Need help?Clause that tells QLever UI which subjects to suggest from (without prefixes). Leave blank if you don't want subject suggestions.
Qlever UI expects the following variables to be used:
- ?qleverui_entity: The subjects that we want to suggest from
Your clause will be used as following:
SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
suggest subjects clause
}
GROUP BY ?qleverui_entity
ORDER BY DESC(?qleverui_count)
",
+ help_text="Need help?Clause that tells QLever UI which subjects to suggest from. Leave blank if you don't want subject suggestions.
",
verbose_name="Suggest subjects clause")
+ suggestPredicates = models.TextField(
+ default='',
+ blank=True,
+ help_text="Need help?Clause that tells QLever UI which predicates to suggest from.
",
+ verbose_name="Suggest predicates clause")
+
suggestObjects = models.TextField(
default='',
blank=True,
- help_text="Need help?Clause that tells QLever UI which objects to suggest from (without prefixes). Only needed for suggestion mode 2 (context insensitive suggestions).
Qlever UI expects the following variables to be used:
- ?qleverui_entity: The objects that we want to suggest from
Your clause will be used as following:
SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
suggest objects clause
}
GROUP BY ?qleverui_entity
ORDER BY DESC(?qleverui_count)
",
+ help_text="Need help?Clause that tells QLever UI which objects to suggest from.
",
verbose_name="Suggest objects clause")
subjectName = models.TextField(
@@ -69,36 +75,18 @@ class Backend(models.Model):
help_text="Need help?Clause that tells QLever UI the name of a subject (without prefixes). Qlever UI expects the following variables to be used:
- ?qleverui_entity: The subject that we want to get the name of
- ?qleverui_name: The variable that will hold the subject's name
Your clause should end in a dot '.' or closing bracket '}'
Your clause will be used as following:
SELECT ?qleverui_name WHERE {
?qleverui_entity <predicate> <object>
OPTIONAL {
subject name clause
}
}
",
verbose_name="Subject name clause")
- alternativeSubjectName = models.TextField(
- default='',
- blank=True,
- help_text="Need help?Clause that tells QLever UI the alternative name of a subject (without prefixes). Qlever UI expects the following variables to be used:
- ?qleverui_entity: The subject that we want to get the name of
- ?qleverui_altname: The variable that will hold the subject's alternative name
Your clause should end in a dot '.' or closing bracket '}'
Your clause will be used as following:
SELECT ?qleverui_altname WHERE {
?qleverui_entity <predicate> <object>
OPTIONAL {
alternative subject name clause
}
}
",
- verbose_name="Alternative subject name clause")
-
predicateName = models.TextField(
default='',
blank=True,
help_text="Need help?Clause that tells QLever UI the name of a predicate (without prefixes). Qlever UI expects the following variables to be used:
- ?qleverui_entity: The predicate that we want to get the name of
- ?qleverui_name: The variable that will hold the predicate's name
Your clause should end in a dot '.' or closing bracket '}'
Your clause will be used as following:
SELECT ?qleverui_name WHERE {
<subject> ?qleverui_entity <object>
OPTIONAL {
predicate name clause
}
}
",
verbose_name="Predicate name clause")
- alternativePredicateName = models.TextField(
- default='',
- blank=True,
- help_text="Need help?Clause that tells QLever UI the alternative name of a predicate (without prefixes). Qlever UI expects the following variables to be used:
- ?qleverui_entity: The predicate that we want to get the name of
- ?qleverui_altname: The variable that will hold the predicate's alternative name
Your clause should end in a dot '.' or closing bracket '}'
Your clause will be used as following:
SELECT ?qleverui_altname WHERE {
<subject> ?qleverui_entity <object>
OPTIONAL {
alternative predicate name clause
}
}
",
- verbose_name="Alternative predicate name clause")
-
objectName = models.TextField(
default='',
blank=True,
help_text="Need help?Clause that tells QLever UI the name of an object (without prefixes). Qlever UI expects the following variables to be used:
- ?qleverui_entity: The object that we want to get the name of
- ?qleverui_name: The variable that will hold the object's name
Your clause should end in a dot '.' or closing bracket '}'
Your clause will be used as following:
SELECT ?qleverui_name WHERE {
<subject> <predicate> ?qleverui_entity
OPTIONAL {
object name clause
}
}
",
verbose_name="Object name clause")
- alternativeObjectName = models.TextField(
- default='',
- blank=True,
- help_text="Need help?Clause that tells QLever UI the alternativename of an object (without prefixes). Qlever UI expects the following variables to be used:
- ?qleverui_entity: The object that we want to get the name of
- ?qleverui_altname: The variable that will hold the object's alternative name
Your clause should end in a dot '.' or closing bracket '}'
Your clause will be used as following:
SELECT ?qleverui_altname WHERE {
<subject> <predicate> ?qleverui_entity
OPTIONAL {
alternative object name clause
}
}
",
- verbose_name="Alternative object name clause")
-
replacePredicates = models.TextField(
default='',
blank=True,
@@ -136,6 +124,11 @@ class Backend(models.Model):
help_text="Suggest Prefix names without a particular entity when autocompleting predicates.",
verbose_name="Suggest prefix names for predicates.")
+ suggestSubjectsInEmptyLine = models.BooleanField(
+ default=False,
+ help_text="Suggest subjects when no character has been typed yet.",
+ verbose_name="Suggest subjects in empty lines.")
+
fillPrefixes = models.BooleanField(
default=True,
help_text="Replace prefixes in suggestions even if they are not yet declared in the query. Add prefix declarations if a suggestion with not yet declared prefix is picked.",
@@ -148,7 +141,7 @@ class Backend(models.Model):
def save(self, *args, **kwargs):
# We need to replace \r because QLever can't handle them very well
- for field in ('subjectName', 'predicateName', 'objectName', 'suggestSubjects', 'suggestObjects', 'alternativeSubjectName', 'alternativePredicateName', 'alternativeObjectName'):
+ for field in ('subjectName', 'predicateName', 'objectName', 'suggestSubjects', 'suggestPredicates', 'suggestObjects'):
setattr(self, field, str(getattr(self, field)).replace(
"\r\n", "\n").replace("\r", "\n"))
super(Backend, self).save(*args, **kwargs)
@@ -195,7 +188,7 @@ def predicateSuggestions(self):
def entityNameQueries(self):
data = {}
- for field in ('subjectName', 'predicateName', 'objectName', 'suggestSubjects', 'suggestObjects', 'alternativeSubjectName', 'alternativePredicateName', 'alternativeObjectName'):
+ for field in ('subjectName', 'predicateName', 'objectName', 'suggestSubjects', 'suggestPredicates', 'suggestObjects'):
data[field.upper()] = getattr(self, field)
return json.dumps(data)
diff --git a/backend/static/js/codemirror/modes/sparql/sparql-hint.js b/backend/static/js/codemirror/modes/sparql/sparql-hint.js
index 2c974cda..ba4a9d17 100755
--- a/backend/static/js/codemirror/modes/sparql/sparql-hint.js
+++ b/backend/static/js/codemirror/modes/sparql/sparql-hint.js
@@ -407,7 +407,7 @@ function getDynamicSuggestions(context) {
sparqlQuery = "";
var sendSparql = !(word.startsWith('?'));
var sparqlLines = "";
- var nameClause;
+ var completionQuery = "";
var suggestVariables;
var appendToSuggestions = "";
var nameList;
@@ -417,17 +417,14 @@ function getDynamicSuggestions(context) {
if (words.length == 1) {
suggestVariables = "both";
appendToSuggestions = " ";
- if (SUGGESTSUBJECTS.length > 0 && word.length > 0 && word != "<") {
- sparqlLines = SUGGESTSUBJECTS.replace(/\n/g, "\n ").trim() + ' .';
- nameClause = SUBJECTNAME;
- altNameClause = ALTERNATIVESUBJECTNAME;
+ if (SUGGESTSUBJECTS.length > 0 && (word.length > 0 || SUGGEST_SUBJECTS_IN_EMPTY_LINE)) {
+ completionQuery = SUGGESTSUBJECTS;
nameList = subjectNames;
} else {
sendSparql = false;
}
+
} else if (words.length == 2) {
- nameClause = PREDICATENAME;
- altNameClause = ALTERNATIVEPREDICATENAME;
suggestVariables = word.startsWith('?') ? "normal" : false;
appendToSuggestions = " ";
nameList = predicateNames;
@@ -439,13 +436,10 @@ function getDynamicSuggestions(context) {
if (suggestionMode == 1) {
sparqlLines = "?qleverui_subject ql:has-predicate ?qleverui_entity .";
} else if (suggestionMode == 2) {
- lines.push(words[0] + " ql:has-predicate ?qleverui_entity .");
- sparqlLines = lines.join("\n ");
+ completionQuery = SUGGESTPREDICATES;
}
} else if (words.length == 3) {
predicateForObject = words[1];
- nameClause = OBJECTNAME;
- altNameClause = ALTERNATIVEOBJECTNAME;
suggestVariables = "normal";
appendToSuggestions = ' .';
nameList = objectNames;
@@ -456,6 +450,8 @@ function getDynamicSuggestions(context) {
sendSparql = false;
}
} else if (suggestionMode == 2) {
+ completionQuery = SUGGESTOBJECTS;
+
// replace the prefixes
var propertyPath = detectPropertyPath(words[1]);
@@ -468,24 +464,23 @@ function getDynamicSuggestions(context) {
property = property.slice(0, property.length - 1);
addAsterisk = true;
}
- property = '<' + property.replace(key + ':', value) + '>';
+ let noPrefixProperty = '<' + property.replace(key + ':', value) + '>';
+
+ if (REPLACE_PREDICATES[noPrefixProperty] !== undefined) {
+ property = REPLACE_PREDICATES[noPrefixProperty];
+ }
+
if (addAsterisk) {
property += "*";
}
- if (REPLACE_PREDICATES[predicate] !== undefined) {
- property = REPLACE_PREDICATES[property];
- }
propertyPath[i] = property;
return false;
}
});
}
- var predicate = propertyPath.join("/");
-
- lines.push(words[0] + " " + predicate + " ?qleverui_entity .");
- sparqlLines = lines.join("\n ");
+ words[1] = propertyPath.join("/");
}
var lastWord = words[1];
@@ -517,84 +512,8 @@ function getDynamicSuggestions(context) {
}
if (sendSparql) {
- // find all entities whose ids match what we typed
- var entityNameWord = ((word.startsWith("<") || word.startsWith('"')) ? "" : "<") + word.replace(/'/g, "\\'");
- var entityQuery =
- " {\n" +
- " SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {\n" +
- " " + sparqlLines + "\n" +
- " }\n" +
- " GROUP BY ?qleverui_entity\n" + ((word.length > 0 && word != "<") ?
- " HAVING regex(?qleverui_entity, '^" + entityNameWord + "')\n" : "") +
- " }\n" + ((nameClause.length > 0) ? // get entity names if we know how to query them
- " OPTIONAL {\n" +
- " " + nameClause.replace(/\n/g, "\n ") + "\n" +
- " }\n" : "") + ((altNameClause.length > 0) ?
- " OPTIONAL {\n" +
- " " + altNameClause.replace(/\n/g, "\n ") + "\n" +
- " }\n" : "");
-
- sparqlQuery = prefixes;
- if (nameClause.length > 0) {
- if (altNameClause.length > 0 && word.length > 0) {
- sparqlQuery +=
- "SELECT ?qleverui_entity (SAMPLE(?qleverui_name) as ?qleverui_name) (SAMPLE(?qleverui_altname) as ?qleverui_altname) (SAMPLE(?qleverui_count) as ?qleverui_count) WHERE {\n" +
- " {\n";
- } else {
- sparqlQuery +=
- "SELECT ?qleverui_entity (SAMPLE(?qleverui_name) as ?qleverui_name) (SAMPLE(?qleverui_count) as ?qleverui_count) WHERE {\n";
- }
- if (word.length > 0) {
- // find all entities whose names match what we typed and UNION it with entityQuery
- sparqlQuery +=
- " {\n" +
- entityQuery +
- " }\n" +
- " UNION\n" +
- " {\n" +
- " {\n" +
- " SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {\n" +
- " " + sparqlLines + "\n" +
- " }\n" +
- " GROUP BY ?qleverui_entity\n" +
- " }\n" +
- " " + nameClause.replace(/\n/g, "\n ") + "\n" +
- " FILTER regex(?qleverui_name, '^\"" + word + "')\n" +
- " }\n";
-
- if (altNameClause.length > 0) {
- sparqlQuery += " }\n" +
- " UNION\n" +
- " {\n" +
- " {\n" +
- " SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {\n" +
- " " + sparqlLines + "\n" +
- " }\n" +
- " GROUP BY ?qleverui_entity\n" +
- " }\n" +
- " " + altNameClause.replace(/\n/g, "\n ") + "\n" +
- " FILTER regex(?qleverui_altname, '^\"" + word + "')\n" +
- " OPTIONAL {\n" +
- " " + nameClause.replace(/\n/g, "\n ") + "\n" +
- " }\n" +
- " }\n";
- }
- } else {
- // There was no input that we can search for -> just do entityQuery
- sparqlQuery += entityQuery;
- }
- } else {
- // We don't know how to get entity names -> just do entityQuery
- sparqlQuery +=
- "SELECT ?qleverui_entity ?qleverui_count WHERE {\n" +
- entityQuery;
- }
- sparqlQuery +=
- "}\n" + ((nameClause.length > 0) ?
- "GROUP BY ?qleverui_entity\n" : "") +
- "ORDER BY DESC(?qleverui_count)";
-
- getQleverSuggestions(sparqlQuery, prefixesRelation, appendToSuggestions, nameList, predicateForObject, word);
+ sparqlLines = replaceQueryPlaceholders(completionQuery, word, prefixes, lines, words);
+ getQleverSuggestions(sparqlLines, prefixesRelation, appendToSuggestions, nameList, predicateForObject, word);
}
if (suggestVariables) {
@@ -611,6 +530,121 @@ function getDynamicSuggestions(context) {
return [];
}
+function replaceQueryPlaceholders(completionQuery, word, prefixes, lines, words) {
+ word = escapeRegExp(word);
+ var word_with_bracket = ((word.startsWith("<") || word.startsWith('"')) ? "" : "<") + word.replace(/'/g, "\\'");
+ sparqlLines = completionQuery.replace(/% 0) {
+ sparqlLines = sparqlLines.replace(/%CURRENT_SUBJECT%/g, words[0]);
+ }
+ if (words.length > 1) {
+ sparqlLines = sparqlLines.replace(/%CURRENT_PREDICATE%/g, words[1]);
+ }
+
+ sparqlLines = evaluateIfStatements(sparqlLines, word, lines, words)
+
+ return sparqlLines;
+}
+
+function evaluateIfStatements(completionQuery, word, lines, words) {
+ // find all IF statements
+ let if_statements = [];
+ const ifRegex = /#\sIF\s+([!A-Z_\s]+)\s+#/;
+ let match = completionQuery.match(ifRegex);
+ let substrIdx = 0;
+ while (match != null) {
+ // find all IF declarations
+ const index = match.index;
+ const len = match[0].length;
+ substrIdx += index + len;
+ if_statements.push({ 'IF': { 'index': substrIdx - len, 'len': len }, 'condition': match[1] });
+ const substr = completionQuery.slice(substrIdx);
+ match = substr.match(ifRegex);
+ }
+
+ if_statements = if_statements.reverse();
+ for (let statement of if_statements) {
+ // find matching ELSE and ENDIFs
+ const start = statement['IF']['index'];
+ const endifMatch = completionQuery.slice(start).match(/#\sENDIF\s#/);
+ const elseMatch = completionQuery.slice(start).match(/#\sELSE\s#/);
+
+ if (elseMatch != null && elseMatch.index < endifMatch.index) {
+ const index = start + elseMatch.index;
+ const len = elseMatch[0].length;
+ statement['ELSE'] = { 'index': index, 'len': len };
+ }
+
+ if (endifMatch == null) {
+ console.error("Number of # IF # and # ENDIF # does not match!");
+ }
+ const index = start + endifMatch.index;
+ const len = endifMatch[0].length;
+ statement['ENDIF'] = { 'index': index, 'len': len }
+
+ let conditionSatisfied = parseAndEvaluateCondition(statement.condition, word, lines, words);
+
+ let result = completionQuery.slice(0, statement['IF']['index']);
+
+ if (conditionSatisfied && statement["ELSE"] == undefined) {
+ // Add content between IF and ENDIF
+ result += completionQuery.slice(statement['IF']['index'] + statement['IF']['len'], statement['ENDIF']['index']);
+ } else if (conditionSatisfied && statement["ELSE"] != undefined) {
+ // Add content between IF and ELSE
+ result += completionQuery.slice(statement['IF']['index'] + statement['IF']['len'], statement['ELSE']['index']);
+ } else if (!conditionSatisfied && statement["ELSE"] != undefined) {
+ // Add content between ELSE and ENDIF
+ result += completionQuery.slice(statement['ELSE']['index'] + statement['ELSE']['len'], statement['ENDIF']['index']);
+ }
+
+ result += completionQuery.slice(statement['ENDIF']['index'] + statement['ENDIF']['len']);
+ completionQuery = result;
+ }
+
+ return completionQuery
+}
+
+function parseAndEvaluateCondition(condition, word, lines, words) {
+ // split condition by AND and OR
+ const logicalOperator = condition.match(/(.*)\s+(OR)\s+(.*)/) || condition.match(/(.*)\s(AND)\s+(.*)/);
+ const negated = condition.startsWith("!");
+ let conditionSatisfied = false;
+ if (logicalOperator != null) {
+ const lhs = parseAndEvaluateCondition(logicalOperator[1], word, lines, words);
+ const rhs = parseAndEvaluateCondition(logicalOperator[3], word, lines, words);
+
+ if (logicalOperator[2] == "OR") {
+ conditionSatisfied = lhs || rhs;
+ } else {
+ conditionSatisfied = lhs && rhs;
+ }
+ } else if (negated) {
+ conditionSatisfied = !parseAndEvaluateCondition(condition.slice(1), word, lines, words);
+ } else {
+ if (condition == "CURRENT_WORD_EMPTY") {
+ conditionSatisfied = (word.length == 0);
+ } else if (condition == "CURRENT_SUBJECT_VARIABLE") {
+ conditionSatisfied = (words.length > 0 && words[0].startsWith("?"));
+ } else if (condition == "CURRENT_PREDICATE_VARIABLE") {
+ conditionSatisfied = (words.length > 1 && words[1].startsWith("?"));
+ } else if (condition == "CONNECTED_TRIPLES_EMPTY") {
+ conditionSatisfied = (lines.length == 0);
+ } else {
+ console.error(`Invalid condition in IF statement: '${condition}'`);
+ }
+ }
+ log(`Evaluating condition: "${condition}", word="${word}", lines=${lines.length}, words=${words}\n result: ${conditionSatisfied}`, 'other');
+ return conditionSatisfied;
+}
function getQleverSuggestions(sparqlQuery, prefixesRelation, appendix, nameList, predicateForObject, word) {
diff --git a/backend/static/js/helper.js b/backend/static/js/helper.js
index 16182a4c..56b01810 100644
--- a/backend/static/js/helper.js
+++ b/backend/static/js/helper.js
@@ -221,6 +221,11 @@ function htmlEscape(str) {
.replace(/>/g, ">")
}
+function escapeRegExp(string) {
+ // Escapes strings for use in QLever REGEX filter
+ return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\\\$&'); // $& means the whole matched string
+}
+
function getShortStr(str, maxLength, column = undefined) {
str = str.replace(/_/g, ' ');
var pos;
diff --git a/backend/templates/forms/adaptingtextarea.html b/backend/templates/forms/adaptingtextarea.html
new file mode 100644
index 00000000..9d3c12d5
--- /dev/null
+++ b/backend/templates/forms/adaptingtextarea.html
@@ -0,0 +1,3 @@
+{% load qleverui_template_tags %}
+
\ No newline at end of file
diff --git a/backend/templates/index.html b/backend/templates/index.html
index 06265f72..3d707c18 100644
--- a/backend/templates/index.html
+++ b/backend/templates/index.html
@@ -26,10 +26,8 @@
var SUBJECTNAME = ENTITYNAMERELATIONS["SUBJECTNAME"];
var PREDICATENAME = ENTITYNAMERELATIONS["PREDICATENAME"];
var OBJECTNAME = ENTITYNAMERELATIONS["OBJECTNAME"];
- var ALTERNATIVESUBJECTNAME = ENTITYNAMERELATIONS["ALTERNATIVESUBJECTNAME"];
- var ALTERNATIVEPREDICATENAME = ENTITYNAMERELATIONS["ALTERNATIVEPREDICATENAME"];
- var ALTERNATIVEOBJECTNAME = ENTITYNAMERELATIONS["ALTERNATIVEOBJECTNAME"];
var SUGGESTSUBJECTS = ENTITYNAMERELATIONS["SUGGESTSUBJECTS"];
+ var SUGGESTPREDICATES = ENTITYNAMERELATIONS["SUGGESTPREDICATES"];
var SUGGESTOBJECTS = ENTITYNAMERELATIONS["SUGGESTOBJECTS"];
var COLLECTEDPREFIXES = {% autoescape off %}{{ prefixes }}{% endautoescape %};
var LANGUAGES = {% autoescape off %}{{ backend.languages }}{% endautoescape %};
@@ -40,6 +38,7 @@
var FILLPREFIXES = {{ backend.fillPrefixes|lower }};
var FILTER_TYPES = {% if backend.filterEntities %}ENTITY{% else %}LITERAL{% endif %};
var REPLACE_PREDICATES = {% autoescape off %}{{ backend.replacePredicatesList }}{% endautoescape %};
+ var SUGGEST_SUBJECTS_IN_EMPTY_LINE = {{ backend.suggestSubjectsInEmptyLine|lower }};
var examples = [];
{% for example in examples %}
examples.push(`{{ example.query|safe }}`);
diff --git a/backend/templatetags/__init__.py b/backend/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/backend/templatetags/qleverui_template_tags.py b/backend/templatetags/qleverui_template_tags.py
new file mode 100644
index 00000000..570c6919
--- /dev/null
+++ b/backend/templatetags/qleverui_template_tags.py
@@ -0,0 +1,13 @@
+from django import template
+
+register = template.Library()
+
+
+@register.filter()
+def split(value, arg='\n'):
+ return str(value).split(arg)
+
+
+@register.filter()
+def minimum(value, arg):
+ return max(value, arg)
diff --git a/resources/backend-sample.csv b/resources/backend-sample.csv
index 846d232d..8620e914 100644
--- a/resources/backend-sample.csv
+++ b/resources/backend-sample.csv
@@ -1,10 +1,203 @@
-id,name,baseUrl,ntFilePath,ntFileLastChange,isDefault,isImporting,maxDefault,filteredLanguage,dynamicSuggestions,suggestSubjects,suggestObjects,subjectName,alternativeSubjectName,predicateName,alternativePredicateName,objectName,alternativeObjectName,replacePredicates,supportedKeywords,supportedFunctions,fillPrefixes,filterEntities
-9999,Wikidata Full @ Uni Freiburg,http://qlever.informatik.uni-freiburg.de/api/wikidata-full,,0,1,0,100,en,2,?qleverui_sitelink ?qleverui_entity,?qleverui_sitelink ?qleverui_entity,?qleverui_entity @en@ ?qleverui_name .,?qleverui_entity @en@ ?qleverui_altname .,"{ ?qleverui_claim ?qleverui_entity .
+id,name,baseUrl,ntFilePath,ntFileLastChange,isDefault,isImporting,maxDefault,filteredLanguage,dynamicSuggestions,suggestSubjects,suggestPredicates,suggestObjects,subjectName,predicateName,objectName,replacePredicates,supportedKeywords,supportedFunctions,supportedPredicateSuggestions,suggestPrefixnamesForPredicates,suggestSubjectsInEmptyLine,fillPrefixes,filterEntities
+9999,Wikidata Full @ Uni Freiburg,http://qlever.informatik.uni-freiburg.de/api/wikidata-full,,0,1,0,100,en,2,"SELECT ?qleverui_entity (SAMPLE(?qleverui_name) as ?qleverui_name) (SAMPLE(?qleverui_altname) as ?qleverui_altname) (SAMPLE(?qleverui_count) as ?qleverui_count) WHERE {
+# IF !CURRENT_WORD_EMPTY #
+ {
+ {
+# ENDIF #
+ {
+ SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
+ ?qleverui_sitelink ?qleverui_entity .
+ }
+ GROUP BY ?qleverui_entity
+# IF !CURRENT_WORD_EMPTY #
+ HAVING regex(?qleverui_entity, '^% ?qleverui_name .
+ }
+ OPTIONAL {
+ ?qleverui_entity @en@ ?qleverui_altname .
+ }
+ }
+# IF !CURRENT_WORD_EMPTY #
+ UNION
+ {
+ {
+ SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
+ ?qleverui_sitelink ?qleverui_entity .
+ }
+ GROUP BY ?qleverui_entity
+ }
+ ?qleverui_entity @en@ ?qleverui_name .
+ FILTER regex(?qleverui_name, '^""%CURRENT_WORD%')
+ }
+ }
+ UNION
+ {
+ {
+ SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
+ ?qleverui_sitelink ?qleverui_entity .
+ }
+ GROUP BY ?qleverui_entity
+ }
+ ?qleverui_entity @en@ ?qleverui_altname .
+ FILTER regex(?qleverui_altname, '^""%CURRENT_WORD%')
+ OPTIONAL {
+ ?qleverui_entity @en@ ?qleverui_name .
+ }
+ }
+}
+# ENDIF #
+GROUP BY ?qleverui_entity
+ORDER BY DESC(?qleverui_count)","%PREFIXES%
+SELECT ?qleverui_entity (SAMPLE(?qleverui_name) as ?qleverui_name) (SAMPLE(?qleverui_altname) as ?qleverui_altname) (SAMPLE(?qleverui_count) as ?qleverui_count) WHERE {
+# IF !CURRENT_WORD_EMPTY #
+ {
+ {
+# ENDIF #
+ {
+ SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
+ %CONNECTED_TRIPLES%
+ %CURRENT_SUBJECT% ql:has-predicate ?qleverui_entity .
+ }
+ GROUP BY ?qleverui_entity
+# IF !CURRENT_WORD_EMPTY #
+ HAVING regex(?qleverui_entity, '^% ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } .
+ }
+ OPTIONAL {
+ { { { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_altname } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_altname } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_altname } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_altname } .
+ }
+ }
+# IF !CURRENT_WORD_EMPTY #
+ UNION
+ {
+ {
+ SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
+ %CONNECTED_TRIPLES%
+ %CURRENT_SUBJECT% ql:has-predicate ?qleverui_entity .
+ }
+ GROUP BY ?qleverui_entity
+ }
+ { { { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } .
+ FILTER regex(?qleverui_name, '^""%CURRENT_WORD%')
+ }
+ }
+ UNION
+ {
+ {
+ SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
+ %CONNECTED_TRIPLES%
+ %CURRENT_SUBJECT% ql:has-predicate ?qleverui_entity .
+ }
+ GROUP BY ?qleverui_entity
+ }
+ { { { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_altname } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_altname } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_altname } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_altname } .
+ FILTER regex(?qleverui_altname, '^""%CURRENT_WORD%')
+ OPTIONAL {
+ { { { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } } UNION
+ { ?qleverui_claim ?qleverui_entity .
+ ?qleverui_claim @en@ ?qleverui_name } .
+ }
+ }
+}
+# ENDIF #
+GROUP BY ?qleverui_entity
+ORDER BY DESC(?qleverui_count)","%PREFIXES%
+SELECT ?qleverui_entity (SAMPLE(?qleverui_name) as ?qleverui_name) (SAMPLE(?qleverui_altname) as ?qleverui_altname) (SAMPLE(?qleverui_count) as ?qleverui_count) WHERE {
+# IF !CURRENT_WORD_EMPTY #
+ {
+ {
+# ENDIF #
+ {
+ SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
+ %CONNECTED_TRIPLES%
+ %CURRENT_SUBJECT% %CURRENT_PREDICATE% ?qleverui_entity .
+ }
+ GROUP BY ?qleverui_entity
+# IF !CURRENT_WORD_EMPTY #
+ HAVING regex(?qleverui_entity, '^% ?qleverui_name .
+ }
+ OPTIONAL {
+ ?qleverui_entity @en@ ?qleverui_altname .
+ }
+ }
+# IF !CURRENT_WORD_EMPTY #
+ UNION
+ {
+ {
+ SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
+ %CONNECTED_TRIPLES%
+ %CURRENT_SUBJECT% %CURRENT_PREDICATE% ?qleverui_entity .
+ }
+ GROUP BY ?qleverui_entity
+ }
+ ?qleverui_entity @en@ ?qleverui_name .
+ FILTER regex(?qleverui_name, '^""%CURRENT_WORD%')
+ }
+ }
+ UNION
+ {
+ {
+ SELECT ?qleverui_entity (COUNT(?qleverui_entity) AS ?qleverui_count) WHERE {
+ %CONNECTED_TRIPLES%
+ %CURRENT_SUBJECT% %CURRENT_PREDICATE% ?qleverui_entity .
+ }
+ GROUP BY ?qleverui_entity
+ }
+ ?qleverui_entity @en@ ?qleverui_altname .
+ FILTER regex(?qleverui_altname, '^""%CURRENT_WORD%')
+ OPTIONAL {
+ ?qleverui_entity @en@ ?qleverui_name .
+ }
+ }
+}
+# ENDIF #
+GROUP BY ?qleverui_entity
+ORDER BY DESC(?qleverui_count)",?qleverui_entity @en@ ?qleverui_name .,"{ ?qleverui_claim ?qleverui_entity .
?qleverui_claim @en@ ?qleverui_name } UNION
{ ?qleverui_claim ?qleverui_entity .
- ?qleverui_claim @en@ ?qleverui_name } .","{ ?qleverui_claim ?qleverui_entity .
- ?qleverui_claim @en@ ?qleverui_altname } UNION
- { ?qleverui_claim ?qleverui_entity .
- ?qleverui_claim @en@ ?qleverui_altname }",?qleverui_entity @en@ ?qleverui_name .,?qleverui_entity @en@ ?qleverui_altname .," @en@
- @en@
- @en@","prefix, select, distinct, where, order, limit, offset, optional, by, as, having, not, textlimit, contains-entity, contains-word, filter, group, union, optional, has-predicate","asc, desc, avg, values, score, text, count, sample, min, max, average, concat, group_concat, langMatches, lang, regex, sum",1,0
+ ?qleverui_claim @en@ ?qleverui_name } .",?qleverui_entity @en@ ?qleverui_name .," @en@
+ @en@
+ @en@","prefix, select, distinct, where, order, limit, offset, optional, by, as, having, not, textlimit, contains-entity, contains-word, filter, group, union, optional, has-predicate","asc, desc, avg, values, score, text, count, sample, min, max, average, concat, group_concat, langMatches, lang, regex, sum","ql:contains-word, ql:contains-entity",1,0,1,0