Skip to content

Commit aa6a469

Browse files
authored
BREAKING CHANGE: Add RDF 1.2 support
This removes RDF-star parsing and instead implements support for RDF 1.2. Concretely, this includes serialization and parsing support for directional language-tagged strings, triple terms, reified triples, and VERSION clauses. This passes all 1.2 (and 1.1) spec tests for N-Triples, N-Quads, Turtle, and TriG. BREAKING CHANGE: RDF-star support has been removed in favor of RDF 1.2 support.
1 parent a476b5b commit aa6a469

File tree

17 files changed

+1462
-332
lines changed

17 files changed

+1462
-332
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- run: npm ci --ignore-scripts
1919
- run: npm run build
2020
- run: npm run test
21-
21+
2222
lint:
2323
runs-on: ubuntu-latest
2424
strategy:
@@ -34,24 +34,22 @@ jobs:
3434
- run: npm ci --ignore-scripts
3535
- run: npm run build
3636
- run: npm run lint
37-
37+
3838
spec:
3939
runs-on: ubuntu-latest
4040
strategy:
4141
fail-fast: false
4242
matrix:
4343
node-version: [12.x, 14.x, 16.x, 18.x, 20.x, 22.x]
44+
spec: [1-1-turtle, 1-1-ntriples, 1-1-nquads, 1-1-trig, 1-2-turtle, 1-2-ntriples, 1-2-nquads, 1-2-trig]
4445
steps:
4546
- name: Use Node.js ${{ matrix.node-version }}
4647
uses: actions/setup-node@v4
4748
with:
4849
node-version: ${{ matrix.node-version }}
4950
- uses: actions/checkout@v4
5051
- run: npm ci
51-
- run: npm run spec-turtle
52-
- run: npm run spec-ntriples
53-
- run: npm run spec-nquads
54-
- run: npm run spec-trig
52+
- run: npm run spec-${{ matrix.spec }}
5553

5654
docs:
5755
runs-on: ubuntu-latest

README.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@
44
[![npm version](https://badge.fury.io/js/n3.svg)](https://www.npmjs.com/package/n3)
55
[![DOI](https://zenodo.org/badge/3058202.svg)](https://zenodo.org/badge/latestdoi/3058202)
66

7-
The N3.js library is an implementation of the [RDF.js low-level specification](http://rdf.js.org/) that lets you handle [RDF](https://www.w3.org/TR/rdf-primer/) in JavaScript easily.
7+
The N3.js library is an implementation of the [RDF.js low-level specification](http://rdf.js.org/) that lets you handle [RDF 1.2](https://www.w3.org/TR/rdf-primer/) in JavaScript easily.
88
It offers:
99

1010
- [**Parsing**](#parsing) triples/quads from
1111
[Turtle](https://www.w3.org/TR/turtle/),
1212
[TriG](https://www.w3.org/TR/trig/),
1313
[N-Triples](https://www.w3.org/TR/n-triples/),
1414
[N-Quads](https://www.w3.org/TR/n-quads/),
15-
[RDF-star](https://www.w3.org/2021/12/rdf-star.html)
1615
and [Notation3 (N3)](https://www.w3.org/TeamSubmission/n3/)
1716
- [**Writing**](#writing) triples/quads to
1817
[Turtle](https://www.w3.org/TR/turtle/),
1918
[TriG](https://www.w3.org/TR/trig/),
2019
[N-Triples](https://www.w3.org/TR/n-triples/),
21-
[N-Quads](https://www.w3.org/TR/n-quads/)
22-
and [RDF-star](https://www.w3.org/2021/12/rdf-star.html)
20+
and [N-Quads](https://www.w3.org/TR/n-quads/)
2321
- [**Storage**](#storing) of triples/quads in memory
2422

2523
Parsing and writing is:
@@ -418,19 +416,17 @@ The N3.js parser and writer is fully compatible with the following W3C specifica
418416
[EARL report](https://raw.githubusercontent.com/rdfjs/N3.js/earl/n3js-earl-report-ntriples.ttl)
419417
- [RDF 1.1 N-Quads](https://www.w3.org/TR/n-quads/)
420418
[EARL report](https://raw.githubusercontent.com/rdfjs/N3.js/earl/n3js-earl-report-nquads.ttl)
419+
- [RDF 1.2 Turtle](https://www.w3.org/TR/rdf12-turtle/)
420+
- [RDF 1.2 TriG](https://www.w3.org/TR/rdf12-trig/)
421+
- [RDF 1.2 N-Triples](https://www.w3.org/TR/rdf12-n-triples/)
422+
- [RDF 1.2 N-Quads](https://www.w3.org/TR/rdf12-n-quads/)
421423

422424
In addition, the N3.js parser also supports [Notation3 (N3)](https://www.w3.org/TeamSubmission/n3/) (no official specification yet).
423425

424-
The N3.js parser and writer are also fully compatible with the RDF-star variants
425-
of the W3C specifications.
426-
427426
The default mode is permissive
428-
and allows a mixture of different syntaxes, including RDF-star.
427+
and allows a mixture of different syntaxes.
429428
Pass a `format` option to the constructor with the name or MIME type of a format
430429
for strict, fault-intolerant behavior.
431-
If a format string contains `star` or `*`
432-
(e.g., `turtlestar` or `TriG*`),
433-
RDF-star support for that format will be enabled.
434430

435431
### Interface specifications
436432
The N3.js submodules are compatible with the following [RDF.js](http://rdf.js.org) interfaces:

package.json

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "n3",
3-
"version": "1.17.3",
3+
"version": "2.0.0-beta.2",
44
"description": "Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.",
55
"author": "Ruben Verborgh <ruben.verborgh@gmail.com>",
66
"keywords": [
@@ -54,16 +54,27 @@
5454
"test": "jest",
5555
"lint": "eslint src perf test spec",
5656
"prepare": "npm run build",
57-
"spec": "npm run spec-turtle && npm run spec-ntriples && npm run spec-nquads && npm run spec-trig",
58-
"spec-earl": "npm run spec-earl-turtle && npm run spec-earl-ntriples && npm run spec-earl-nquads && npm run spec-earl-trig",
59-
"spec-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-triples/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/",
60-
"spec-nquads": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-quads/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/",
61-
"spec-turtle": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-turtle/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/",
62-
"spec-trig": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-trig/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/",
63-
"spec-earl-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-triples/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-ntriples.ttl",
64-
"spec-earl-nquads": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-quads/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-nquads.ttl",
65-
"spec-earl-turtle": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-turtle/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-turtle.ttl",
66-
"spec-earl-trig": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-trig/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-trig.ttl",
57+
"spec": "npm run spec-1-1 && npm run spec-1-2",
58+
"spec-1-1": "npm run spec-1-1-turtle && npm run spec-1-1-ntriples && npm run spec-1-1-nquads && npm run spec-1-1-trig",
59+
"spec-1-1-earl": "npm run spec-1-1-earl-turtle && npm run spec-1-1-earl-ntriples && npm run spec-1-1-earl-nquads && npm run spec-1-1-earl-trig",
60+
"spec-1-1-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-triples/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/",
61+
"spec-1-1-nquads": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-quads/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/",
62+
"spec-1-1-turtle": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-turtle/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/ --skip \"(bad-numeric-escape|bareword_decimal)\"",
63+
"spec-1-1-trig": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-trig/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/ --skip \"(bad-numeric-escape|IRI-resolution-01)\"",
64+
"spec-1-1-earl-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-triples/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-ntriples.ttl",
65+
"spec-1-1-earl-nquads": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-n-quads/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-nquads.ttl",
66+
"spec-1-1-earl-turtle": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-turtle/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-turtle.ttl",
67+
"spec-1-1-earl-trig": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf11/rdf-trig/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-trig.ttl",
68+
"spec-1-2": "npm run spec-1-2-turtle && npm run spec-1-2-ntriples && npm run spec-1-2-nquads && npm run spec-1-2-trig",
69+
"spec-1-2-earl": "npm run spec-1-2-earl-turtle && npm run spec-1-2-earl-ntriples && npm run spec-1-2-earl-nquads && npm run spec-1-2-earl-trig",
70+
"spec-1-2-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-triples/syntax/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/",
71+
"spec-1-2-nquads": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-quads/syntax/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/",
72+
"spec-1-2-turtle": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-turtle/syntax/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/",
73+
"spec-1-2-trig": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-trig/syntax/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/",
74+
"spec-1-2-earl-ntriples": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-triples/syntax/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-ntriples.ttl",
75+
"spec-1-2-earl-nquads": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-quads/syntax/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-nquads.ttl",
76+
"spec-1-2-earl-turtle": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-turtle/syntax/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-turtle.ttl",
77+
"spec-1-2-earl-trig": "rdf-test-suite spec/parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-trig/syntax/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-trig.ttl",
6778
"spec-clean": "rm -r .rdf-test-suite-cache/",
6879
"docs": "cd src && docco *.js -o ../docs && cd .."
6980
},

perf/N3Parser-perf.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const TEST = `- Parsing file ${filename}`;
1616
console.time(TEST);
1717

1818
let count = 0;
19-
new N3.Parser({ baseIRI: base }).parse(fs.createReadStream(filename), (error, quad) => {
19+
new N3.Parser({ baseIRI: base, format: 'text/turtle' }).parse(fs.createReadStream(filename), (error, quad) => {
2020
assert(!error, error);
2121
if (quad)
2222
count++;

spec/parser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ const { StreamParser } = require('..');
55
module.exports = {
66
parse: function (data, baseIRI, options) {
77
return require('arrayify-stream').arrayifyStream(require('streamify-string')(data).pipe(
8-
new StreamParser(Object.assign({ baseIRI: baseIRI }, options))));
8+
new StreamParser(Object.assign({ baseIRI: baseIRI, parseUnsupportedVersions: true }, options))));
99
},
1010
};

src/IRIs.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ export default {
1111
string: `${XSD}string`,
1212
},
1313
rdf: {
14-
type: `${RDF}type`,
15-
nil: `${RDF}nil`,
16-
first: `${RDF}first`,
17-
rest: `${RDF}rest`,
18-
langString: `${RDF}langString`,
14+
type: `${RDF}type`,
15+
nil: `${RDF}nil`,
16+
first: `${RDF}first`,
17+
rest: `${RDF}rest`,
18+
langString: `${RDF}langString`,
19+
dirLangString: `${RDF}dirLangString`,
20+
reifies: `${RDF}reifies`,
1921
},
2022
owl: {
2123
sameAs: 'http://www.w3.org/2002/07/owl#sameAs',

src/N3DataFactory.js

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,17 @@ export class Literal extends Term {
8888
// Find the last quotation mark (e.g., '"abc"@en-us')
8989
const id = this.id;
9090
let atPos = id.lastIndexOf('"') + 1;
91+
const dirPos = id.lastIndexOf('--');
9192
// If "@" it follows, return the remaining substring; empty otherwise
92-
return atPos < id.length && id[atPos++] === '@' ? id.substr(atPos).toLowerCase() : '';
93+
return atPos < id.length && id[atPos++] === '@' ? (dirPos > atPos ? id.substr(0, dirPos) : id).substr(atPos).toLowerCase() : '';
94+
}
95+
96+
// ### The direction of this literal
97+
get direction() {
98+
// Find the last double dash (e.g., '"abc"@en-us--ltr')
99+
const id = this.id;
100+
const atPos = id.lastIndexOf('--') + 2;
101+
return atPos > 1 && atPos < id.length ? id.substr(atPos).toLowerCase() : '';
93102
}
94103

95104
// ### The datatype IRI of this literal
@@ -104,8 +113,8 @@ export class Literal extends Term {
104113
const char = dtPos < id.length ? id[dtPos] : '';
105114
// If "^" it follows, return the remaining substring
106115
return char === '^' ? id.substr(dtPos + 2) :
107-
// If "@" follows, return rdf:langString; xsd:string otherwise
108-
(char !== '@' ? xsd.string : rdf.langString);
116+
// If "@" follows, return rdf:langString or rdf:dirLangString; xsd:string otherwise
117+
(char !== '@' ? xsd.string : (id.indexOf('--', dtPos) > 0 ? rdf.dirLangString : rdf.langString));
109118
}
110119

111120
// ### Returns whether this object represents the same term as the other
@@ -119,14 +128,16 @@ export class Literal extends Term {
119128
this.termType === other.termType &&
120129
this.value === other.value &&
121130
this.language === other.language &&
131+
((this.direction === other.direction) || (this.direction === '' && !other.direction)) &&
122132
this.datatype.value === other.datatype.value;
123133
}
124134

125135
toJSON() {
126136
return {
127-
termType: this.termType,
128-
value: this.value,
129-
language: this.language,
137+
termType: this.termType,
138+
value: this.value,
139+
language: this.language,
140+
direction: this.direction,
130141
datatype: { termType: 'NamedNode', value: this.datatypeString },
131142
};
132143
}
@@ -216,9 +227,22 @@ export function termFromId(id, factory, nested) {
216227
return factory.literal(id.substr(1, id.length - 2));
217228
// Literal with datatype or language
218229
const endPos = id.lastIndexOf('"', id.length - 1);
230+
let languageOrDatatype;
231+
if (id[endPos + 1] === '@') {
232+
languageOrDatatype = id.substr(endPos + 2);
233+
const dashDashIndex = languageOrDatatype.lastIndexOf('--');
234+
if (dashDashIndex > 0 && dashDashIndex < languageOrDatatype.length) {
235+
languageOrDatatype = {
236+
language: languageOrDatatype.substr(0, dashDashIndex),
237+
direction: languageOrDatatype.substr(dashDashIndex + 2),
238+
};
239+
}
240+
}
241+
else {
242+
languageOrDatatype = factory.namedNode(id.substr(endPos + 3));
243+
}
219244
return factory.literal(id.substr(1, endPos - 1),
220-
id[endPos + 1] === '@' ? id.substr(endPos + 2)
221-
: factory.namedNode(id.substr(endPos + 3)));
245+
languageOrDatatype);
222246
case '[':
223247
id = JSON.parse(id);
224248
break;
@@ -255,7 +279,7 @@ export function termToId(term, nested) {
255279
case 'Variable': return `?${term.value}`;
256280
case 'DefaultGraph': return '';
257281
case 'Literal': return `"${term.value}"${
258-
term.language ? `@${term.language}` :
282+
term.language ? `@${term.language}${term.direction ? `--${term.direction}` : ''}` :
259283
(term.datatype && term.datatype.value !== xsd.string ? `^^${term.datatype.value}` : '')}`;
260284
case 'Quad':
261285
const res = [
@@ -350,6 +374,11 @@ function literal(value, languageOrDataType) {
350374
if (typeof languageOrDataType === 'string')
351375
return new Literal(`"${value}"@${languageOrDataType.toLowerCase()}`);
352376

377+
// Create a language-tagged string with base direction
378+
if (languageOrDataType !== undefined && !('termType' in languageOrDataType)) {
379+
return new Literal(`"${value}"@${languageOrDataType.language.toLowerCase()}--${languageOrDataType.direction.toLowerCase()}`);
380+
}
381+
353382
// Automatically determine datatype for booleans and numbers
354383
let datatype = languageOrDataType ? languageOrDataType.value : '';
355384
if (datatype === '') {

0 commit comments

Comments
 (0)