Commit e416ed6b authored by Marcel Nellesen's avatar Marcel Nellesen
Browse files

Merge branch 'Topic/1229-inheritance' into 'Product/1210-inheritance'

New: Implement Inheritance

See merge request !48
parents 1bef948d cd828fcb
{
"name": "@coscine/form-generator",
"version": "1.10.0",
"version": "1.10.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -3605,9 +3605,9 @@
}
},
"@types/node": {
"version": "13.13.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz",
"integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A=="
"version": "14.14.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.12.tgz",
"integrity": "sha512-ASH8OPHMNlkdjrEdmoILmzFfsJICvhBsFfAum4aKZ/9U4B6M6tTmTPh+f3ttWdD74CEGV5XvXWkbyfSdXaTd7g=="
},
"@types/normalize-package-data": {
"version": "2.4.0",
......@@ -3653,9 +3653,9 @@
}
},
"@types/rdfjs__namespace": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@types/rdfjs__namespace/-/rdfjs__namespace-1.1.2.tgz",
"integrity": "sha512-xgRo4FkwZrm8EZN/z1eG9FvuvsfjK5d1lv+aaUdFno4N3VqIuuVKO6Pjh+UDcGbg89QUlQ6Xlk4qCRiRkzT4NQ==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/rdfjs__namespace/-/rdfjs__namespace-1.1.3.tgz",
"integrity": "sha512-seCWK6z0TqF40ioY7lMhR624I9ovo7xhnhvhAvbWlxfXYtsoQ4QEoddaWFmJDqfat2M1Xv6ThRSN2JGm2OtTIw==",
"requires": {
"@types/rdf-js": "*"
}
......@@ -8534,9 +8534,9 @@
}
},
"clownface": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/clownface/-/clownface-1.0.0.tgz",
"integrity": "sha512-8gAAqdD5ueYt62FHE9Zd4OTrcYEfiyQlxV/5NYxzKY5uO/HZbTv1+jhFHgYa3XD7BC+mkg+LFR9Mh168wj9aQQ==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/clownface/-/clownface-1.2.0.tgz",
"integrity": "sha512-01iluBG+GPzFFFvFPhZxuWf8gU8mQVdm6gkvI71xl4fJyJSX7VpX3US4LbMC+0D6+KK75C+Q9AFtYpzv3fKvmg==",
"requires": {
"@rdfjs/data-model": "^1.1.0",
"@rdfjs/namespace": "^1.0.0"
......@@ -12826,9 +12826,9 @@
}
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.0",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.0.tgz",
"integrity": "sha512-NEKcI0+osT5bBFZ1SFGzJMQETjQWZrSvMO1g0nAR/w0t328Z41eN8BJEIZyFCl2HsuiJpa9AN474Nh2qLVwGLQ==",
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"dev": true,
"optional": true,
"requires": {
......@@ -12960,9 +12960,9 @@
"optional": true
},
"import-fresh": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
"integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz",
"integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==",
"dev": true,
"optional": true,
"requires": {
......@@ -12970,6 +12970,16 @@
"resolve-from": "^4.0.0"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"optional": true,
"requires": {
"yallist": "^4.0.0"
}
},
"parse-json": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz",
......@@ -13010,9 +13020,19 @@
}
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"dev": true,
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"optional": true
}
......@@ -15154,11 +15174,6 @@
"integrity": "sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ==",
"dev": true
},
"jquery": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
"integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
},
"js-base64": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
......@@ -15367,6 +15382,13 @@
"cross-fetch": "^3.0.6",
"http-link-header": "^1.0.2",
"relative-to-absolute-iri": "^1.0.5"
},
"dependencies": {
"@types/node": {
"version": "13.13.35",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.35.tgz",
"integrity": "sha512-q9aeOGwv+RRou/ca4aJVUM/jD5u7LBexu+rq9PkA/NhHNn8JifcMo94soKm0b6JGSfw/PSNdqtc428OscMvEYA=="
}
}
},
"jsonld-streaming-parser": {
......@@ -23199,22 +23221,22 @@
}
},
"rdf-validate-shacl": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/rdf-validate-shacl/-/rdf-validate-shacl-0.2.3.tgz",
"integrity": "sha512-CBXwRrkQbKkx7iIsiM8hPLzeWQo5ZrDag9HAMtla46SYt14BS9QnCOD/VcuQYEZVVrxiw80PoOG7qHKxLVOWnw==",
"requires": {
"@rdfjs/dataset": "~1.0.1",
"@rdfjs/namespace": "~1.1.0",
"@rdfjs/term-set": "~1.0.1",
"clownface": "~1.0.0",
"debug": "^4.1.1",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/rdf-validate-shacl/-/rdf-validate-shacl-0.2.5.tgz",
"integrity": "sha512-Xs9fmGJT7R7YX5NU+7OyW4eVv0zGvPwXfvt8sqS/MaPV07BvKvlKkyzR2OBIYSsiiEvuj5ufcfmKUHvIFSZHCg==",
"requires": {
"@rdfjs/dataset": "^1.0.0",
"@rdfjs/namespace": "^1.0.0",
"@rdfjs/term-set": "^1.0.0",
"clownface": "^1.0.0",
"debug": "^4.0.0",
"rdf-validate-datatype": "^0.1.1"
},
"dependencies": {
"debug": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
......@@ -26689,9 +26711,9 @@
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.0.0-beta.8",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.8.tgz",
"integrity": "sha512-oouKUQWWHbSihqSD7mhymGPX1OQ4hedzAHyvm8RdyHh6m3oIvoRF+NM45i/bhNOlo8jCnuJhaSUf/6oDjv978g==",
"version": "npm:vue-loader@16.1.1",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.1.tgz",
"integrity": "sha512-wz/+HFg/3SBayHWAlZXARcnDTl3VOChrfW9YnxvAweiuyKX/7IGx1ad/4yJHmwhgWlOVYMAbTiI7GV8G33PfGQ==",
"dev": true,
"optional": true,
"requires": {
......@@ -22,26 +22,27 @@
<script lang="ts">
import Vue from 'vue';
import { validationMixin } from 'vuelidate';
import { required, minLength, maxLength } from 'vuelidate/lib/validators';
import VueI18n from 'vue-i18n';
import BootstrapVue from 'bootstrap-vue';
import Vuelidate from 'vuelidate';
import { validationMixin } from 'vuelidate';
import { required, minLength, maxLength } from 'vuelidate/lib/validators';
import { Quad } from 'rdf-js';
import { LanguageUtil } from '@coscine/app-util';
import FieldReader from './util/FieldReader';
import factory from 'rdf-ext';
import SHACLValidator from 'rdf-validate-shacl';
import rdfParser from "rdf-parse";
import { Readable } from 'stream';
import WrapperInput from './components/WrapperInput.vue';
Vue.use(BootstrapVue);
import LinkedDataHandler from './base/LinkedDataHandler';
Vue.use(VueI18n);
Vue.use(BootstrapVue);
Vue.use(Vuelidate);
const i18n = new VueI18n({
locale: 'en',
......@@ -49,10 +50,7 @@ const i18n = new VueI18n({
silentFallbackWarn: true,
});
import Vuelidate from 'vuelidate';
Vue.use(Vuelidate);
export default Vue.extend({
export default LinkedDataHandler.extend({
i18n,
name: 'FormGenerator',
components: {
......@@ -102,7 +100,8 @@ export default Vue.extend({
},
data() {
return {
quads: [] as any[],
properties: [] as Array<Quad>,
quads: [] as Array<Quad>,
fixedValueIds: [] as any[],
sortedSHACLDefinition: [] as any[],
errorMessages: {} as any,
......@@ -137,9 +136,6 @@ export default Vue.extend({
},
},
methods: {
input() {
this.$emit('input', this.formData);
},
createValidations() {
const me = this;
const validator = {} as any;
......@@ -154,7 +150,7 @@ export default Vue.extend({
return new Promise((resolve, reject) => {
me.timeouts[nodename] = setTimeout(async() => {
// Validate the whole data and check if for the current nodename an error is logged
const report = await me.validatorFunction();
const report = await me.validateMetadata(me.formData, me.quads, me.applicationProfileId);
let resolveValue = true;
if (report.results.some((entry: any) => entry.path.value === nodename)) {
const result = report.results.find((entry: any) => entry.path.value === nodename);
......@@ -171,122 +167,32 @@ export default Vue.extend({
}
this.validations = validator;
},
async validatorFunction() {
const shapes = await this.loadDataset(this.SHACLDefinition, this.mimeType, this.applicationProfileId);
// RDF/JSON => JSON-LD since the loadDataset function doesn't support RDF/JSON
const dataObject = {} as any;
dataObject['@type'] = this.applicationProfileId;
for (const entry of Object.keys(this.formData)) {
for (const metadataEntry of this.formData[entry]) {
if (metadataEntry['value'] !== '') {
if (metadataEntry['type'] === 'uri') {
dataObject[entry] = {
'@id': metadataEntry['value'],
'@type': metadataEntry['datatype']
};
} else {
dataObject[entry] = {
'@value': metadataEntry['value'],
'@type': metadataEntry['datatype']
};
}
}
}
}
const data = await this.loadDataset(JSON.stringify(dataObject), 'application/ld+json', null);
const validator = new SHACLValidator(shapes);
const report = validator.validate(data);
this.$emit('isValid', report.conforms);
return report;
},
async getQuads (data: string, mimeType: string, baseUri: string | null) : Promise<any> {
const input = new Readable({
read: () => {
input.push(data);
input.push(null);
}
});
const quads = [] as any[];
return new Promise((resolve) => {
rdfParser.parse(input, { contentType: mimeType, baseIRI: baseUri })
.on('data', (quad: any) => {
quads.push(quad);
})
.on('end', () => resolve(quads));
});
},
async loadDataset (data: string, mimeType: string, baseUri: string | null) {
const quads = await this.getQuads(data, mimeType, baseUri);
const dataSet = factory.dataset();
for (const quad of quads) {
dataSet.add(quad);
}
return dataSet;
},
checkField(data: any, nodename: string, property: string = 'value') {
return FieldReader.isValueAssignedToKey(data, nodename, property);
},
fieldDefinition(formElement: any) {
if (this.checkField(formElement, 'http://www.w3.org/ns/shacl#order')) {
if (
this.checkField(formElement, 'http://www.w3.org/ns/shacl#datatype')
) {
let datatype = FieldReader.getObject(formElement, 'http://www.w3.org/ns/shacl#datatype');
if (datatype['value'] === 'http://www.w3.org/2001/XMLSchema#string') {
if (
this.checkField(
formElement,
'http://datashapes.org/dash#singleLine'
) &&
FieldReader.getObject(formElement, 'http://datashapes.org/dash#singleLine')[
'value'
] === 'false'
) {
return 'InputTextArea';
} else {
return 'InputTextField';
}
} else if (
datatype['value'] === 'http://www.w3.org/2001/XMLSchema#date'
) {
return 'InputDatePicker';
}
else if (
datatype['value'] === 'http://www.w3.org/2001/XMLSchema#boolean'
) {
return 'InputBooleanCombobox';
}
} else {
if (
this.checkField(formElement, 'http://www.w3.org/ns/shacl#class')
) {
return 'InputCombobox';
}
}
}
return null;
},
async retrieveSHACLDefinition() : Promise<any> {
if (this.SHACLDefinition !== null && this.SHACLDefinition !== '') {
return this.getQuads(this.SHACLDefinition, this.mimeType, this.applicationProfileId);
}
return [];
},
async handleApplicationProfiles() {
this.quads = await this.retrieveSHACLDefinition();
this.quads = await this.retrieveQuads(this.SHACLDefinition, this.mimeType, this.applicationProfileId);
this.properties = this.retrieveProperties(this.SHACLDefinition, this.quads, this.applicationProfileId);
const unmappedSubjects = this.retrievePropertyOrder(this.properties);
this.setPropertySettings(this.properties);
const unmappedSubjects = {} as any;
this.setSHACLDefinition(unmappedSubjects);
this.createValidations();
await this.validateMetadata(this.formData, this.quads, this.applicationProfileId);
},
input() {
this.$emit('input', this.formData);
},
setPropertySettings(properties: Array<Quad>) {
const propertySubjects = this.getPropertySubjects(properties);
this.errorMessages = {} as any;
this.timeouts = {} as any;
for (let i = 0; i < this.quads.length; i++) {
if (this.quads[i].predicate.value === 'http://www.w3.org/ns/shacl#order') {
unmappedSubjects[this.quads[i].object.value] = this.quads.filter((entry) =>
entry.subject.value === this.quads[i].subject.value);
this.errorMessages[this.quads[i].subject.value] = '';
this.timeouts[this.quads[i].subject.value] = null;
}
for (const propertySubject of propertySubjects) {
this.errorMessages[propertySubject] = '';
this.timeouts[propertySubject] = null;
}
},
setSHACLDefinition(unmappedSubjects: any) {
this.fixedValueIds = [];
this.sortedSHACLDefinition = [];
let keys = Object.keys(unmappedSubjects).sort((a, b) => parseInt(a) - parseInt(b));
......@@ -299,10 +205,47 @@ export default Vue.extend({
this.$set(this.formData, nodeName, [{ value: '' }]);
}
}
this.createValidations();
await this.validatorFunction();
},
checkField(data: any, nodename: string, property: string = 'value') {
return FieldReader.isValueAssignedToKey(data, nodename, property);
},
fieldDefinition(formElement: any) {
if (
this.checkField(formElement, 'http://www.w3.org/ns/shacl#datatype')
) {
let datatype = FieldReader.getObject(formElement, 'http://www.w3.org/ns/shacl#datatype');
if (datatype['value'] === 'http://www.w3.org/2001/XMLSchema#string') {
if (
this.checkField(
formElement,
'http://datashapes.org/dash#singleLine'
) &&
FieldReader.getObject(formElement, 'http://datashapes.org/dash#singleLine')[
'value'
] === 'false'
) {
return 'InputTextArea';
} else {
return 'InputTextField';
}
} else if (
datatype['value'] === 'http://www.w3.org/2001/XMLSchema#date'
) {
return 'InputDatePicker';
}
else if (
datatype['value'] === 'http://www.w3.org/2001/XMLSchema#boolean'
) {
return 'InputBooleanCombobox';
}
} else {
if (
this.checkField(formElement, 'http://www.w3.org/ns/shacl#class')
) {
return 'InputCombobox';
}
}
return null;
},
},
});
......
import Vue from 'vue'
import factory from 'rdf-ext';
import SHACLValidator from 'rdf-validate-shacl';
import rdfParser from 'rdf-parse';
import { Readable } from 'stream';
import { Quad } from 'rdf-js';
export default Vue.extend({
data() {
return {
shapes: [] as string[],
};
},
methods: {
async validateMetadata(formData: any, quads: Array<Quad>, applicationProfileId: string) {
// RDF/JSON => JSON-LD since the loadDataset function doesn't support RDF/JSON
const combinedDataObject = [];
const dataObject = {} as any;
dataObject['@type'] = applicationProfileId;
for (const entry of Object.keys(formData)) {
for (const metadataEntry of formData[entry]) {
if (metadataEntry['value'] !== '') {
if (metadataEntry['type'] === 'uri') {
dataObject[entry] = {
'@id': metadataEntry['value'],
'@type': metadataEntry['datatype']
};
} else {
dataObject[entry] = {
'@value': metadataEntry['value'],
'@type': metadataEntry['datatype']
};
}
}
}
}
combinedDataObject.push(dataObject);
// rdfs:subClassOf definitions have to be included for the validation to work with inheritance
const subClasses = quads.filter((quad) => quad.predicate.value === 'http://www.w3.org/2000/01/rdf-schema#subClassOf');
for (const subClassQuad of subClasses) {
combinedDataObject.push({
'@id': subClassQuad.subject.value,
'http://www.w3.org/2000/01/rdf-schema#subClassOf': [
{
'@id': subClassQuad.object.value,
},
],
});
}
// The non-referenced shapes have also to be represented
const nonReferenced = subClasses.filter((quad) =>
!subClasses.some((subQuad) => subQuad.subject.value === quad.object.value));
for (const nonReference of nonReferenced) {
combinedDataObject.push({
'@id': nonReference.object.value,
});
}
const data = await this.loadDataset(JSON.stringify(combinedDataObject), 'application/ld+json', null);
const validator = new SHACLValidator(quads);
const report = validator.validate(data);
this.$emit('isValid', report.conforms);
return report;
},
async getQuads (data: string, mimeType: string, baseUri: string | null) : Promise<Array<Quad>> {
const input = new Readable({
read: () => {
input.push(data);
input.push(null);
}
});
const quads = [] as Array<Quad>;
return new Promise((resolve) => {
rdfParser.parse(input, { contentType: mimeType, baseIRI: baseUri })
.on('data', (quad: Quad) => {
quads.push(quad);
})
.on('end', () => resolve(quads));
});
},
async loadDataset (data: string, mimeType: string, baseUri: string | null) {
const quads = await this.getQuads(data, mimeType, baseUri);
const dataSet = factory.dataset();
for (const quad of quads) {
dataSet.add(quad);
}
return dataSet;
},
fillShapesList(quads: Array<Quad>, currentShape: string) {
this.shapes = [];
this.fillShapesListRecursive(quads, this.shapes, currentShape);
},
fillShapesListRecursive(quads: Array<Quad>, shapes: Array<string>, currentShape: string) {
if (!shapes.includes(currentShape)) {
shapes.push(currentShape);
const subClasses = quads.filter((quad) =>
quad.predicate.value === 'http://www.w3.org/2000/01/rdf-schema#subClassOf'
&& quad.subject.value === currentShape)
.map((quad) => quad.object.value);
for (const subClass of subClasses) {
this.fillShapesListRecursive(quads, shapes, subClass);
}
}
},
async retrieveQuads(SHACLDefinition: string, mimeType: string, applicationProfileId: string) : Promise<Array<Quad>> {
if (SHACLDefinition !== '') {
return this.getQuads(SHACLDefinition, mimeType, applicationProfileId);
}
return [];
},
retrieveProperties(SHACLDefinition: string, quads: Array<Quad>, applicationProfileId: string) : Array<Quad> {
if (SHACLDefinition !== '') {
this.fillShapesList(quads, applicationProfileId);
const propertySets = quads.filter((quad) =>
quad.predicate.value === 'http://www.w3.org/ns/shacl#property'
&& this.shapes.includes(quad.subject.value)
).map((quad) => quad.object.value);
return quads.filter((quad) => propertySets.includes(quad.subject.value));
}
return [];
},
retrievePropertyOrder(properties: Array<Quad>) {
const propertySubjects = this.getPropertySubjects(properties);
let highestOrder = Math.max(...properties.filter((property) =>
property.predicate.value === 'http://www.w3.org/ns/shacl#order'
).map((property) => Number(property.object.value)));
if (highestOrder === -Infinity) {
highestOrder = 0;
}
const unmappedSubjects = {} as any;
let manualOrder = highestOrder + 1;
for (const propertySubject of propertySubjects) {
let currentOrder = 0;
const orderDefinitions = properties.filter((property) =>
property.subject.value === propertySubject
&& property.predicate.value === 'http://www.w3.org/ns/shacl#order');