Lightning fast, asynchronous, streaming RDF for JavaScript
The N3.js library lets you handle RDF in JavaScript easily, in Node.js and the browser.
It offers:
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 – by far the fastest parser in JavaScript
Installation
For Node.js, N3.js comes as an npm package.
$ npm install n3
var N3 = require('n3');
N3.js seamlessly works in browsers. Generate a browser version as follows:
$ cd N3.js
$ npm install
$ npm run browser
<script src="n3-browser.min.js"></script>
In addition, N3.js is fully compatible with browserify,
so you can write code for Node.js and deploy it to browsers.
Triple representation
For maximum performance and ease of use,
triples are simple objects with string properties.
URLs, URIs and IRIs are simple strings. For example, parsing this RDF document:
@prefix c: <http://example.org/cartoons#>.
c:Tom a c:Cat.
results in this JavaScript object:
{
subject: 'http://example.org/cartoons#Tom',
predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
object: 'http://example.org/cartoons#Cat'
}
Literals are represented as double quoted strings. For example, parsing this RDF document:
c:Tom c:name "Tom".
results in this JavaScript object:
{
subject: 'http://example.org/cartoons#Tom',
predicate: 'http://example.org/cartoons#name',
object: '"Tom"'
}
This allows you to create and compare literals fast and easily:
triple.object === 'http://example.org/cartoons#Cat'
triple.object === '"Tom"'
For literals with a language or type, add a marker (@
or ^^
) and the corresponding value as-is:
'"Tom"@en-gb'
'"1"^^http://www.w3.org/2001/XMLSchema#integer'
An optional fourth element signals the graph to which a triple belongs:
{
subject: 'http://example.org/cartoons#Tom',
predicate: 'http://example.org/cartoons#name',
object: '"Tom"',
graph: 'http://example.org/mycartoon'
}
The N3.js Utility (N3.Util
) can help you with these representations.
Parsing
From an RDF document to triples
N3.Parser
transforms Turtle, TriG, N-Triples or N-Quads document into triples through a callback:
var parser = N3.Parser();
parser.parse('@prefix c: <http://example.org/cartoons#>.\n' +
'c:Tom a c:Cat.\n' +
'c:Jerry a c:Mouse;\n' +
' c:smarterThan c:Tom.',
function (error, triple, prefixes) {
if (triple)
console.log(triple.subject, triple.predicate, triple.object, '.');
else
console.log("# That's all, folks!", prefixes)
});
The callback's first argument is an error value, the second is a triple.
If there are no more triples,
the callback is invoked one last time with null
for triple
and a hash of prefixes as third argument.
Pass a second callback to parse
to retrieve prefixes as they are read.
By default, N3.Parser
parses a permissive superset of Turtle, TriG, N-Triples and N-Quads.
For strict compatibility with any of those languages, pass a format
argument upon creation:
var parser1 = N3.Parser({ format: 'N-Triples' });
var parser2 = N3.Parser({ format: 'application/trig' });
From RDF chunks to triples
N3.Parser
can also parse triples from RDF documents arriving in chunks,
for instance, when being downloaded or read from disk.
Use addChunk
to add a piece of data, and end
to signal the end.
var parser = N3.Parser(), triples = [];
parser.parse(function (error, triple, prefixes) { triple && triples.push(triple); });
parser.addChunk('@prefix c: <http://example.org/cartoons#>.\n');
parser.addChunk('c:Tom a ');
parser.addChunk('c:Cat. c:Jerry a');
console.log(triples);
parser.addChunk(' c:Mouse.');
parser.end();
console.log(triples);
From an RDF stream to triples
N3.Parser
can parse Node.js streams as they grow,
returning triples as soon as they're ready.
This behavior sets N3.js apart from most other libraries.
var parser = N3.Parser(),
rdfStream = fs.createReadStream('cartoons.ttl');
parser.parse(rdfStream, console.log);
In addition, N3.StreamParser
offers a Node.js stream implementation,
so you can transform RDF streams and pipe them to anywhere.
This solution is ideal if your consumer is slower,
since source data is only read when the consumer is ready.
var streamParser = N3.StreamParser(),
rdfStream = fs.createReadStream('cartoons.ttl');
rdfStream.pipe(streamParser);
streamParser.pipe(new SlowConsumer());
function SlowConsumer() {
var writer = new require('stream').Writable({ objectMode: true });
writer._write = function (triple, encoding, done) {
console.log(triple);
setTimeout(done, 1000);
};
return writer;
}
A dedicated prefix
event signals every prefix with prefix
and iri
arguments.
Writing
From triples to a string
N3.Writer
serializes triples as an RDF document.
Write triples through addTriple
.
var writer = N3.Writer({ prefixes: { c: 'http://example.org/cartoons#' } });
writer.addTriple('http://example.org/cartoons#Tom',
'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
'http://example.org/cartoons#Cat');
writer.addTriple({
subject: 'http://example.org/cartoons#Tom',
predicate: 'http://example.org/cartoons#name',
object: '"Tom"'
});
writer.end(function (error, result) { console.log(result); });
By default, N3.Writer
writes Turtle (or TriG for triples with a graph
property).
To write N-Triples (or N-Quads) instead, pass a format
argument upon creation:
var writer1 = N3.Writer({ format: 'N-Triples' });
var writer2 = N3.Writer({ format: 'application/trig' });
From triples to an RDF stream
N3.Writer
can also write triples to a Node.js stream.
var writer = N3.Writer(process.stdout, { prefixes: { c: 'http://example.org/cartoons#' } });
writer.addTriple('http://example.org/cartoons#Tom',
'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
'http://example.org/cartoons#Cat');
writer.addTriple({
subject: 'http://example.org/cartoons#Tom',
predicate: 'http://example.org/cartoons#name',
object: '"Tom"'
});
writer.end();
From a triple stream to an RDF stream
N3.StreamWriter
is a writer implementation as a Node.js stream.
var 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);
Blank nodes and lists
You might want to use the […]
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:
var writer = N3.Writer({ prefixes: { c: 'http://example.org/cartoons#',
foaf: 'http://xmlns.com/foaf/0.1/' } });
writer.addTriple(writer.blank('http://xmlns.com/foaf/0.1/givenName', '"Tom"@en'),
'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
'http://example.org/cartoons#Cat');
writer.addTriple('http://example.org/cartoons#Jerry',
'http://xmlns.com/foaf/0.1/knows',
writer.blank([{
predicate: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
object: 'http://example.org/cartoons#Cat'
},{
predicate: 'http://xmlns.com/foaf/0.1/givenName',
object: '"Tom"@en',
}]));
writer.addTriple('http://example.org/cartoons#Mammy',
'http://example.org/cartoons#hasPets',
writer.list([
'http://example.org/cartoons#Tom',
'http://example.org/cartoons#Jerry'
]));
writer.end(function (error, result) { console.log(result); });
Be careful to use the output of blank
and list
only once and only as argument to addTriple
of the same writer,
as return values of these functions are unspecified.
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 a triple with :Mickey
as subject.
var store = N3.Store();
store.addTriple('http://ex.org/Pluto', 'http://ex.org/type', 'http://ex.org/Dog');
store.addTriple('http://ex.org/Mickey', 'http://ex.org/type', 'http://ex.org/Mouse');
var mickey = store.find('http://ex.org/Mickey', null, null)[0];
console.log(mickey.subject, mickey.predicate, mickey.object, '.');
Utility
N3.Util
offers helpers for IRI and literal representations.
As IRIs are most common, they are represented as simple strings:
var N3Util = N3.Util;
N3Util.isIRI('http://example.org/cartoons#Mickey');
Literals are represented as double quoted strings:
N3Util.isLiteral('"Mickey Mouse"');
N3Util.getLiteralValue('"Mickey Mouse"');
N3Util.isLiteral('"Mickey Mouse"@en');
N3Util.getLiteralLanguage('"Mickey Mouse"@en');
N3Util.isLiteral('"3"^^http://www.w3.org/2001/XMLSchema#integer');
N3Util.getLiteralType('"3"^^http://www.w3.org/2001/XMLSchema#integer');
N3Util.isLiteral('"http://example.org/"');
N3Util.getLiteralValue('"http://example.org/"');
Note the difference between 'http://example.org/'
(IRI) and '"http://example.org/"'
(literal).
Also note that the double quoted literals are not raw Turtle/TriG syntax:
N3Util.isLiteral('"This word is "quoted"!"');
N3Util.isLiteral('"3"^^http://www.w3.org/2001/XMLSchema#integer');
The above string represents the string This word is "quoted"!,
even though the correct Turtle/TriG syntax for that is "This word is \"quoted\"!"
N3.js thus always parses literals, but adds quotes to differentiate from IRIs:
new N3.Parser().parse('<a> <b> "This word is \\"quoted\\"!".', console.log);
Literals can be created with createLiteral
:
N3Util.createLiteral('My text', 'en-gb');
N3Util.createLiteral('123', 'http://www.w3.org/2001/XMLSchema#integer');
N3Util.createLiteral(123);
N3Util.createLiteral(false);
Blank nodes start with _:
, and can be tested for as follows:
N3Util.isBlank('_:b1');
N3Util.isIRI('_:b1');
N3Util.isLiteral('_:b1');
Prefixed names can be tested and expanded:
var prefixes = { rdfs: 'http://www.w3.org/2000/01/rdf-schema#' };
N3Util.isPrefixedName('rdfs:label');
N3Util.expandPrefixedName('rdfs:label', prefixes);
Loading the utility globally
For convenience, N3Util
can be loaded globally:
require('n3').Util(global);
isIRI('http://example.org/cartoons#Mickey');
isLiteral('"Mickey Mouse"');
If desired, its methods can even be added directly on all strings:
require('n3').Util(String, true);
'http://example.org/cartoons#Mickey'.isIRI();
'"Mickey Mouse"'.isLiteral();
Compatibility
Specifications
The N3.js parser and writer is fully compatible with the following W3C specifications:
Pass a format
option to the constructor with the name or MIME type of a format
for strict, fault-intolerant behavior.
Note that the library does not support full Notation3 yet
(and a standardized specification for this syntax is currently lacking).
Breaking changes
N3.js 0.4.x introduces the following breaking changes from 0.3.x versions:
- The fourth element of a quad is named
graph
instead of context
. N3.Writer
and N3.Store
constructor options are passed as a hash { prefixes: { … } }
.N3.Util
URI methods such as isUri
are now IRI methods such as isIRI
.
License, status and contributions
The N3.js library is copyrighted by Ruben Verborgh
and released under the MIT License.
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.