All files / rdf/reasoners reasoner.ts

90.9% Statements 40/44
70% Branches 14/20
100% Functions 12/12
90.9% Lines 40/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190          13x                                     5537x   5537x                 10x   10x                                                       24x           24x         24x 24x             146x 145x   1x         73160x                     1929x   1929x       4965x       4965x   4965x       4965x   4965x 4965x   4965x 1929x   3036x   3036x                         9930x 9930x             73x 73x     73x 73x 73x     73x 399x     73x   73x 466394x     73x   73x   73x                                
import * as rdfjs from "@rdfjs/types";
import { RdfStore } from 'rdf-stores';
import { rdf, RDF } from '../../ontologies';
import { dataFactory } from '../data-factory';
 
const { namedNode, blankNode } = dataFactory;
 
/**
 * A handler to generate graph URIs from any given URI.
 */
export interface GraphUriGenerator {
    /**
     * Generate a graph URI from a given URI.
     * @param uri A URI.
     */
    getGraphUri(uri: string | rdfjs.Quad_Graph): string;
}
 
/**
 * Default implementation of the InferenceGraphHandler interface. It generates URIs 
 * for inference graphs using a Mentor specific URN scheme.
 */
export class DefaultInferenceGraphHandler implements GraphUriGenerator {
    getGraphUri(uri: string | rdfjs.Quad_Graph): string {
        const u = typeof uri === "string" ? uri : uri.value;
 
        return `urn:mentor:inference:${u}`;
    }
 
    /**
     * Check if a given URI is an inference graph URI.
     * @param uri A URI.
     * @returns `true` if the URI is an inference graph URI, `false` otherwise.
     */
    isInferenceGraphUri(uri: string | rdfjs.Quad_Graph): boolean {
        const u = typeof uri === "string" ? uri : uri.value;
 
        return u.startsWith("urn:mentor:inference:");
    }
}
 
/**
 * An interface for reasoners that operate on existing graphs in the RDF store and
 * materialise the inferred triples in an inference graph.
 */
export interface Reasoner {
 
    /**
     * A handler to generate inference graph URIs from any given graph URI if no explicit target graph is provided.
     */
    targetUriGenerator: GraphUriGenerator;
 
    /**
     * Apply inference on the source graph and store the inferred triples in the target graph.
     * @param store The store to be inferenced.
     * @param sourceGraph The source graph where to find the triples to be inferenced.
     * @param targetGraph The optional target graph where to store the inferred triples. If none is provided, the graph from getGraphUri will be used.
     */
    expand(store: rdfjs.DatasetCore, sourceGraph: string | rdfjs.Quad_Graph, targetGraph?: string | rdfjs.Quad_Graph): rdfjs.DatasetCore;
}
 
/**
 * A base class for reasoners that expand graphs with inferred triples.
 */
export abstract class ReasonerBase implements Reasoner {
    protected store: rdfjs.DatasetCore = RdfStore.createDefault().asDataset();
 
    public sourceGraph?: rdfjs.Quad_Graph;
 
    public targetGraph?: rdfjs.Quad_Graph;
 
    public readonly errors: { message: string, quad: rdfjs.Quad }[] = [];
 
    readonly targetUriGenerator: GraphUriGenerator;
 
    constructor(targetUriGenerator: GraphUriGenerator) {
        if (targetUriGenerator) {
            this.targetUriGenerator = targetUriGenerator;
        } else E{
            throw new Error(`Invalid value for targetUriGenerator: ${targetUriGenerator}`);
        }
    }
 
    protected getGraphNode(graph: string | rdfjs.Quad_Graph): rdfjs.Quad_Graph {
        if (typeof graph === "string") {
            return namedNode(graph);
        } else {
            return graph;
        }
    }
 
    protected isW3CNode(term: rdfjs.Quad_Subject | rdfjs.Quad_Object): boolean {
        return term.value.startsWith("http://www.w3.org");
    }
 
    /**
     * Get the URIs of ordered list members in the store.
     * @param graphUris Optional graph URI or array of graph URIs to query.
     * @param listUri URI of the list to get the items from.
     * @returns An array of URIs of the items in the list.
     */
    getListItems(listUri: string): rdfjs.Quad_Object[] {
        // To do: Fix #10
        const list = listUri.includes(':') ? namedNode(listUri) : blankNode(listUri);
 
        return this._getListItems(list);
    }
 
    private _getListItems(subject: rdfjs.Quad_Subject): rdfjs.Quad_Object[] {
        Iif (!this.sourceGraph) {
            return [];
        }
 
        let first = Array.from(this.match(this.sourceGraph, subject, rdf.first, null));
 
        Iif (!first.length) {
            return [];
        }
 
        const rest = Array.from(this.match(this.sourceGraph, subject, rdf.rest, null));
 
        const firstItem = first[0].object;
        const restList = rest[0]?.object;
 
        if (restList.value === RDF.nil) {
            return [firstItem];
        } else {
            const restItems = this._getListItems(restList as rdfjs.Quad_Subject);
 
            return [firstItem, ...restItems];
        }
    }
 
    /**
     * Query the store for triples matching the given pattern supporting multiple graphs.
     * @param graphUris Optional graph URI or array of graph URIs to query.
     * @param subject A subject URI or null to match any subject.
     * @param predicate A predicate URI or null to match any predicate.
     * @param object An object URI or null to match any object.
     * @todo Refactor and merge with the same method in the Mentor RDF Store class.
     */
    *match(graph: rdfjs.Quad_Graph, subject: rdfjs.Quad_Subject | null, predicate: rdfjs.Quad_Predicate | null, object: rdfjs.Quad_Object | null) {
        if (graph !== undefined) {
            yield* this.store.match(subject, predicate, object, graph);
        } else E{
            yield* this.store.match(subject, predicate, object);
        }
    }
 
    public expand(store: rdfjs.DatasetCore, sourceGraph: string | rdfjs.Quad_Graph, targetGraph?: string | rdfjs.Quad_Graph): rdfjs.DatasetCore {
        Eif (!targetGraph) {
            targetGraph = this.targetUriGenerator.getGraphUri(sourceGraph);
        }
 
        this.store = store;
        this.sourceGraph = this.getGraphNode(sourceGraph);
        this.targetGraph = this.getGraphNode(targetGraph);
 
        // Ensure the target graph is empty so this function is idempotent and consistent.
        for (let quad of store.match(null, null, null, this.targetGraph)) {
            store.delete(quad);
        }
 
        this.beforeInference();
 
        for (let quad of store.match(null, null, null, this.sourceGraph)) {
            this.applyInference(quad)
        }
 
        this.afterInference();
 
        this.resetState();
 
        return store;
    }
 
    protected beforeInference() { }
 
    protected afterInference() { }
 
    protected resetState() { }
 
    abstract applyInference(quad: rdfjs.Quad): void;
 
    /**
     * Indicate if a given resource should *not* be inferred to be a owl:NamedIndividual.
     * @param id A resource URI or blank node identifier.
     */
    protected abstract isClass(id: string): boolean;
}