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 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 | 12x 45x 45x 45x 45x 45x 45x 45x 45x 52510x 13537x 38973x 17982x 118x 37470x 118x 3109x 118x 118x 3846x 9075x 4791x 9075x 2231x 3846x 3846x 3846x 3846x 9780x 9780x 705x 9075x 9075x 9075x 5741x 3846x 118x 118x 118x 118x 118x 118x 118x 118x 543698x 543698x 52510x 38973x 52510x 83x 543698x 543698x 95624x 95624x 543698x 543698x 543698x 543698x 231104x 312594x 72338x 5053x 67285x 19659x 47626x 3354x 72338x 25796x 25796x 25448x 348x 75x 273x 19x 19x 254x 5x 5x 249x 9x 9x 25796x 7295x 4592x 7295x 94284x 94284x 543698x 476468x 67230x 543698x 543698x 543698x 543698x 543698x 231104x 312594x 72338x 8829x 72338x 4039x 4039x 4492x 4492x 4492x 2803x 2803x 2803x 4039x 4039x 3846x 3846x 4039x 7295x 7295x 12x 7283x | import * as rdfjs from "@rdfjs/types";
import { owl, rdf, rdfs, skos, sh } from "../../ontologies";
import { SkosReasoner } from "./skos-reasoner";
import { GraphUriGenerator, DefaultInferenceGraphHandler } from "./reasoner";
import { RdfDataFactory } from "../rdf-data-factory";
const { quad } = RdfDataFactory;
/**
* A simple RDFS reasoner that expands the graph with inferred triples.
*/
export class RdfsReasoner extends SkosReasoner {
protected ontologies: Set<string> = new Set();
protected classes: Set<string> = new Set();
protected properties: Set<string> = new Set();
protected individuals: Set<rdfjs.Quad_Subject> = new Set();
/**
* The super properties of each property, as asserted with `rdfs:subPropertyOf`.
*/
private readonly _superProperties: Map<string, { property: rdfjs.Quad_Subject, superProperties: Set<string> }> = new Map();
/**
* The ranges asserted for each property.
*/
private readonly _ranges: Map<string, rdfjs.Quad_Object[]> = new Map();
/**
* The domains asserted for each property.
*/
private readonly _domains: Map<string, rdfjs.Quad_Object[]> = new Map();
constructor(targetUriGenerator: GraphUriGenerator = new DefaultInferenceGraphHandler()) {
super(targetUriGenerator);
}
protected isIgnoredNode(term: rdfjs.Quad_Subject | rdfjs.Quad_Object): boolean {
switch (term.value) {
case skos.Concept.value:
case skos.ConceptScheme.value:
case skos.Collection.value:
case skos.OrderedCollection.value:
case sh.Shape.value:
case sh.NodeShape.value:
case sh.PropertyShape.value:
return true;
default:
return false;
}
}
protected isClass(id: string): boolean {
return super.isClass(id) ||
this.classes.has(id) ||
this.properties.has(id) ||
this.ontologies.has(id);
}
protected afterInference() {
super.afterInference();
// After all axioms have been inferred, add the inferred individuals to the graph.
const individuals = [...this.individuals].filter(x => !this.isClass(x.value));
for (let individual of individuals) {
this.store.add(quad(individual, rdf.type, owl.NamedIndividual, this.targetGraph));
}
this._inferInheritedRangesAndDomains();
}
/**
* Infers the ranges and domains that properties inherit from their super properties
* (OWL 2 RL rules scm-rng2 and scm-dom2), following `rdfs:subPropertyOf` chains.
*/
private _inferInheritedRangesAndDomains() {
for (const { property, superProperties } of this._superProperties.values()) {
for (const superProperty of this._getAncestorProperties(property.value, superProperties)) {
for (const range of this._ranges.get(superProperty) ?? []) {
this.store.add(quad(property, rdfs.range, range, this.targetGraph));
}
for (const domain of this._domains.get(superProperty) ?? []) {
this.store.add(quad(property, rdfs.domain, domain, this.targetGraph));
}
}
}
}
/**
* Gets the transitive super properties of a property, nearest first. Cycles in the
* property hierarchy are followed only once.
* @param property The IRI or blank node id of a property.
* @param superProperties The direct super properties of the property.
* @returns The IRIs of all super properties, excluding the property itself.
*/
private _getAncestorProperties(property: string, superProperties: Set<string>): string[] {
const result: string[] = [];
const visited = new Set<string>([property]);
const queue = [...superProperties];
while (queue.length > 0) {
const current = queue.shift()!;
if (visited.has(current)) {
continue;
}
visited.add(current);
result.push(current);
for (const next of this._superProperties.get(current)?.superProperties ?? []) {
queue.push(next);
}
}
return result;
}
protected override resetState(): void {
super.resetState();
this.ontologies.clear();
this.classes.clear();
this.properties.clear();
this.individuals.clear();
this._superProperties.clear();
this._ranges.clear();
this._domains.clear();
}
override applyInference(quad: rdfjs.Quad) {
super.applyInference(quad);
// Treat all named nodes with rdf:type definitions as potential individuals.
if (quad.subject.termType == "NamedNode" && quad.predicate.equals(rdf.type)) {
// Only consider individuals that are not of a type that is ignored such as skos:Concept.
if (!this.isIgnoredNode(quad.object)) {
this.individuals.add(quad.subject);
}
if (quad.object.value == owl.Ontology.value) {
this.ontologies.add(quad.subject.value);
}
}
this.inferClassAxioms(quad as rdfjs.Quad);
this.inferPropertyAxioms(quad as rdfjs.Quad);
}
protected assertClass(subject: rdfjs.Quad_Subject) {
this.store.add(quad(subject, rdf.type, rdfs.Class, this.targetGraph));
this.classes.add(subject.value);
}
protected inferClassAxioms(q: rdfjs.Quad) {
let s = q.subject;
let p = q.predicate;
let o = q.object.termType != "Literal" ? q.object : undefined;
if (!o) {
return;
}
switch (p.value) {
case rdf.type.value: {
if (o.equals(rdfs.Class)) {
// No need to infer the class type, as it is already asserted.
this.classes.add(s.value);
} else if (o.equals(owl.Class)) {
this.assertClass(s);
} else if (!this.isW3CNode(o)) {
this.assertClass(o);
}
return;
}
case rdfs.subClassOf.value: {
this.assertClass(s);
if (!this.isW3CNode(o)) {
this.assertClass(o);
} else if (o.equals(rdfs.Resource)) {
this.store.add(quad(rdfs.Resource, rdf.type, rdfs.Class, this.targetGraph));
} else if (o.equals(rdfs.Class)) {
this.store.add(quad(rdfs.Class, rdf.type, rdfs.Class, this.targetGraph));
this.store.add(quad(rdfs.Class, rdfs.subClassOf, rdfs.Resource, this.targetGraph));
} else if (o.equals(rdfs.Datatype)) {
this.store.add(quad(rdfs.Datatype, rdf.type, rdfs.Class, this.targetGraph));
this.store.add(quad(rdfs.Datatype, rdfs.subClassOf, rdfs.Class, this.targetGraph));
} else if (o.equals(owl.Class)) {
this.store.add(quad(owl.Class, rdf.type, rdfs.Class, this.targetGraph));
this.store.add(quad(owl.Class, rdfs.subClassOf, rdfs.Class, this.targetGraph));
}
return;
}
case rdfs.range.value:
case rdfs.domain.value: {
if (!this.isW3CNode(o)) {
this.assertClass(o);
}
return;
}
}
}
protected assertProperty(subject: rdfjs.Quad_Subject) {
this.store.add(quad(subject, rdf.type, rdf.Property, this.targetGraph));
this.properties.add(subject.value);
}
/**
* Assert that a predicate denotes a property (RDFS entailment rule rdf1). Predicates of the
* W3C vocabularies are left out, mirroring how classes are inferred from usage.
* @param predicate The predicate of a statement.
*/
protected assertPropertyFromUsage(predicate: rdfjs.Quad_Predicate) {
if (predicate.termType != "NamedNode" || this.isW3CNode(predicate)) {
return;
}
this.assertProperty(predicate);
}
protected inferPropertyAxioms(quad: rdfjs.Quad) {
let s = quad.subject;
let p = quad.predicate;
let o = quad.object.termType != "Literal" ? quad.object : undefined;
// Note: A predicate denotes a property even if the statement has a literal object, so this
// is asserted before the literals are filtered out below. Without it, a data document that
// only *uses* the predicates of an ontology would not show any properties.
this.assertPropertyFromUsage(p);
if (!o) {
return;
}
switch (p.value) {
case rdf.type.value: {
if (o.equals(rdf.Property)) {
// No need to infer the property type, as it is already asserted.
this.properties.add(s.value);
}
return;
}
case rdfs.subPropertyOf.value: {
this._addSuperProperty(s, o);
return;
}
case rdfs.range.value: {
this.assertProperty(s);
this._addToList(this._ranges, s.value, o);
return;
}
case rdfs.domain.value: {
this.assertProperty(s);
this._addToList(this._domains, s.value, o);
return;
}
}
}
/**
* Records that a property is a sub property of another property.
* @param property The sub property.
* @param superProperty The super property.
*/
private _addSuperProperty(property: rdfjs.Quad_Subject, superProperty: rdfjs.Quad_Object) {
let entry = this._superProperties.get(property.value);
if (!entry) {
entry = { property, superProperties: new Set() };
this._superProperties.set(property.value, entry);
}
entry.superProperties.add(superProperty.value);
}
/**
* Adds a value to the list stored under a key.
* @param map The map of lists.
* @param key The key of the list.
* @param value The value to add.
*/
private _addToList(map: Map<string, rdfjs.Quad_Object[]>, key: string, value: rdfjs.Quad_Object) {
const list = map.get(key);
if (list) {
list.push(value);
} else {
map.set(key, [value]);
}
}
} |