extract-cbd-shape
Version:
Extract an entity based on CBD and a SHACL shape
140 lines (120 loc) • 3.87 kB
text/typescript
import {Term} from "@rdfjs/types";
import {Path,} from "./Path";
import {CbdExtracted} from "./CBDShapeExtractor";
//TODO: split this file up between Shape functionality and SHACL to our Shape class conversion steps. Also introduce a ShEx to Shape Template
export class NodeLink {
public pathPattern: Path;
public link: Term;
constructor(pathPattern: Path, link: Term) {
this.pathPattern = pathPattern;
this.link = link;
}
}
export class ShapeError {
type: "and" | "or";
errors: (ShapeError | Path)[] = [];
constructor(type: "and" | "or", errors: (ShapeError | Path)[] = []) {
this.type = type;
this.errors = errors;
}
toString(): string {
if (this.errors.length === 1) {
return this.errors[0].toString();
} else {
const sep = this.type == "and" ? " && " : " || ";
return "(" + this.errors.map((x) => x.toString()).join(sep) + ")";
}
}
}
export class ShapeTemplate {
closed: boolean;
nodeLinks: Array<NodeLink>;
requiredPaths: Array<Path>;
optionalPaths: Array<Path>;
atLeastOneLists: Array<Array<ShapeTemplate>>;
label?: string;
constructor() {
//All properties will be added, but if a required property is not available, then we need to further look it up
this.requiredPaths = [];
//If there’s a nodelink through one of the properties, I want to know what other shape to look up in the shapes graph from there
this.nodeLinks = [];
this.atLeastOneLists = [];
this.optionalPaths = [];
this.closed = false; //default value
}
fillPathsAndLinks(extraPaths: Array<Path>, extraNodeLinks: Array<NodeLink>) {
for (let list of this.atLeastOneLists) {
for (let item of list) {
extraPaths.push(...item.requiredPaths);
extraPaths.push(...item.optionalPaths);
// extraPaths.push(...item.nodeLinks.map((x) => x.pathPattern));
extraNodeLinks.push(...item.nodeLinks);
item.fillPathsAndLinks(extraPaths, extraNodeLinks);
}
}
}
private invalidAtLeastOneLists(
extract: CbdExtracted,
): ShapeError | undefined {
const out = new ShapeError("and");
for (let list of this.atLeastOneLists) {
const sub = new ShapeError("or");
let atLeastOne = false;
for (let item of list) {
const error = item.requiredAreNotPresent(extract);
if (error) {
sub.errors.push(error);
} else {
atLeastOne = true;
break;
}
}
if (!atLeastOne) {
out.errors.push(sub);
}
}
if (out.errors.length > 0) {
return out;
}
return;
}
private requiredPathsAreNotPresent(
extract: CbdExtracted,
): ShapeError | undefined {
const errors = this.requiredPaths.filter((path) => !path.found(extract));
if (errors.length > 0) {
return new ShapeError("and", errors);
} else {
return;
}
}
requiredAreNotPresent(extract: CbdExtracted): ShapeError | undefined {
const required = this.requiredPathsAreNotPresent(extract);
const atLeastOne = this.invalidAtLeastOneLists(extract);
if (required && atLeastOne) {
return new ShapeError("and", [...required.errors, ...atLeastOne.errors]);
}
if (required) return required;
if (atLeastOne) return atLeastOne;
}
}
export class RDFMap<T> {
private namedNodes: Map<String, T> = new Map();
private blankNodes: Map<String, T> = new Map();
set(node: Term, item: T) {
if (node.termType === "NamedNode") {
this.namedNodes.set(node.value, item);
}
if (node.termType === "BlankNode") {
this.blankNodes.set(node.value, item);
}
}
get(node: Term): T | undefined {
if (node.termType === "NamedNode") {
return this.namedNodes.get(node.value);
}
if (node.termType === "BlankNode") {
return this.blankNodes.get(node.value);
}
}
}