Select Git revision
LinkedDataHandler.ts
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
LinkedDataHandler.ts 4.71 KiB
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 { prefixes } from '@zazuko/rdf-vocabularies';
import type { Dataset, Quad, Quad_Object, Quad_Subject } from 'rdf-js';
export default Vue.extend({
data() {
return {
errorMessages: {} as { [nodeName: string]: Quad_Object[] },
timeOutId: 0,
validationDebounce: 500,
};
},
methods: {
getMetadataSubject(metadata: Dataset): Quad_Subject {
let subjectNode: Quad_Subject = factory.blankNode();
const rdfType = factory.namedNode(prefixes.rdf + 'type');
if (metadata.size) {
for (const entry of metadata.match(undefined, rdfType)) {
subjectNode = entry.subject;
break;
}
}
return subjectNode;
},
triggerMetadataValidation(metadata: Dataset, dataset: Dataset): void {
if (this.timeOutId) {
clearTimeout(this.timeOutId);
}
this.timeOutId = setTimeout(async () => {
const combinedMetadata = factory.dataset(Array.from(metadata));
// rdfs:subClassOf definitions have to be included for the validation to work with inheritance
const subClasses = dataset.match(
null,
factory.namedNode(prefixes.rdfs + 'subClassOf')
);
for (const subClassQuad of subClasses) {
combinedMetadata.add(subClassQuad);
}
// Describe that the selected value for a class relationship is a value of that class to satisfy validation
const pathRelations = dataset.match(
null,
factory.namedNode(prefixes.sh + 'path')
);
const classRelations = Array.from(pathRelations).map((pathRelation) => {
return {
path: pathRelation.object,
classRelations: dataset.match(
pathRelation.subject,
factory.namedNode(prefixes.sh + 'class')
),
};
});
for (const classRelation of classRelations.filter(
(entry) => entry.classRelations && entry.classRelations.size
)) {
const metadataRelations = metadata.match(
undefined,
classRelation.path
);
if (metadataRelations.size) {
for (const metadataRelation of metadataRelations) {
for (const classEntry of classRelation.classRelations) {
combinedMetadata.add(
factory.quad(
metadataRelation.object as Quad_Subject,
factory.namedNode(prefixes.rdf + 'type'),
classEntry.object
)
);
}
}
}
}
const validator = new SHACLValidator(dataset);
const report = validator.validate(combinedMetadata);
this.$emit('isValid', report);
this.errorMessages = {};
for (const result of report.results) {
if (result.path) {
const path = result.path;
if (
metadata.some(
(entry) =>
entry.predicate.value === path.value &&
entry.object.value !== ''
)
) {
this.errorMessages[path.value] = result.message;
}
}
}
return report;
}, this.validationDebounce) as unknown as number;
},
async getQuads(
data: string,
mimeType: string,
baseUri: string | undefined
): Promise<Dataset> {
const input = new Readable({
read: () => {
input.push(data);
input.push(null);
},
});
const dataset = factory.dataset();
return new Promise((resolve) => {
rdfParser
.parse(input, { contentType: mimeType, baseIRI: baseUri })
.on('data', (quad: Quad) => {
dataset.add(quad);
})
.on('end', () => resolve(dataset as unknown as Dataset));
});
},
async loadDataset(
data: string,
mimeType: string,
baseUri: string | undefined
): Promise<Dataset> {
const quads = await this.getQuads(data, mimeType, baseUri);
const dataSet = factory.dataset();
for (const quad of quads) {
dataSet.add(quad);
}
return dataSet as unknown as Dataset;
},
async retrieveDataset(
shapes: string,
mimeType: string,
selectedShape: string
): Promise<Dataset> {
if (shapes !== '') {
return await this.getQuads(
shapes,
mimeType,
selectedShape ? selectedShape : 'https://purl.org/coscine/ap/'
);
}
return factory.dataset() as unknown as Dataset;
},
},
});