Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.
npm install n3The N3.js library is an implementation of the RDF.js low-level specification that lets you handle RDF 1.2 in JavaScript easily.
It offers:
- Parsing triples/quads from
Turtle,
TriG,
N-Triples,
N-Quads,
and Notation3 (N3)
- Writing triples/quads to
Turtle,
TriG,
N-Triples,
and N-Quads
- Storage of triples/quads in memory
Parsing and writing is:
- 🎛 asynchronous – triples arrive as soon as possible
- 🚰 streaming – streams are parsed as data comes in, so you can parse files larger than memory
- ⚡️ fast – triples are flying out at high speeds
``Bash`
$ npm install n3
`JavaScript`
const N3 = require('n3');
N3.js seamlessly works in browsers via webpack
or browserify.
If you're unfamiliar with these tools,
you can read
_webpack: Creating a Bundle – getting started_
or
_Introduction to browserify_.
You will need to create a "UMD bundle" and supply a name (e.g. with the -s N3 option in browserify).
You can also load it via CDN:
`html`
N3.DataFactory will give you the factory functions to create triples and quads:
`JavaScript`
const { DataFactory } = N3;
const { namedNode, literal, defaultGraph, quad } = DataFactory;
const myQuad = quad(
namedNode('https://ruben.verborgh.org/profile/#me'), // Subject
namedNode('http://xmlns.com/foaf/0.1/givenName'), // Predicate
literal('Ruben', 'en'), // Object
defaultGraph(), // Graph
);
console.log(myQuad.termType); // Quad
console.log(myQuad.value); // ''
console.log(myQuad.subject.value); // https://ruben.verborgh.org/profile/#me
console.log(myQuad.object.value); // Ruben
console.log(myQuad.object.datatype.value); // http://www.w3.org/1999/02/22-rdf-syntax-ns#langString
console.log(myQuad.object.language); // en
In the rest of this document, we will treat “triples” and “quads” equally:
we assume that a quad is simply a triple in a named or default graph.
N3.Parser transforms Turtle, TriG, N-Triples, or N-Quads document into quads through a callback:`JavaScriptPREFIX c:
const tomAndJerry =
# Tom is a cat
c:Tom a c:Cat.
c:Jerry a c:Mouse;
c:smarterThan c:Tom.
const parser = new N3.Parser();
parser.parse(tomAndJerry,
(error, quad, prefixes) => {
if (quad)
console.log(quad);
else
console.log("# That's all, folks!", prefixes);
});
`null
The callback's first argument is an optional error value, the second is a quad.
If there are no more quads,
the callback is invoked one last time with for quad
and a hash of prefixes as third argument.
Alternatively, an object can be supplied, where onQuad, onPrefix and onComment are used to listen for quads, prefixes and comments as follows:`JavaScript
const parser = new N3.Parser();
parser.parse(tomAndJerry, {
// onQuad (required) accepts a listener of type (quad: RDF.Quad) => void
onQuad: (err, quad) => { console.log(quad); },
// onPrefix (optional) accepts a listener of type (prefix: string, iri: NamedNode) => void
onPrefix: (prefix, iri) => { console.log(prefix, 'expands to', iri.value); },
// onComment (optional) accepts a listener of type (comment: string) => void
onComment: (comment) => { console.log('#', comment); },
});
`
If no callbacks are provided, parsing happens synchronously returning an array of quads:
`JavaScript
const parser = new N3.Parser();
// An array of resultant Quads
const quadArray = parser.parse(tomAndJerry);
`
By default, N3.Parser parses a permissive superset of Turtle, TriG, N-Triples, and N-Quads.format
For strict compatibility with any of those languages, pass a argument upon creation:
`JavaScript`
const parser1 = new N3.Parser({ format: 'N-Triples' });
const parser2 = new N3.Parser({ format: 'application/trig' });
Notation3 (N3) is supported _only_ through the format argument:
`JavaScript`
const parser3 = new N3.Parser({ format: 'N3' });
const parser4 = new N3.Parser({ format: 'Notation3' });
const parser5 = new N3.Parser({ format: 'text/n3' });
It is possible to provide the base IRI of the document that you want to parse.
This is done by passing a baseIRI argument upon creation:`JavaScript`
const parser = new N3.Parser({ baseIRI: 'http://example.org/' });
By default, N3.Parser will prefix blank node labels with a b{digit}_ prefix.blankNodePrefix
This is done to prevent collisions of unrelated blank nodes having identical
labels. The constructor argument can be used to modify the`
prefix or, if set to an empty string, completely disable prefixing:JavaScript`
const parser = new N3.Parser({ blankNodePrefix: '' });
The parser can output a backwards chaining rule such as _:q <= _:p. in two ways:_:p log:implies _:q.
- as (default)_:q log:isImpliedBy _:p.
- as (when the isImpliedBy flag is set to true)`JavaScript`
const parser = new N3.Parser({ isImpliedBy: true });
N3.Parser can parse Node.js streams as they grow,
returning quads as soon as they're ready.
`JavaScript`
const parser = new N3.Parser(),
rdfStream = fs.createReadStream('cartoons.ttl');
parser.parse(rdfStream, console.log);
N3.StreamParser is a Node.js stream and RDF.js Sink implementation.
This solution is ideal if your consumer is slower,
since source data is only read when the consumer is ready.
`JavaScript
const streamParser = new N3.StreamParser(),
rdfStream = fs.createReadStream('cartoons.ttl');
rdfStream.pipe(streamParser);
streamParser.pipe(new SlowConsumer());
function SlowConsumer() {
const writer = new require('stream').Writable({ objectMode: true });
writer._write = (quad, encoding, done) => {
console.log(quad);
setTimeout(done, 1000);
};
return writer;
}
`
A dedicated prefix event signals every prefix with prefix and term arguments.comment
A dedicated event can be enabled by setting comments: true in the N3.StreamParser constructor.
N3.Writer serializes quads as an RDF document.addQuad
Write quads through .
`JavaScriptc
const writer = new N3.Writer({ prefixes: { c: 'http://example.org/cartoons#' } }); // Create a writer which uses as a prefix for the namespace http://example.org/cartoons#`
writer.addQuad(quad(
namedNode('http://example.org/cartoons#Tom'), // Subject
namedNode('http://example.org/cartoons#name'), // Predicate
literal('Tom') // Object
));
writer.end((error, result) => console.log(result));
By default, N3.Writer writes Turtle (or TriG if some quads are in a named graph).format
To write N-Triples (or N-Quads) instead, pass a argument upon creation:
`JavaScript`
const writer1 = new N3.Writer({ format: 'N-Triples' });
const writer2 = new N3.Writer({ format: 'application/trig' });
N3.Writer can also write quads to a Node.js stream through addQuad.
`JavaScript`
const writer = new N3.Writer(process.stdout, { end: false, prefixes: { c: 'http://example.org/cartoons#' } });
writer.addQuad(
namedNode('http://example.org/cartoons#Tom'), // Subject
namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), // Predicate
namedNode('http://example.org/cartoons#Cat') // Object
);
writer.addQuad(quad(
namedNode('http://example.org/cartoons#Tom'), // Subject
namedNode('http://example.org/cartoons#name'), // Predicate
literal('Tom') // Object
));
writer.end();
N3.StreamWriter is a Node.js stream and RDF.js Sink implementation.
`JavaScript`
const streamParser = new N3.StreamParser(),
inputStream = fs.createReadStream('cartoons.ttl'),
streamWriter = new N3.StreamWriter({ prefixes: { c: 'http://example.org/cartoons#' } });
inputStream.pipe(streamParser);
streamParser.pipe(streamWriter);
streamWriter.pipe(process.stdout);
and list (…) notations of Turtle and TriG.
However, a streaming writer cannot create these automatically:
the shorthand notations are only possible if blank nodes or list heads are not used later on,
which can only be determined conclusively at the end of the stream.The
blank and list functions allow you to create them manually instead:
`JavaScript
const writer = new N3.Writer({ prefixes: { c: 'http://example.org/cartoons#',
foaf: 'http://xmlns.com/foaf/0.1/' } });
writer.addQuad(
writer.blank(
namedNode('http://xmlns.com/foaf/0.1/givenName'),
literal('Tom', 'en')),
namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
namedNode('http://example.org/cartoons#Cat')
);
writer.addQuad(quad(
namedNode('http://example.org/cartoons#Jerry'),
namedNode('http://xmlns.com/foaf/0.1/knows'),
writer.blank([{
predicate: namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
object: namedNode('http://example.org/cartoons#Cat'),
},{
predicate: namedNode('http://xmlns.com/foaf/0.1/givenName'),
object: literal('Tom', 'en'),
}])
));
writer.addQuad(
namedNode('http://example.org/cartoons#Mammy'),
namedNode('http://example.org/cartoons#hasPets'),
writer.list([
namedNode('http://example.org/cartoons#Tom'),
namedNode('http://example.org/cartoons#Jerry'),
])
);
writer.end((error, result) => console.log(result));
`Storing
N3.Store allows you to store triples in memory and find them fast.In this example, we create a new store and add the triples
:Pluto a :Dog. and :Mickey a :Mouse.
Then, we find triples with :Mickey as subject.`JavaScript
const store = new N3.Store();
store.add(
quad(
namedNode('http://ex.org/Pluto'),
namedNode('http://ex.org/type'),
namedNode('http://ex.org/Dog')
)
);
store.add(
quad(
namedNode('http://ex.org/Mickey'),
namedNode('http://ex.org/type'),
namedNode('http://ex.org/Mouse')
)
);// Retrieve all quads
for (const quad of store)
console.log(quad);
// Retrieve Mickey's quads
for (const quad of store.match(namedNode('http://ex.org/Mickey'), null, null))
console.log(quad);
`If you are using multiple stores, you can reduce memory consumption by allowing them to share an entity index:
`JavaScript
const entityIndex = new N3.EntityIndex();
const store1 = new N3.Store([], { entityIndex });
const store2 = new N3.Store([], { entityIndex });
`$3
This store adheres to the Dataset interface which exposes the following propertiesAttributes:
-
size — A non-negative integer that specifies the number of quads in the set.Methods:
-
add — Adds the specified quad to the dataset. Existing quads, as defined in Quad.equals, will be ignored.
- delete — Removes the specified quad from the dataset.
- has — Determines whether a dataset includes a certain quad.
- match — Returns a new dataset that is comprised of all quads in the current instance matching the given arguments.
- [Symbol.iterator] — Implements the iterator protocol to allow iteration over all quads in the dataset as in the example above.$3
The store implements the following manipulation methods in addition to the standard Dataset Interface
(documentation):
- addQuad to insert one quad
- addQuads to insert an array of quads
- removeQuad to remove one quad
- removeQuads to remove an array of quads
- remove to remove a stream of quads
- removeMatches to remove all quads matching the given pattern
- deleteGraph to remove all quads with the given graph
- createBlankNode returns an unused blank node identifier$3
The store provides the following search methods
(documentation):
- match returns a stream and generator of quads matching the given pattern
- getQuads returns an array of quads matching the given pattern
- countQuads counts the number of quads matching the given pattern
- forEach executes a callback on all matching quads
- every returns whether a callback on matching quads always returns true
- some returns whether a callback on matching quads returns true at least once
- getSubjects returns an array of unique subjects occurring in matching quads
- forSubjects executes a callback on unique subjects occurring in matching quads
- getPredicates returns an array of unique predicates occurring in matching quad
- forPredicates executes a callback on unique predicates occurring in matching quads
- getObjects returns an array of unique objects occurring in matching quad
- forObjects executes a callback on unique objects occurring in matching quads
- getGraphs returns an array of unique graphs occurring in matching quad
- forGraphs executes a callback on unique graphs occurring in matching quadsReasoning
N3.js supports reasoning as follows:
`JavaScript
import { Reasoner, Store, Parser } from 'n3';const parser = new Parser({ format: 'text/n3' });
const rules =
const rulesDataset = new Store(parser.parse(rules));
const dataset = new Store(/ Dataset /)
// Applies the rules to the store; mutating it
const reasoner = new Reasoner(store);
reasoner.reason(rulesDataset);
`Note: N3.js currently only supports rules with Basic Graph Patterns in the premise and conclusion. Built-ins and backward-chaining are not supported. For an RDF/JS reasoner that supports all Notation3 reasoning features, see eye-js.
Compatibility
$3
The N3.js parser and writer is fully compatible with the following W3C specifications:
- RDFÂ 1.1 Turtle
– EARL report
- RDFÂ 1.1 TriG
– EARL report
- RDFÂ 1.1 N-Triples
– EARL report
- RDFÂ 1.1 N-Quads
– EARL report
- RDFÂ 1.2 Turtle
- RDFÂ 1.2 TriG
- RDFÂ 1.2 N-Triples
- RDFÂ 1.2 N-QuadsIn addition, the N3.js parser also supports Notation3 (N3) (no official specification yet).
The default mode is permissive
and allows a mixture of different syntaxes.
Pass aÂ
format option to the constructor with the name or MIME type of a format
for strict, fault-intolerant behavior.$3
The N3.js submodules are compatible with the following RDF.js interfaces:-
N3.DataFactory implements
DataFactory
- the terms it creates implement Term
and one of
NamedNode,
BlankNode,
Literal,
Variable,
DefaultGraph
- the triples/quads it creates implement
Term,
Triple
and
Quad
- N3.StreamParser implements
Stream
and
Sink
- N3.StreamWriter implements
Stream
and
Sink
- N3.Store implements
Store
Source
Sink
DatasetCore`Contributions are welcome, and bug reports or pull requests are always helpful.
If you plan to implement a larger feature, it's best to contact me first.