Skip to content

Commit

Permalink
fix: resolve blank node errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jeswr committed Jan 18, 2023
1 parent 40e507e commit 92187fa
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 22 deletions.
16 changes: 14 additions & 2 deletions __tests__/main-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DataFactory, Parser } from 'n3';
import fs from 'fs';
import path from 'path';
import { write } from '../lib';
import 'jest-rdf';

async function getQuads(file: string) {
const parser = new Parser({ rdfStar: true } as any);
Expand All @@ -16,10 +17,21 @@ async function getQuads(file: string) {
};
}

const loose: Record<string, boolean | undefined> = {
'bnodes5.ttl': true,
};

it('It should correctly write turtle files', async () => {
for (const file of fs.readdirSync(path.join(__dirname, '..', 'data'))) {
const { string } = await getQuads(file);
expect(string.replace(/b0_/g, '')).toEqual(fs.readFileSync(path.join(__dirname, '..', 'data', file)).toString());
const { string, quads } = await getQuads(file);

if (loose[file]) {
// If loose we only need the quads to match when we re-parse the string
expect((new Parser()).parse(string)).toBeRdfIsomorphic(quads);
} else {
// If not loose we expect an exact string match
expect(string.replace(/b\d+_/g, '')).toEqual(fs.readFileSync(path.join(__dirname, '..', 'data', file)).toString());
}
}
});

Expand Down
2 changes: 1 addition & 1 deletion data/bnodes.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ ex:t a _:b1 .

ex:k a _:b1 .

_:b2 a ex:Thing .
[] a ex:Thing .
9 changes: 9 additions & 0 deletions data/bnodes1.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@prefix ex: <http://example.com/ns#> .

ex:j a _:b1 .

ex:t a _:b1, [] .

ex:k a _:b1, [] .

[] a ex:Thing .
5 changes: 5 additions & 0 deletions data/bnodes2.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@prefix ex: <http://example.com/ns#> .

[] ex:p [
ex:p2 ex:o
] .
3 changes: 3 additions & 0 deletions data/bnodes3.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@prefix ex: <http://example.com/ns#> .

[] ex:p [], [], ([] []) .
3 changes: 3 additions & 0 deletions data/bnodes4.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@prefix ex: <http://example.com/ns#> .

_:s3 a _:s3 .
15 changes: 15 additions & 0 deletions data/bnodes5.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@prefix ex: <http://example.com/ns#> .

[] ex:p [
ex:p []
] .

_:s1 ex:t ex:k .

ex:s ex:p _:s1 .

_:s2 ex:t ex:k .

_:s3 ex:p _:s2 .

_:s3 a _:s3 .
7 changes: 7 additions & 0 deletions data/bnodes6.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@prefix : <http://example.com/ns#> .

:o :p _:s1 .

:o1 :p _:s1 .

_:s1 :p :o .
81 changes: 62 additions & 19 deletions lib/ttlwriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,63 @@ export class TTLWriter {

this.writer.newLine(1);

// First write Named Node subjects
for (const subject of this.store.getSubjects(null, null, null)) {
if (subject.termType === 'NamedNode') {
await this.writeTurtleSubject(subject);
}
}

// Then write blank node subjects that can be anonymized at the top level
for (const subject of this.store.getSubjects(null, null, null)) {
await this.writeTurtleSubject(subject);
if (
subject.termType === 'BlankNode'
&& !this.explicitBnodes.has(subject.value)
// Ensure still in store as subject
&& this.store.getQuads(subject, null, null, null).length > 0
&& this.store.getQuads(null, subject, null, null).length === 0
&& this.store.getQuads(null, null, subject, null).length === 0
) {
await this.writeTurtleSubject(subject, true);
}
}

// Next write blank nodes that cannot be anonymized within another set of statements
// (it is not an explicit bnode,
// occurs as the object of one quad,
// and only as the subject in other quads)
for (const subject of this.store.getSubjects(null, null, null)) {
// Ensure still in store as subject
if (
subject.termType === 'BlankNode' && !(
this.store.getQuads(null, null, subject, null).length !== 1
|| !this.store.getQuads(null, null, subject, null)[0].subject.equals(subject)
)
) {
this.explicitBnodes.add(subject.value);
await this.writeTurtleSubject(subject);
}
}

for (const subject of this.store.getSubjects(null, null, null)) {
// Ensure still in store as subject
if (this.store.getQuads(subject, null, null, null).length > 0) {
if (subject.termType === 'BlankNode') {
this.explicitBnodes.add(subject.value);
}
await this.writeTurtleSubject(subject);
}
}

this.writer.end();
}

private async writeTurtleSubject(term: Term) {
this.writer.add(await this.termToString(term));
private async writeTurtleSubject(term: Term, anonymizeSubject = false) {
if (anonymizeSubject) {
this.writer.add('[]');
} else {
this.writer.add(await this.termToString(term));
}
this.writer.add(' ');
this.writer.indent();
await this.writeTurtlePredicates(term);
Expand All @@ -83,18 +125,17 @@ export class TTLWriter {
}
}
} if (term.termType === 'Literal' && (term.datatypeString === 'http://www.w3.org/2001/XMLSchema#integer'
|| term.datatypeString === 'http://www.w3.org/2001/XMLSchema#boolean')) {
|| term.datatypeString === 'http://www.w3.org/2001/XMLSchema#boolean')) {
return term.value;
}
if (term.termType === 'Quad') {
if (!term.graph.equals(DataFactory.defaultGraph())) {
throw new Error('Default graph expected on nested quads');
}
return `<<${await this.termToString(term.subject as any)} ${
term.predicate.termType === 'NamedNode'
&& term.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'
? 'a'
: await this.termToString(term.predicate as any)} ${await this.termToString(term.object as any)}>>`;
return `<<${await this.termToString(term.subject as any)} ${term.predicate.termType === 'NamedNode'
&& term.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'
? 'a'
: await this.termToString(term.predicate as any)} ${await this.termToString(term.object as any)}>>`;
}
return termToString(term);
}
Expand Down Expand Up @@ -146,11 +187,11 @@ export class TTLWriter {
const nonBlankObjects: Term[] = [];
for (const object of objects) {
if (object.termType === 'BlankNode'
&& [
...this.store.match(null, null, object),
...this.store.match(null, object, null),
].length === 0
&& !this.explicitBnodes.has(object.value)
&& [
...this.store.match(null, null, object),
...this.store.match(null, object, null),
].length === 0
&& !this.explicitBnodes.has(object.value)
) {
blankObjects.push(object);
} else {
Expand All @@ -176,11 +217,13 @@ export class TTLWriter {
}
if (!(await this.writeList(blank))) {
this.writer.add('[');
this.writer.indent();
this.writer.newLine(1);
await this.writeTurtlePredicates(blank);
this.writer.deindent();
this.writer.newLine(1);
if (this.store.getQuads(blank, null, null, null).length > 0) {
this.writer.indent();
this.writer.newLine(1);
await this.writeTurtlePredicates(blank);
this.writer.deindent();
this.writer.newLine(1);
}
this.writer.add(']');
}
}
Expand Down
62 changes: 62 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.27.5",
"jest": "^29.3.1",
"jest-rdf": "^1.8.0",
"pre-commit": "^1.2.2",
"semantic-release": "^20.0.2",
"ts-jest": "^29.0.5",
Expand Down

0 comments on commit 92187fa

Please sign in to comment.