1use std::collections::{HashMap, HashSet, VecDeque};
4
5use oxigraph::model::*;
6
7use crate::graph::{RDF, RDFS, iri, with_union};
8
9#[cfg(test)]
10#[path = "ontology_tests.rs"]
11mod tests;
12
13const OWL: &str = "http://www.w3.org/2002/07/owl#";
14const RDFS_CLASSES: &[&str] = &[
15 "Class",
16 "Container",
17 "ContainerMembershipProperty",
18 "Datatype",
19 "Literal",
20 "Resource",
21];
22const RDFS_PROPERTIES: &[&str] = &[
23 "comment",
24 "domain",
25 "isDefinedBy",
26 "label",
27 "member",
28 "range",
29 "seeAlso",
30 "subClassOf",
31 "subPropertyOf",
32];
33const OWL_CLASSES: &[&str] = &[
34 "AllDifferent",
35 "AllDisjointClasses",
36 "AllDisjointProperties",
37 "Annotation",
38 "AnnotationProperty",
39 "AsymmetricProperty",
40 "Axiom",
41 "Class",
42 "DataRange",
43 "DatatypeProperty",
44 "DeprecatedClass",
45 "DeprecatedProperty",
46 "FunctionalProperty",
47 "InverseFunctionalProperty",
48 "IrreflexiveProperty",
49 "NamedIndividual",
50 "NegativePropertyAssertion",
51 "ObjectProperty",
52 "Ontology",
53 "OntologyProperty",
54 "ReflexiveProperty",
55 "Restriction",
56 "SymmetricProperty",
57 "TransitiveProperty",
58];
59const OWL_PROPERTIES: &[&str] = &[
60 "allValuesFrom",
61 "annotatedProperty",
62 "annotatedSource",
63 "annotatedTarget",
64 "assertionProperty",
65 "cardinality",
66 "complementOf",
67 "datatypeComplementOf",
68 "differentFrom",
69 "disjointUnionOf",
70 "disjointWith",
71 "distinctMembers",
72 "equivalentClass",
73 "equivalentProperty",
74 "hasKey",
75 "hasSelf",
76 "hasValue",
77 "intersectionOf",
78 "inverseOf",
79 "maxCardinality",
80 "maxQualifiedCardinality",
81 "members",
82 "minCardinality",
83 "minQualifiedCardinality",
84 "onClass",
85 "onDataRange",
86 "onDatatype",
87 "onProperties",
88 "onProperty",
89 "oneOf",
90 "propertyChainAxiom",
91 "propertyDisjointWith",
92 "qualifiedCardinality",
93 "sameAs",
94 "someValuesFrom",
95 "sourceIndividual",
96 "targetIndividual",
97 "targetValue",
98 "unionOf",
99 "withRestrictions",
100 "backwardCompatibleWith",
101 "deprecated",
102 "incompatibleWith",
103 "priorVersion",
104 "versionInfo",
105 "bottomDataProperty",
106 "topDataProperty",
107 "bottomObjectProperty",
108 "topObjectProperty",
109 "imports",
110 "versionIRI",
111];
112
113fn recognized(node: NamedNodeRef<'_>, rdfs: &[&str], owl: &[&str]) -> bool {
114 node.as_str()
115 .strip_prefix(RDFS)
116 .is_some_and(|s| rdfs.contains(&s))
117 || node
118 .as_str()
119 .strip_prefix(OWL)
120 .is_some_and(|s| owl.contains(&s))
121}
122
123#[derive(Default)]
124struct Copies {
125 blanks: HashMap<BlankNode, BlankNode>,
126 pending: VecDeque<(NamedOrBlankNode, bool)>,
127}
128
129impl Copies {
130 fn subject(&mut self, node: NamedOrBlankNodeRef<'_>) -> NamedOrBlankNode {
131 match node {
132 NamedOrBlankNodeRef::NamedNode(n) => n.into_owned().into(),
133 NamedOrBlankNodeRef::BlankNode(n) => {
134 self.pending.push_back((n.into_owned().into(), false));
135 self.blanks
136 .entry(n.into_owned())
137 .or_default()
138 .clone()
139 .into()
140 }
141 }
142 }
143
144 fn object(&mut self, node: TermRef<'_>, deep: bool) -> Term {
145 match node {
146 TermRef::BlankNode(n) => Term::from(self.subject(n.into())),
147 TermRef::NamedNode(n) if deep => {
148 self.pending.push_back((n.into_owned().into(), true));
149 n.into_owned().into()
150 }
151 _ => node.into_owned(),
152 }
153 }
154}
155
156pub fn inoculate(data: &mut Dataset, ontology: &Dataset) {
157 let ontology = with_union(ontology);
158 let mut copies = Copies::default();
159 let individuals: HashSet<_> = ontology
160 .quads_for_pattern(
161 None,
162 Some(iri(RDF, "type").as_ref()),
163 Some(iri(OWL, "NamedIndividual").as_ref().into()),
164 Some(GraphNameRef::DefaultGraph),
165 )
166 .map(|q| q.subject.into_owned())
167 .collect();
168 for quad in ontology.iter().filter(|q| q.graph_name.is_default_graph()) {
169 let schema_type = quad.predicate == iri(RDF, "type").as_ref()
170 && matches!(quad.object, TermRef::NamedNode(n) if recognized(n, RDFS_CLASSES, OWL_CLASSES));
171 let incoming = match quad.object {
172 TermRef::NamedNode(n) => individuals.contains(&n.into_owned().into()),
173 TermRef::BlankNode(n) => individuals.contains(&n.into_owned().into()),
174 _ => false,
175 };
176 if schema_type || recognized(quad.predicate, RDFS_PROPERTIES, OWL_PROPERTIES) || incoming {
177 let subject = copies.subject(quad.subject);
178 let object = copies.object(quad.object, false);
179 data.insert(&Quad::new(
180 subject,
181 quad.predicate,
182 object,
183 GraphName::DefaultGraph,
184 ));
185 }
186 }
187 copies
188 .pending
189 .extend(individuals.into_iter().map(|n| (n, true)));
190 let mut visited = HashSet::new();
191 while let Some((node, deep)) = copies.pending.pop_front() {
192 if !visited.insert((node.clone(), deep)) {
193 continue;
194 }
195 let subject = copies.subject(node.as_ref());
196 for quad in ontology.quads_for_pattern(
197 Some(node.as_ref()),
198 None,
199 None,
200 Some(GraphNameRef::DefaultGraph),
201 ) {
202 let object = copies.object(quad.object, deep);
203 data.insert(&Quad::new(
204 subject.clone(),
205 quad.predicate,
206 object,
207 GraphName::DefaultGraph,
208 ));
209 }
210 }
211}