All files / rdf/reasoners reasoner.ts

88.63% Statements 39/44
65% Branches 13/20
100% Functions 12/12
88.63% Lines 39/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 191 192 193 194 195 196 197 198 199 200          13x                                                   30840x   30840x                 48128x   48128x                                                           47x           47x         47x 47x             244x 244x             632527x                     4954x   4954x       12790x       12790x   12790x       12790x   12790x 12790x   12790x 4954x   7836x   7836x                         25580x 25580x             122x 122x     122x 122x 122x       122x 1961x     122x   122x 543752x     122x   122x   122x                                
import * as rdfjs from "@rdfjs/types";
import { RdfStore } from 'rdf-stores';
import { rdf, RDF } from '../../ontologies';
import { RdfDataFactory } from '../rdf-data-factory';
 
const { namedNode, blankNode } = RdfDataFactory;
 
/**
 * 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;
 
    /**
     * Check if a given URI is the URI of an inference graph generated by this handler.
     * @param uri A URI.
     * @returns `true` if the URI is an inference graph URI, `false` otherwise.
     */
    isInferenceGraphUri(uri: string | rdfjs.Quad_Graph): boolean;
}
 
/**
 * 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.
     * The source graph is iterated live while the inferred triples are added, so an
     * implementation must only ever write to 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 E{
            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.
        // The match is a live iterator over the store, so snapshot it before deleting.
        for (const quad of Array.from(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;
}