All files / rdf/reasoners skos-reasoner.ts

100% Statements 61/61
100% Branches 29/29
100% Functions 11/11
100% Lines 61/61

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          13x           47x   47x   47x           47x     47x       543752x 543752x                   122x   122x 13455x 13254x                 122x   122x 122x 122x 122x       15666x   15666x       15867x   15867x       17982x           543752x 543752x 543752x   543752x 231105x     312647x   72348x   13286x 59062x   22x   72348x         13455x 13455x 13455x     1199x 1199x 1199x     1213x 1213x 1213x           9171x   9171x       8x     8x       543752x 543752x 543752x   543752x 231105x     312647x   72348x   204x 72144x   6x   72348x     9171x 9171x     8x 8x        
import * as rdfjs from "@rdfjs/types";
import { rdf, skos } from "../../ontologies";
import { DefaultInferenceGraphHandler, GraphUriGenerator, ReasonerBase } from "./reasoner";
import { RdfDataFactory } from "../rdf-data-factory";
 
const { quad } = RdfDataFactory
 
/**
 * A simple SKOS reasoner that expands the graph with inferred triples.
 */
export class SkosReasoner extends ReasonerBase {
    protected concepts: Set<string> = new Set();
 
    protected conceptSchemes: Set<string> = new Set();
 
    protected collections: Set<string> = new Set();
 
    /**
     * Subjects of `skos:inScheme` triples, keyed by their value. Whether they are concepts can only
     * be decided once all triples of the graph were processed, see {@link afterInference}.
     */
    private readonly _inSchemeSubjects: Map<string, rdfjs.Quad_Subject> = new Map();
 
    constructor(targetUriGenerator: GraphUriGenerator = new DefaultInferenceGraphHandler()) {
        super(targetUriGenerator);
    }
 
    applyInference(quad: rdfjs.Quad): void {
        this.inferConceptAxioms(quad);
        this.inferCollectionAxioms(quad);
    }
 
    /**
     * Infers the subjects of `skos:inScheme` triples to be concepts, unless they are collections.
     * `skos:inScheme` has no domain, and `skos:Collection` is disjoint with `skos:Concept`. The
     * type of a collection may be processed after its `skos:inScheme` triple, so this is done after
     * all triples of the graph were processed.
     */
    protected afterInference(): void {
        super.afterInference();
 
        for (let [id, subject] of this._inSchemeSubjects) {
            if (!this.collections.has(id)) {
                this.assertConcept(subject);
            }
        }
    }
 
    /**
     * Clears the state that was collected for the graph, so it does not affect the next graph.
     */
    protected resetState(): void {
        super.resetState();
 
        this.concepts.clear();
        this.conceptSchemes.clear();
        this.collections.clear();
        this._inSchemeSubjects.clear();
    }
 
    protected assertConcept(subject: rdfjs.Quad_Subject) {
        this.store.add(quad(subject, rdf.type, skos.Concept, this.targetGraph));
 
        this.concepts.add(subject.value);
    }
 
    protected assertConceptScheme(subject: rdfjs.Quad_Subject) {
        this.store.add(quad(subject, rdf.type, skos.ConceptScheme, this.targetGraph));
 
        this.conceptSchemes.add(subject.value);
    }
 
    protected isClass(id: string): boolean {
        return this.concepts.has(id) ||
            this.conceptSchemes.has(id) ||
            this.collections.has(id);
    }
 
    inferConceptAxioms(quad: rdfjs.Quad) {
        let s = quad.subject;
        let p = quad.predicate;
        let o = quad.object.termType != "Literal" ? quad.object : undefined;
 
        if (!o) {
            return;
        }
 
        switch (p.value) {
            case rdf.type.value: {
                if (o.equals(skos.Concept)) {
                    // No need to infer the type, as it is already asserted.
                    this.concepts.add(s.value);
                } else if (o.equals(skos.ConceptScheme)) {
                    // No need to infer the type, as it is already asserted.
                    this.conceptSchemes.add(s.value);
                }
                return;
            }
            case skos.inScheme.value: {
                // The subject may be a collection, so it is only inferred to be a concept after
                // all triples were processed.
                this._inSchemeSubjects.set(s.value, s);
                this.assertConceptScheme(o);
                return;
            }
            case skos.topConceptOf.value: {
                this.assertConcept(s);
                this.assertConceptScheme(o);
                return;
            }
            case skos.hasTopConcept.value: {
                this.assertConcept(o);
                this.assertConceptScheme(s);
                return;
            }
        }
    }
 
    protected assertCollection(subject: rdfjs.Quad_Subject) {
        this.store.add(quad(subject, rdf.type, skos.Collection, this.targetGraph));
 
        this.collections.add(subject.value);
    }
 
    protected assertOrderedCollection(subject: rdfjs.Quad_Subject) {
        this.store.add(quad(subject, rdf.type, skos.OrderedCollection, this.targetGraph));
 
        // We do not make a distinction between collections and ordered collections here.
        this.collections.add(subject.value);
    }
 
    inferCollectionAxioms(quad: rdfjs.Quad) {
        let s = quad.subject;
        let p = quad.predicate;
        let o = quad.object.termType != "Literal" ? quad.object : undefined;
 
        if (!o) {
            return;
        }
 
        switch (p.value) {
            case rdf.type.value: {
                if (o.equals(skos.Collection)) {
                    // No need to infer the type, as it is already asserted.
                    this.collections.add(s.value);
                } else if (o.equals(skos.OrderedCollection)) {
                    // No need to infer the type, as it is already asserted.
                    this.collections.add(s.value);
                }
                return;
            }
            case skos.member.value: {
                this.assertCollection(s);
                return;
            }
            case skos.memberList.value: {
                this.assertOrderedCollection(s);
                return;
            }
        }
    }
}