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          12x                                     5538x   5538x                 10x   10x                                                       25x           25x         25x 25x             148x 147x   1x         74208x                     2171x   2171x       5591x       5591x   5591x       5591x   5591x 5591x   5591x 2171x   3420x   3420x                         11182x 11182x             74x 74x     74x 74x 74x     74x 399x     74x   74x 472056x     74x   74x   74x                                
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;
}