Skip to content

Commit be0108b

Browse files
authored
Merge pull request #11 from psychoinformatics-de/n3
Introduce `n3`
2 parents d812b91 + 9e56ea5 commit be0108b

File tree

13 files changed

+178
-572
lines changed

13 files changed

+178
-572
lines changed

package-lock.json

Lines changed: 6 additions & 404 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "shacl-tulip",
3-
"version": "0.0.2",
3+
"version": "0.0.3",
44
"type": "module",
55
"main": "./src/index.js",
66
"module": "./src/index.js",
@@ -24,6 +24,6 @@
2424
"dependencies": {
2525
"@rdfjs/fetch-lite": "^3.3.0",
2626
"@rdfjs/formats-common": "^3.1.0",
27-
"rdf-ext": "^2.5.2"
27+
"n3": "^1.25.2"
2828
}
2929
}

src/classes/ClassDataset.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export class ClassDataset extends RdfDataset {
1515
if (quad.predicate.value === RDFS.subClassOf.value &&
1616
quad.subject.termType !== 'BlankNode' &&
1717
quad.object.termType !== 'BlankNode' ) {
18-
this.addQuad(quad)
19-
this.dispatchEvent(new CustomEvent('quad', { detail: quad }));
18+
this.addQuad(quad)
19+
this.dispatchEvent(new CustomEvent('quad', { detail: quad }));
2020
}
2121
}
2222
}

src/classes/FormBase.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
*
33
*/
44

5-
import rdf from 'rdf-ext';
65
import { RDF } from '../modules/namespaces';
76
import { isEmptyObject, toIRI} from '../modules/utils';
7+
import { DataFactory } from 'n3';
8+
const { namedNode, blankNode, quad } = DataFactory;
89

910
export class FormBase {
1011

@@ -140,7 +141,7 @@ export class FormBase {
140141
// Identify the record's subject (named or blank node)
141142
var subject = this._getRecordSubjectTerm(subject_uri, this.content[class_uri][subject_uri])
142143
// Add the triple stating the subject is of type class
143-
let firstQuad = rdf.quad(subject, rdf.namedNode(RDF.type.value), rdf.namedNode(class_uri))
144+
let firstQuad = quad(subject, namedNode(RDF.type.value), namedNode(class_uri))
144145
quadArray.push(firstQuad)
145146
// Now we need to add all triples relating to the properties of the record.
146147
for (var triple_predicate of Object.keys(this.content[class_uri][subject_uri])) {
@@ -161,21 +162,21 @@ export class FormBase {
161162
continue
162163
}
163164
// now set the predicate as a named node
164-
var predicate = rdf.namedNode(triple_predicate)
165+
var predicate = namedNode(triple_predicate)
165166
// In order to set the node type of the object, we first need to figure it out
166167
var [nodeFunc, dt] = shapesDS.getPropertyNodeKind(class_uri, triple_predicate, this.ID_IRI)
167168
// Now we can create the object nodes for each property
168169
for (var val of this.content[class_uri][subject_uri][triple_predicate]) {
169170
// val: all values of a given property of an identifiable object
170171
let triple_object
171172
if (dt) {
172-
triple_object = nodeFunc(val, rdf.namedNode(dt))
173+
triple_object = nodeFunc(val, namedNode(dt))
173174
} else {
174175
triple_object = nodeFunc(val)
175176
}
176177
// and finally we can add the quads to the store
177-
let quad = rdf.quad(subject, predicate, triple_object)
178-
quadArray.push(quad)
178+
let q = quad(subject, predicate, triple_object)
179+
quadArray.push(q)
179180
}
180181
}
181182
return quadArray
@@ -202,9 +203,9 @@ export class FormBase {
202203
var subject
203204
if (Object.keys(record).indexOf(this.ID_IRI) >= 0) {
204205
var subject_iri = record[this.ID_IRI][0]
205-
subject = rdf.namedNode(subject_iri)
206+
subject = namedNode(subject_iri)
206207
} else {
207-
subject = rdf.blankNode(record_id)
208+
subject = blankNode(record_id)
208209
}
209210
return subject
210211
}
@@ -258,7 +259,7 @@ export class FormBase {
258259
if (this.content[class_uri]) {
259260
// If we are in edit mode, the first step is to delete existing quads from graphData
260261
if (editMode) {
261-
RdfDS.data.graph.deleteMatches(rdf.namedNode(node_uri), null, null, null)
262+
RdfDS.data.graph.deleteMatches(namedNode(node_uri), null, null, null)
262263
}
263264

264265
// Then we generate the quads
@@ -285,9 +286,9 @@ export class FormBase {
285286
// - for each triple in oldTriples: create a new one with same subject and predicate
286287
// and with new IRI as object, then delete the old triple
287288
if (editMode && subject_iri !== null && subject_iri !== node_uri) {
288-
var objectQuads = RdfDS.getObjectTriples(rdf.namedNode(node_uri))
289+
var objectQuads = RdfDS.getObjectTriples(namedNode(node_uri))
289290
objectQuads.forEach((quad) => {
290-
let new_quad = rdf.quad(quad.subject, quad.predicate, subject)
291+
let new_quad = quad(quad.subject, quad.predicate, subject)
291292
RdfDS.data.graph.delete(quad)
292293
RdfDS.data.graph.add(new_quad)
293294
});

src/classes/RdfDataset.js

Lines changed: 54 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import rdf from 'rdf-ext';
21
import { readRDF } from '../modules/io'
32
import { RDF, XSD } from '../modules/namespaces';
43
import { toCURIE } from '../modules/utils';
5-
import formatsPretty from '@rdfjs/formats/pretty.js'
4+
import { Store, Writer, DataFactory } from 'n3';
5+
const { namedNode, literal } = DataFactory;
66

77
/**
88
* A class wrapping an RDF dataset (quad-store) from the `rdf-ext` library.
@@ -13,8 +13,6 @@ export class RdfDataset {
1313
*/
1414
constructor(data = {}) {
1515
this.data = data;
16-
this.rdfPretty = rdf.clone();
17-
this.rdfPretty.formats.import(formatsPretty);
1816
this.data.prefixes = {};
1917
this.data.serializedGraph = '';
2018
this.data.graphLoaded = false;
@@ -37,11 +35,11 @@ export class RdfDataset {
3735

3836
/**
3937
* Create a quad store compliant with the [RDF/JS dataset specification](https://rdf.js.org/dataset-spec/)
40-
* via the `rdf-ext` package
41-
* @returns {import("rdf-ext").DatasetCore} The RDF dataset instance.
38+
* via the `n3` package
39+
* @returns {} The RDF dataset instance.
4240
*/
4341
createDataset() {
44-
return rdf.dataset()
42+
return new Store();
4543
}
4644
/**
4745
* Loads RDF data from a given URL and processes prefixes and quads.
@@ -60,16 +58,14 @@ export class RdfDataset {
6058
// Load prefixes
6159
quadStream.on('prefix', (prefix, ns) => {
6260
this.onPrefixFn(prefix, ns)
61+
}).on('data', quad => {
62+
this.onDataFn(quad)
6363
}).on('end', () => {
64+
this.onDataEndFn()
6465
this.onPrefixEndFn()
65-
})
66-
// Load data
67-
quadStream.on('data', quad => {
68-
this.onDataFn(quad)
69-
}).on('end', async () => {
70-
await this.onDataEndFn()
66+
}).on('error', err => {
67+
console.error('Error while processing quadStream:', err);
7168
});
72-
7369
return result
7470
}
7571

@@ -83,7 +79,7 @@ export class RdfDataset {
8379
/**
8480
* Process an RDF prefix.
8581
* @param {string} prefix - The prefix string.
86-
* @param {import("rdf-ext").NamedNode} ns - The namespace associated with the prefix.
82+
* @param {} ns - The namespace associated with the prefix.
8783
*/
8884
onPrefixFn(prefix, ns) {
8985
this.data.prefixes[prefix] = ns.value;
@@ -96,63 +92,65 @@ export class RdfDataset {
9692

9793
/**
9894
* Process an RDF quad
99-
* @param {import("rdf-ext").Quad} quad - The RDF quad.
95+
* @param {} quad - The RDF quad.
10096
*/
10197
onDataFn(quad) {
10298
// The first following line, moved here from shacl-vue's graphdata composable,
10399
// was an attempt to solve https://hub.datalad.org/datalink/annotate-trr379-demo/issues/32.
104100
// But it was a faulty attempt, since the object was different. Still, leaving it here since
105101
// deleting matches would prospectively solve the duplication of named node or literal objects
106-
this.data.graph.deleteMatches(quad.subject, quad.predicate, quad.object, null)
102+
// this.data.graph.removeMatches(quad.subject, quad.predicate, quad.object, null)
107103
this.addQuad(quad)
108104
this.dispatchEvent(new CustomEvent('quad', { detail: quad }));
109105
}
110-
async onDataEndFn() {
111-
await this.updateSerializedGraph()
106+
107+
onDataEndFn() {
112108
this.data.graphLoaded = true
113109
this.dispatchEvent(new CustomEvent('graphLoaded', { detail: this.data.graph }));
114110
}
115111

116112
/**
117113
* Add an RDF quad to the dataset
118-
* @param {import("rdf-ext").Quad} quad - The RDF quad to add.
114+
* @param {} quad - The RDF quad to add.
119115
*/
120116
addQuad(quad) {
121-
this.data.graph.add(quad)
117+
this.data.graph.addQuad(quad)
122118
}
123119

124120
/**
125121
* Serializes the RDF graph to Turtle format.
126122
* @returns {Promise<string>} The serialized RDF graph in Turtle format.
127123
*/
128124
async serializeGraph() {
129-
return (await this.rdfPretty.io.dataset.toText('text/turtle', this.data.graph)).trim()
130-
}
131-
132-
async updateSerializedGraph() {
133-
this.data.serializedGraph = (await this.rdfPretty.io.dataset.toText('text/turtle', this.data.graph)).trim()
125+
// Using N3.Writer to serialize graph to Turtle
126+
return new Promise((resolve, reject) => {
127+
const writer = new Writer({ prefixes: this.data.prefixes });
128+
writer.addQuads(this.data.graph.getQuads(null, null, null, null));
129+
writer.end((error, result) => {
130+
if (error) reject(error);
131+
else resolve(result.trim());
132+
});
133+
});
134134
}
135135

136136
/**
137137
* Checks if a given RDF node represents an RDF list.
138-
* @param {import("rdf-ext").Term} node - The RDF node to check.
138+
* @param {} node - The RDF node to check.
139139
* @returns {boolean} True if the node represents an RDF list, otherwise false.
140140
*/
141141
isRdfList(node) {
142142
let hasFirst = false;
143143
let hasRest = false;
144-
this.data.graph.forEach((quad) => {
145-
if (quad.subject.equals(node)) {
146-
if (quad.predicate.value === RDF.first.value) hasFirst = true;
147-
if (quad.predicate.value === RDF.rest.value) hasRest = true;
148-
}
144+
this.data.graph.getQuads(node, null, null, null).forEach((quad) => {
145+
if (quad.predicate.value === RDF.first.value) hasFirst = true;
146+
if (quad.predicate.value === RDF.rest.value) hasRest = true;
149147
});
150148
return hasFirst && hasRest;
151149
};
152150

153151
/**
154152
* Converts an RDF list to an array.
155-
* @param {import("rdf-ext").Term} startNode - The starting node of the RDF list.
153+
* @param {} startNode - The starting node of the RDF list.
156154
* @returns {Array} The converted RDF list as an array.
157155
*/
158156
rdfListToArray(startNode) {
@@ -161,79 +159,55 @@ export class RdfDataset {
161159
while (currentNode && currentNode.value !== RDF.nil.value) {
162160
let listItem = null;
163161
// Get the first element in the RDF list
164-
this.data.graph.forEach((quad) => {
165-
if (quad.subject.equals(currentNode) && quad.predicate.value === RDF.first.value) {
166-
// Resolve blank nodes recursively, but handle literals and IRIs separately
167-
if (quad.object.termType === "BlankNode") {
168-
listItem = this.resolveBlankNode(quad.object, this.data.graph);
169-
} else if (quad.object.termType === "Literal") {
170-
listItem = quad.object.value; // Store literal value
171-
} else if (quad.object.termType === "NamedNode") {
172-
listItem = quad.object.value; // Store IRI as a string
162+
this.data.graph.getQuads(currentNode, RDF.first, null, null).forEach(quad => {
163+
if (quad.object.termType === 'BlankNode') {
164+
if (this.isRdfList(quad.object)) {
165+
listItem = this.rdfListToArray(quad.object);
166+
} else {
167+
listItem = this.resolveBlankNode(quad.object);
173168
}
169+
} else if (quad.object.termType === 'Literal' || quad.object.termType === 'NamedNode') {
170+
listItem = quad.object.value;
174171
}
175172
});
176173
if (listItem !== null) {
177174
listItems.push(listItem);
178175
}
179176
// Move to the next item in the list (rdf:rest)
180-
let nextNode = null;
181-
this.data.graph.forEach((quad) => {
182-
if (quad.subject.equals(currentNode) && quad.predicate.value === RDF.rest.value) {
183-
nextNode = quad.object;
184-
}
185-
});
186-
currentNode = nextNode;
177+
const restQuads = this.data.graph.getQuads(currentNode, RDF.rest, null, null);
178+
currentNode = restQuads.length > 0 ? restQuads[0].object : null;
187179
}
188180
return listItems;
189181
};
190182

191183
resolveBlankNode(blankNode) {
192184
let resolvedObject = {};
193-
this.data.graph.forEach((quad) => {
194-
if (quad.subject.equals(blankNode)) {
195-
const predicate = quad.predicate.value;
196-
const object = quad.object;
197-
198-
// If the object is a blank node, resolve it recursively
199-
if (object.termType === "BlankNode") {
200-
// Check if it's an RDF list and convert it to an array
201-
if (this.isRdfList(object)) {
202-
resolvedObject[predicate] = this.rdfListToArray(object);
203-
} else {
204-
resolvedObject[predicate] = this.resolveBlankNode(object);
205-
}
206-
} else if (object.termType === "Literal") {
207-
resolvedObject[predicate] = object.value; // Handle literal values
208-
} else if (object.termType === "NamedNode") {
209-
resolvedObject[predicate] = object.value; // Handle IRIs as strings
185+
this.data.graph.getQuads(blankNode, null, null, null).forEach(({ predicate, object }) => {
186+
if (object.termType === 'BlankNode') {
187+
if (this.isRdfList(object)) {
188+
resolvedObject[predicate.value] = this.rdfListToArray(object);
189+
} else {
190+
resolvedObject[predicate.value] = this.resolveBlankNode(object);
210191
}
192+
} else if (object.termType === 'Literal' || object.termType === 'NamedNode') {
193+
resolvedObject[predicate.value] = object.value;
211194
}
212195
});
213196
return resolvedObject;
214197
}
215198

216199
getLiteralAndNamedNodes(predicate, propertyClass, prefixes) {
217200
var propClassCurie = toCURIE(propertyClass, prefixes)
218-
// a) use the literal node with xsd data type
219-
const literalNodes = rdf.grapoi({ dataset: this.data.graph })
220-
.hasOut(predicate, rdf.literal(String(propClassCurie), XSD.anyURI))
221-
.quads();
222-
// b) and the named node
223-
const uriNodes = rdf.grapoi({ dataset: this.data.graph })
224-
.hasOut(predicate, rdf.namedNode(propertyClass))
225-
.quads();
226-
// return as a concatenated array of quads
227-
return Array.from(literalNodes).concat(Array.from(uriNodes))
201+
const literalQuads = this.data.graph.getQuads(null, predicate, literal(String(propClassCurie), XSD.anyURI), null)
202+
const uriQuads = this.data.graph.getQuads(null, predicate, namedNode(propertyClass), null)
203+
return literalQuads.concat(uriQuads)
228204
}
229205

230206
getSubjectTriples(someTerm) {
231-
const quads = rdf.grapoi({ dataset: this.data.graph, term: someTerm }).out().quads();
232-
return Array.from(quads)
207+
return this.data.graph.getQuads(someTerm, null, null, null);
233208
}
234209

235210
getObjectTriples(someTerm) {
236-
const quads = rdf.grapoi({ dataset: this.data.graph, term: someTerm }).in().quads();
237-
return Array.from(quads)
211+
return this.data.graph.getQuads(null, null, someTerm, null);
238212
}
239213
}

0 commit comments

Comments
 (0)