Skip to content

Commit ffc841b

Browse files
committed
Support injection of "direct triples" in RDF output.
RDF consumers might want to get so-called “direct triples”, where each mapping record is additionally represented by a single triple of the form ?subject_id ?predicate_id ?object_id . Such triples are not required and are redundant, so we do not emit them by default, but we add an option to request them.
1 parent d28aa00 commit ffc841b

6 files changed

Lines changed: 147 additions & 1 deletion

File tree

cli/src/main/java/org/incenp/obofoundry/sssom/cli/SimpleCLI.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ private void oldEnableSSSOMPyJSON(boolean arg) {
270270
enableSSSOMPyJSON(arg);
271271
}
272272

273+
@Option(names = {"--rdf-direct-triples" },
274+
description = "Inject direct triples when writing in RDF/TTL format.")
275+
boolean rdfDirectTriples;
276+
273277
@Option(names = {"--sorting" }, negatable = true, defaultValue = "true", fallbackValue = "true",
274278
description = "Enable/disable sorting of mappings. This is enabled by default.")
275279
boolean sortMappings;
@@ -701,6 +705,7 @@ private SSSOMWriter getWriter(String filename, String metaFilename, Serialisatio
701705
}
702706
outputOpts.defaultWriteExtraMetadata = ExtraMetadataPolicy.UNDEFINED;
703707
outputOpts.defaultEnableCondensation = false;
708+
((RDFWriter) writer).setInjectDirectTriples(outputOpts.rdfDirectTriples);
704709
break;
705710

706711
case TSV:

cli/src/site/apt/index.apt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,11 @@ sssom-cli set.sssom.tsv:metadata.yaml
285285
option is equivalent to using <<<--output-format json>>>,
286286
<<<--json-short-iris>>>, and <<<--json-write-ld-context>>> combined.
287287

288+
When writing to RDF Turtle, the <<<--rdf-direct-triples>>> option can
289+
be used to enable the production of “direct triples” of the form
290+
<<<?subject_id ?predicate_id ?object_id .>>>, in addition to the
291+
standard reified representation of mapping records.
292+
288293
** 2.5. Slot condensation
289294

290295
By default, when writing the result set, “propagatable slots” are

ext/src/main/java/org/incenp/obofoundry/sssom/rdf/RDFConverter.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public class RDFConverter {
8484
private Version assumedVersion = Version.SSSOM_1_0;
8585
private int bnodeCounter;
8686
private Set<String> excludedSlots;
87+
private boolean directTriples = false;
8788

8889
/**
8990
* Creates a new instance with the default policy for converting non-standard
@@ -117,6 +118,22 @@ public RDFConverter(ExtraMetadataPolicy policy, Version assumedVersion) {
117118
this.assumedVersion = assumedVersion;
118119
}
119120

121+
/**
122+
* Creates a new instance with an explicit policy for converting non-standard
123+
* metadata and optionally inject direct triples for mapping records.
124+
*
125+
* @param policy The non-standard metadata policy.
126+
* @param withDirectTriples If {@code true}, for every mapping record a direct
127+
* triple of the form
128+
* {@code ?subject_id ?predicate_id ?object_id .} will
129+
* be injected, in addition to the standard reified
130+
* form.
131+
*/
132+
public RDFConverter(ExtraMetadataPolicy policy, boolean withDirectTriples) {
133+
extraPolicy = policy;
134+
directTriples = withDirectTriples;
135+
}
136+
120137
/*
121138
* SSSOM to RDF conversions
122139
*/
@@ -237,6 +254,11 @@ public Model toRDF(MappingSet ms, PrefixManager prefixManager) {
237254
// Add mapping metadata slots
238255
mappingVisitor.subject = mappingNode;
239256
mappingHelper.visitSlots(mapping, mappingVisitor);
257+
258+
if ( injectDirectTriple(mapping) ) {
259+
model.add(Values.iri(mapping.getSubjectId()), Values.iri(mapping.getPredicateId()),
260+
Values.iri(mapping.getObjectId()));
261+
}
240262
}
241263

242264
// Add namespace declarations
@@ -418,6 +440,21 @@ private SlotHelper<Mapping> getMappingHelper() {
418440
return helper;
419441
}
420442

443+
/*
444+
* Decides whether a mapping should be rendered as a "direct triple".
445+
*
446+
* This excludes (1) literal mappings, (2) mappings to sssom:NoTermFound, (3)
447+
* negated mappings, as tentatively recommended by the current spec proposal.
448+
* This also excludes any mapping that is missing at least one component of the
449+
* direct triple; such mappings are not supposed to exist, but there’s nothing
450+
* preventing client code from manufacturing one.
451+
*/
452+
private boolean injectDirectTriple(Mapping mapping) {
453+
return directTriples && !mapping.isLiteral() && !mapping.isUnmapped()
454+
&& mapping.getPredicateModifier() != PredicateModifier.NOT && mapping.getSubjectId() != null
455+
&& mapping.getPredicateId() != null && mapping.getObjectId() != null;
456+
}
457+
421458
/*
422459
* Extract the SSSOM Version value from the RDF model.
423460
*/

ext/src/main/java/org/incenp/obofoundry/sssom/rdf/RDFWriter.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class RDFWriter extends SSSOMWriter {
4242
private static final String PAV_NS = "http://purl.org/pav/";
4343

4444
private org.eclipse.rdf4j.rio.RDFWriter writer;
45+
private boolean directTriples = false;
4546

4647
/**
4748
* Creates a new instance that will write data to the specified file.
@@ -76,14 +77,27 @@ public RDFWriter(OutputStream stream) {
7677
writer.getWriterConfig().set(BasicWriterSettings.INLINE_BLANK_NODES, true);
7778
}
7879

80+
/**
81+
* Configures the writer to inject “direct triples” or not.
82+
*
83+
* @param withDirectTriples If {@code true}, for every mapping record the writer
84+
* will inject a triple of the form
85+
* {@code ?subject_id ?predicate_id ?object_id .} in
86+
* addition to the standard reified representation of
87+
* the record.
88+
*/
89+
public void setInjectDirectTriples(boolean withDirectTriples) {
90+
directTriples = withDirectTriples;
91+
}
92+
7993
@Override
8094
protected void doWrite(MappingSet mappingSet) throws IOException {
8195
// We might need those prefixes as some SSSOM slots are represented in RDF using
8296
// properties in the corresponding namespaces.
8397
prefixManager.add("dcterms", DCTERMS_NS);
8498
prefixManager.add("pav", PAV_NS);
8599

86-
RDFConverter converter = new RDFConverter(extraPolicy);
100+
RDFConverter converter = new RDFConverter(extraPolicy, directTriples);
87101
converter.excludeMappingSlots(condenseSet(mappingSet));
88102
Model rdfSet = converter.toRDF(mappingSet, prefixManager);
89103

ext/src/test/java/org/incenp/obofoundry/sssom/rdf/RDFWriterTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,28 @@ void testWriteURIExtensionValue() throws SSSOMFormatException, IOException {
134134
(w) -> w.setExtraMetadataPolicy(ExtraMetadataPolicy.UNDEFINED));
135135
}
136136

137+
@Test
138+
void testWriteDirectTriples() throws IOException {
139+
MappingSet ms = getTestSet();
140+
ms.getMappings()
141+
.add(Mapping.builder().subjectId("https://example.org/entities/0003")
142+
.predicateId("http://www.w3.org/2004/02/skos/core#closeMatch").objectLabel("Third entity")
143+
.objectType(EntityType.RDFS_LITERAL)
144+
.mappingJustification("https://w3id.org/semapv/vocab/ManualMappingCuration").build());
145+
ms.getMappings()
146+
.add(Mapping.builder().subjectId("https://example.org/entities/0004")
147+
.predicateId("http://www.w3.org/2004/02/skos/core#exactMatch")
148+
.objectId("https://w3id.org/sssom/NoTermFound")
149+
.mappingJustification("https://w3id.org/semapv/vocab/ManualMappingCuration").build());
150+
ms.getMappings()
151+
.add(Mapping.builder().subjectId("https://example.org/entities/0005")
152+
.predicateId("http://www.w3.org/2004/02/skos/core#exactMatch")
153+
.objectId("https://example.com/entities/0005").predicateModifier(PredicateModifier.NOT)
154+
.mappingJustification("https://w3id.org/semapv/vocab/ManualMappingCuration").build());
155+
156+
assertWrittenAsExpected(ms, "test-ttl-output-with-direct-triples", null, (w) -> w.setInjectDirectTriples(true));
157+
}
158+
137159
private void assertWrittenAsExpected(MappingSet ms, String expectedBasename, String actualBasename,
138160
Consumer<RDFWriter> consumer) throws IOException {
139161
if ( actualBasename == null ) {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
@prefix COMENT: <https://example.com/entities/> .
2+
@prefix COMPID: <https://example.com/people/> .
3+
@prefix FBbt: <http://purl.obolibrary.org/obo/FBbt_> .
4+
@prefix ORGENT: <https://example.org/entities/> .
5+
@prefix ORGPID: <https://example.org/people/> .
6+
@prefix UBERON: <http://purl.obolibrary.org/obo/UBERON_> .
7+
@prefix dcterms: <http://purl.org/dc/terms/> .
8+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
9+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
10+
@prefix semapv: <https://w3id.org/semapv/vocab/> .
11+
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
12+
@prefix sssom: <https://w3id.org/sssom/> .
13+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
14+
15+
FBbt:12345678 skos:closeMatch UBERON:1234567 .
16+
17+
ORGENT:0001 skos:closeMatch COMENT:0011 .
18+
19+
ORGENT:0002 skos:closeMatch COMENT:0012 .
20+
21+
<https://example.org/sets/exo2c> a sssom:MappingSet;
22+
dcterms:creator COMPID:0000-0000-0002-5678, ORGPID:0000-0000-0001-1234;
23+
dcterms:license <https://creativecommons.org/licenses/by/4.0/>;
24+
sssom:mappings [ a owl:Axiom;
25+
owl:annotatedProperty skos:closeMatch;
26+
owl:annotatedSource FBbt:12345678;
27+
owl:annotatedTarget UBERON:1234567;
28+
sssom:mapping_justification semapv:ManualMappingCuration
29+
], [ a owl:Axiom;
30+
owl:annotatedProperty skos:closeMatch;
31+
owl:annotatedSource ORGENT:0001;
32+
owl:annotatedTarget COMENT:0011;
33+
sssom:mapping_justification semapv:ManualMappingCuration;
34+
sssom:object_label "alpha";
35+
sssom:subject_label "alice";
36+
sssom:subject_type owl:Class
37+
], [ a owl:Axiom;
38+
owl:annotatedProperty skos:closeMatch;
39+
owl:annotatedSource ORGENT:0002;
40+
owl:annotatedTarget COMENT:0012;
41+
sssom:confidence 7.0E-1;
42+
sssom:mapping_justification semapv:ManualMappingCuration;
43+
sssom:object_label "beta";
44+
sssom:subject_label "bob";
45+
sssom:subject_type owl:Class
46+
], [ a owl:Axiom;
47+
owl:annotatedProperty skos:closeMatch;
48+
owl:annotatedSource ORGENT:0003;
49+
sssom:mapping_justification semapv:ManualMappingCuration;
50+
sssom:object_label "Third entity";
51+
sssom:object_type rdfs:Literal
52+
], [ a owl:Axiom;
53+
owl:annotatedProperty skos:exactMatch;
54+
owl:annotatedSource ORGENT:0004;
55+
owl:annotatedTarget sssom:NoTermFound;
56+
sssom:mapping_justification semapv:ManualMappingCuration
57+
], [ a owl:Axiom;
58+
owl:annotatedProperty skos:exactMatch;
59+
owl:annotatedSource ORGENT:0005;
60+
owl:annotatedTarget COMENT:0005;
61+
sssom:mapping_justification semapv:ManualMappingCuration;
62+
sssom:predicate_modifier sssom:NegatedPredicate
63+
] .

0 commit comments

Comments
 (0)