Comparing version 0.3.6 to 0.4.0
@@ -17,5 +17,22 @@ // **N3Lexer** tokenizes N3 documents. | ||
// ## Constructor | ||
function N3Lexer() { | ||
function N3Lexer(options) { | ||
if (!(this instanceof N3Lexer)) | ||
return new N3Lexer(); | ||
return new N3Lexer(options); | ||
// In line mode (N-Triples or N-Quads), only simple features may be parsed | ||
if (options && options.lineMode) { | ||
// Don't tokenize special literals | ||
this._tripleQuotedString = this._number = this._boolean = /$0^/; | ||
// Swap the tokenize method for a restricted version | ||
var self = this; | ||
this._tokenize = this.tokenize; | ||
this.tokenize = function (input, callback) { | ||
this._tokenize(input, function (error, token) { | ||
if (!error && /IRI|prefixed|literal|langcode|type|\.|eof/.test(token.type)) | ||
callback && callback(error, token); | ||
else | ||
callback && callback(error || self._syntaxError(token.type, callback = null)); | ||
}); | ||
}; | ||
} | ||
} | ||
@@ -33,8 +50,9 @@ | ||
_langcode: /^@([a-z]+(?:-[a-z0-9]+)*)(?=[^a-z0-9\-])/i, | ||
_prefix: /^((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:[\.\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:(?=\s|<)/, | ||
_prefixedName: /^((?:[A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:[\.\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:((?:(?:[0-:A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])(?:(?:[\.\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])*(?:[\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~]))?)?)(?=[,;\s#()\[\]]|\.[\s#()\[\]<"'])/, | ||
_number: /^[\-+]?(?:\d+\.?\d*([eE](?:[\-\+])?\d+)|\d*\.?\d+)(?=[.,;\s#()\[\]])/, | ||
_boolean: /^(?:true|false)(?=[.,;\s#()\[\]])/, | ||
_keyword: /^@[a-z]+(?=\s)/, | ||
_sparqlKeyword: /^(?:PREFIX|BASE)(?=\s)/i, | ||
_prefix: /^((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:(?=[#\s<])/, | ||
_prefixed: /^((?:[A-Za-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)?:((?:(?:[0-:A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])(?:(?:[\.\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~])*(?:[\-0-:A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff]|%[0-9a-fA-F]{2}|\\[!#-\/;=?\-@_~]))?)?)(?=\.?[,;\s#()\[\]\{\}"'<])/, | ||
_blank: /^_:((?:[0-9A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?=\.?[,;:\s#()\[\]\{\}"'<])/, | ||
_number: /^[\-+]?(?:\d+\.?\d*([eE](?:[\-\+])?\d+)|\d*\.?\d+)(?=[.,;:\s#()\[\]\{\}"'<])/, | ||
_boolean: /^(?:true|false)(?=[.,;:\s#()\[\]\{\}"'<])/, | ||
_keyword: /^@[a-z]+(?=[\s#<:])/, | ||
_sparqlKeyword: /^(?:PREFIX|BASE|GRAPH)(?=[\s#<:])/i, | ||
_shortPredicates: /^a(?=\s+|<)/, | ||
@@ -45,3 +63,2 @@ _newline: /^[ \t]*(?:#[^\n\r]*)?(?:\r\n|\n|\r)[ \t]*/, | ||
// ## Private methods | ||
@@ -103,2 +120,14 @@ | ||
case '_': | ||
// Try to find a blank node. Since it can contain (but not end with) a dot, | ||
// we always need a non-dot character before deciding it is a prefixed name. | ||
// Therefore, try inserting a space if we're at the end of the input. | ||
if ((match = this._blank.exec(input)) || | ||
inputFinished && (match = this._blank.exec(input + ' '))) { | ||
type = 'prefixed'; | ||
prefix = '_'; | ||
value = match[1]; | ||
} | ||
break; | ||
case '"': | ||
@@ -177,2 +206,4 @@ case "'": | ||
case 'P': | ||
case 'G': | ||
case 'g': | ||
// Try to find a SPARQL-style keyword. | ||
@@ -212,2 +243,4 @@ if (match = this._sparqlKeyword.exec(input)) | ||
case ')': | ||
case '{': | ||
case '}': | ||
// The next token is punctuation | ||
@@ -233,4 +266,4 @@ matchLength = 1; | ||
// Therefore, try inserting a space if we're at the end of the input. | ||
else if ((match = this._prefixedName.exec(input)) || | ||
inputFinished && (match = this._prefixedName.exec(input + ' '))) { | ||
else if ((match = this._prefixed.exec(input)) || | ||
inputFinished && (match = this._prefixed.exec(input + ' '))) { | ||
type = 'prefixed'; | ||
@@ -266,10 +299,6 @@ prefix = match[1] || ''; | ||
// Signals the syntax error through the callback | ||
function reportSyntaxError(self) { | ||
self._input = null; | ||
match = /^\S*/.exec(input); | ||
callback(new Error('Syntax error: unexpected "' + match[0] + '" on line ' + self._line + '.')); | ||
} | ||
function reportSyntaxError(self) { callback(self._syntaxError(/^\S*/.exec(input)[0])); } | ||
}, | ||
// ### `unescape` replaces N3 escape codes by their corresponding characters. | ||
// ### `_unescape` replaces N3 escape codes by their corresponding characters. | ||
_unescape: function (item) { | ||
@@ -288,3 +317,3 @@ try { | ||
if (charCode < 0xFFFF) return fromCharCode(charCode); | ||
return fromCharCode(0xD800 + ((charCode -= 0x10000) >> 10), 0xDC00 + (charCode & 0x3FF)); | ||
return fromCharCode(0xD800 + ((charCode -= 0x10000) / 0x400), 0xDC00 + (charCode & 0x3FF)); | ||
} | ||
@@ -302,3 +331,9 @@ else { | ||
// ### `_syntaxError` creates a syntax error for the given issue | ||
_syntaxError: function (issue) { | ||
this._input = null; | ||
return new Error('Syntax error: unexpected "' + issue + '" on line ' + this._line + '.'); | ||
}, | ||
// ## Public methods | ||
@@ -305,0 +340,0 @@ |
@@ -9,32 +9,47 @@ // **N3Parser** parses N3 documents. | ||
var absoluteURI = /:/, | ||
var absoluteIRI = /:/, | ||
documentPart = /[^\/]*$/, | ||
rootURI = /^(?:[^:]+:\/*)?[^\/]*/; | ||
rootIRI = /^(?:[^:]+:\/*)?[^\/]*/; | ||
// The next ID for new blank nodes | ||
var blankNodeCount = 0; | ||
var blankNodePrefix = 0, blankNodeCount = 0; | ||
// ## Constructor | ||
function N3Parser(config) { | ||
function N3Parser(options) { | ||
if (!(this instanceof N3Parser)) | ||
return new N3Parser(config); | ||
// Initialize the parser settings | ||
config = config || {}; | ||
return new N3Parser(options); | ||
this._tripleStack = []; | ||
this._lexer = config.lexer || new N3Lexer(); | ||
this._blankNodes = Object.create(null); | ||
this._graph = null; | ||
// Set the document URI | ||
if (!config.documentURI) { | ||
this._baseURI = null; | ||
this._baseURIPath = null; | ||
// Set the document IRI. | ||
options = options || {}; | ||
if (!options.documentIRI) { | ||
this._baseIRI = null; | ||
this._baseIRIPath = null; | ||
} | ||
else { | ||
if (config.documentURI.indexOf('#') > 0) | ||
throw new Error('Invalid document URI'); | ||
this._baseURI = config.documentURI; | ||
this._baseURIPath = this._baseURI.replace(documentPart, ''); | ||
this._baseURIRoot = this._baseURI.match(rootURI)[0]; | ||
if (options.documentIRI.indexOf('#') > 0) | ||
throw new Error('Invalid document IRI'); | ||
this._baseIRI = options.documentIRI; | ||
this._baseIRIPath = this._baseIRI.replace(documentPart, ''); | ||
this._baseIRIRoot = this._baseIRI.match(rootIRI)[0]; | ||
} | ||
// Set supported features depending on the format. | ||
var format = (typeof options.format === 'string') && options.format.match(/\w*$/)[0].toLowerCase(), | ||
isTurtle = format === 'turtle', isTriG = format === 'trig', | ||
isNTriples = /triple/.test(format), isNQuads = /quad/.test(format), | ||
isLineMode = isNTriples || isNQuads; | ||
if (!(this._supportsNamedGraphs = !isTurtle)) | ||
this._readPredicateOrNamedGraph = this._readPredicate; | ||
this._supportsQuads = !(isTurtle || isTriG || isNTriples); | ||
// Disable relative IRIs in N-Triples or N-Quads mode | ||
if (isLineMode) { | ||
this._baseIRI = ''; | ||
this._resolveIRI = function (token) { | ||
this._error('Disallowed relative IRI', token); | ||
return this._callback = noop, this._subject = null; | ||
}; | ||
} | ||
this._lexer = options.lexer || new N3Lexer({ lineMode: isLineMode }); | ||
} | ||
@@ -46,3 +61,3 @@ | ||
N3Parser._resetBlankNodeIds = function () { | ||
blankNodeCount = 0; | ||
blankNodePrefix = blankNodeCount = 0; | ||
}; | ||
@@ -58,2 +73,5 @@ | ||
case 'eof': | ||
if (this._graph !== null) | ||
return this._error('Unclosed graph', token); | ||
delete this._prefixes._; | ||
return this._callback(null, null, this._prefixes); | ||
@@ -70,6 +88,17 @@ // It could be a prefix declaration. | ||
this._sparqlStyle = false; | ||
return this._readBaseURI; | ||
return this._readBaseIRI; | ||
case 'BASE': | ||
this._sparqlStyle = true; | ||
return this._readBaseURI; | ||
return this._readBaseIRI; | ||
// It could be a graph. | ||
case '{': | ||
if (this._supportsNamedGraphs) { | ||
this._graph = ''; | ||
this._subject = null; | ||
return this._readSubject; | ||
} | ||
case 'GRAPH': | ||
if (this._supportsNamedGraphs) { | ||
return this._readNamedGraphLabel; | ||
} | ||
// Otherwise, the next token must be a subject. | ||
@@ -83,20 +112,15 @@ default: | ||
_readSubject: function (token) { | ||
this._predicate = null; | ||
switch (token.type) { | ||
case 'IRI': | ||
if (this._baseURI === null || absoluteURI.test(token.value)) | ||
if (this._baseIRI === null || absoluteIRI.test(token.value)) | ||
this._subject = token.value; | ||
else | ||
this._subject = this._resolveURI(token.value); | ||
this._subject = this._resolveIRI(token); | ||
break; | ||
case 'prefixed': | ||
if (token.prefix === '_') { | ||
this._subject = this._blankNodes[token.value] || | ||
(this._blankNodes[token.value] = '_:b' + blankNodeCount++); | ||
} | ||
else { | ||
var prefix = this._prefixes[token.prefix]; | ||
if (prefix === undefined) | ||
return this._error('Undefined prefix "' + token.prefix + ':"', token); | ||
this._subject = prefix + token.value; | ||
} | ||
var prefix = this._prefixes[token.prefix]; | ||
if (prefix === undefined) | ||
return this._error('Undefined prefix "' + token.prefix + ':"', token); | ||
this._subject = prefix + token.value; | ||
break; | ||
@@ -113,8 +137,10 @@ case '[': | ||
return this._readListItem; | ||
case '}': | ||
return this._readPunctuation(token); | ||
default: | ||
return this._error('Expected subject but got ' + token.type, token); | ||
} | ||
this._subjectHasPredicate = false; | ||
// The next token must be a predicate. | ||
return this._readPredicate; | ||
// The next token must be a predicate, | ||
// or, if the subject was actually a graph IRI, a named graph. | ||
return this._readPredicateOrNamedGraph; | ||
}, | ||
@@ -127,6 +153,6 @@ | ||
case 'abbreviation': | ||
if (this._baseURI === null || absoluteURI.test(token.value)) | ||
if (this._baseIRI === null || absoluteIRI.test(token.value)) | ||
this._predicate = token.value; | ||
else | ||
this._predicate = this._resolveURI(token.value); | ||
this._predicate = this._resolveIRI(token); | ||
break; | ||
@@ -144,11 +170,10 @@ case 'prefixed': | ||
break; | ||
case '.': | ||
case ']': | ||
case '}': | ||
// Expected predicate didn't come, must have been trailing semicolon. | ||
return this._readBlankNodeTail(token, true); | ||
case '.': | ||
// A dot is not allowed if the subject did not have a predicate yet | ||
if (!this._subjectHasPredicate) | ||
return this._error('Unexpected dot', token); | ||
// Expected predicate didn't come, must have been trailing semicolon. | ||
return this._readPunctuation(token, true); | ||
if (this._predicate === null) | ||
return this._error('Unexpected ' + token.type, token); | ||
this._subject = null; | ||
return this._readBlankNodeTail(token); | ||
case ';': | ||
@@ -160,3 +185,2 @@ // Extra semicolons can be safely ignored | ||
} | ||
this._subjectHasPredicate = true; | ||
// The next token must be an object. | ||
@@ -170,18 +194,12 @@ return this._readObject; | ||
case 'IRI': | ||
if (this._baseURI === null || absoluteURI.test(token.value)) | ||
if (this._baseIRI === null || absoluteIRI.test(token.value)) | ||
this._object = token.value; | ||
else | ||
this._object = this._resolveURI(token.value); | ||
this._object = this._resolveIRI(token); | ||
break; | ||
case 'prefixed': | ||
if (token.prefix === '_') { | ||
this._object = this._blankNodes[token.value] || | ||
(this._blankNodes[token.value] = '_:b' + blankNodeCount++); | ||
} | ||
else { | ||
var prefix = this._prefixes[token.prefix]; | ||
if (prefix === undefined) | ||
return this._error('Undefined prefix "' + token.prefix + ':"', token); | ||
this._object = prefix + token.value; | ||
} | ||
var prefix = this._prefixes[token.prefix]; | ||
if (prefix === undefined) | ||
return this._error('Undefined prefix "' + token.prefix + ':"', token); | ||
this._object = prefix + token.value; | ||
break; | ||
@@ -208,12 +226,28 @@ case 'literal': | ||
// ### `_readPredicateOrNamedGraph` reads a triple's predicate, or a named graph. | ||
_readPredicateOrNamedGraph: function (token) { | ||
return token.type === '{' ? this._readGraph(token) : this._readPredicate(token); | ||
}, | ||
// ### `_readGraph` reads a graph. | ||
_readGraph: function (token) { | ||
if (token.type !== '{') | ||
return this._error('Expected graph but got ' + token.type, token); | ||
// The "subject" we read is actually the GRAPH's label | ||
this._graph = this._subject, this._subject = null; | ||
return this._readSubject; | ||
}, | ||
// ### `_readBlankNodeHead` reads the head of a blank node. | ||
_readBlankNodeHead: function (token) { | ||
if (token.type === ']') | ||
return this._readBlankNodeTail(token, true); | ||
else | ||
return this._readPredicate(token); | ||
if (token.type === ']') { | ||
this._subject = null; | ||
return this._readBlankNodeTail(token); | ||
} | ||
this._predicate = null; | ||
return this._readPredicate(token); | ||
}, | ||
// ### `_readBlankNodeTail` reads the end of a blank node. | ||
_readBlankNodeTail: function (token, empty) { | ||
_readBlankNodeTail: function (token) { | ||
if (token.type !== ']') | ||
@@ -223,7 +257,7 @@ return this._readPunctuation(token); | ||
// Store blank node triple. | ||
if (empty !== true) | ||
this._callback(null, { subject: this._subject, | ||
if (this._subject !== null) | ||
this._callback(null, { subject: this._subject, | ||
predicate: this._predicate, | ||
object: this._object, | ||
context: 'n3/contexts#default' }); | ||
object: this._object, | ||
graph: this._graph || '' }); | ||
@@ -241,3 +275,4 @@ // Restore parent triple that contains the blank node. | ||
// The blank node was the subject, so continue reading the predicate. | ||
return this._readPredicate; | ||
// If the blank node didn't contain any predicates, it could also be the label of a named graph. | ||
return this._predicate !== null ? this._readPredicate : this._readPredicateOrNamedGraph; | ||
}, | ||
@@ -251,3 +286,6 @@ | ||
if (token.prefix === '') { | ||
value = token.value; | ||
if (this._baseIRI === null || absoluteIRI.test(token.value)) | ||
value = token.value; | ||
else | ||
value = this._resolveIRI(token); | ||
} | ||
@@ -284,12 +322,6 @@ else { | ||
case 'prefixed': | ||
if (token.prefix === '_') { | ||
item = this._blankNodes[token.value] || | ||
(this._blankNodes[token.value] = '_:b' + blankNodeCount++); | ||
} | ||
else { | ||
var prefix = this._prefixes[token.prefix]; | ||
if (prefix === undefined) | ||
return this._error('Undefined prefix "' + token.prefix + ':"', token); | ||
item = prefix + token.value; | ||
} | ||
var prefix = this._prefixes[token.prefix]; | ||
if (prefix === undefined) | ||
return this._error('Undefined prefix "' + token.prefix + ':"', token); | ||
item = prefix + token.value; | ||
break; | ||
@@ -319,8 +351,8 @@ case 'literal': | ||
// If this list is contained within a parent list, return the membership triple here. | ||
// This will be `<parent list elemen>t rdf:first <this list>.`. | ||
// This will be `<parent list element> rdf:first <this list>.`. | ||
if (stack.length !== 0 && stack[stack.length - 1].type === 'list') | ||
this._callback(null, { subject: parentTriple.subject, | ||
this._callback(null, { subject: parentTriple.subject, | ||
predicate: parentTriple.predicate, | ||
object: parentTriple.object, | ||
context: 'n3/contexts#default' }); | ||
object: parentTriple.object, | ||
graph: this._graph || '' }); | ||
// Restore the parent triple's subject. | ||
@@ -367,13 +399,13 @@ this._subject = parentTriple.subject; | ||
// The rest of the list is in the current head. | ||
this._callback(null, { subject: prevItemHead, | ||
this._callback(null, { subject: prevItemHead, | ||
predicate: RDF_REST, | ||
object: itemHead, | ||
context: 'n3/contexts#default' }); | ||
object: itemHead, | ||
graph: this._graph || '' }); | ||
} | ||
// Add the item's value. | ||
if (item !== null) | ||
this._callback(null, { subject: itemHead, | ||
this._callback(null, { subject: itemHead, | ||
predicate: RDF_FIRST, | ||
object: item, | ||
context: 'n3/contexts#default' }); | ||
object: item, | ||
graph: this._graph || '' }); | ||
return next; | ||
@@ -383,7 +415,13 @@ }, | ||
// ### `_readPunctuation` reads punctuation between triples or triple parts. | ||
_readPunctuation: function (token, empty) { | ||
var next; | ||
_readPunctuation: function (token) { | ||
var next, subject = this._subject, graph = this._graph; | ||
switch (token.type) { | ||
// A closing brace ends a graph | ||
case '}': | ||
if (this._graph === null) | ||
return this._error('Unexpected graph closing', token); | ||
this._graph = null; | ||
// A dot just ends the statement, without sharing anything with the next. | ||
case '.': | ||
this._subject = null; | ||
next = this._readInTopContext; | ||
@@ -399,2 +437,23 @@ break; | ||
break; | ||
// An IRI means this is a quad (only allowed if not already inside a graph). | ||
case 'IRI': | ||
if (this._supportsQuads && this._graph === null) { | ||
if (this._baseIRI === null || absoluteIRI.test(token.value)) | ||
graph = token.value; | ||
else | ||
graph = this._resolveIRI(token); | ||
subject = this._subject; | ||
next = this._readQuadPunctuation; | ||
break; | ||
} | ||
// An prefixed name means this is a quad (only allowed if not already inside a graph). | ||
case 'prefixed': | ||
if (this._supportsQuads && this._graph === null) { | ||
var prefix = this._prefixes[token.prefix]; | ||
if (prefix === undefined) | ||
return this._error('Undefined prefix "' + token.prefix + ':"', token); | ||
graph = prefix + token.value; | ||
next = this._readQuadPunctuation; | ||
break; | ||
} | ||
default: | ||
@@ -404,10 +463,17 @@ return this._error('Expected punctuation to follow "' + this._object + '"', token); | ||
// A triple has been completed now, so return it. | ||
if (!empty) | ||
this._callback(null, { subject: this._subject, | ||
if (subject !== null) | ||
this._callback(null, { subject: subject, | ||
predicate: this._predicate, | ||
object: this._object, | ||
context: 'n3/contexts#default' }); | ||
object: this._object, | ||
graph: graph || '' }); | ||
return next; | ||
}, | ||
// ### `_readQuadPunctuation` reads punctuation after a quad. | ||
_readQuadPunctuation: function (token) { | ||
if (token.type !== '.') | ||
return this._error('Expected dot to follow quad', token); | ||
return this._readInTopContext; | ||
}, | ||
// ### `_readPrefix` reads the prefix of a prefix declaration. | ||
@@ -418,34 +484,55 @@ _readPrefix: function (token) { | ||
this._prefix = token.value; | ||
return this._readPrefixURI; | ||
return this._readPrefixIRI; | ||
}, | ||
// ### `_readPrefixURI` reads the URI of a prefix declaration. | ||
_readPrefixURI: function (token) { | ||
// ### `_readPrefixIRI` reads the IRI of a prefix declaration. | ||
_readPrefixIRI: function (token) { | ||
if (token.type !== 'IRI') | ||
return this._error('Expected IRI to follow prefix "' + this._prefix + ':"', token); | ||
var prefixURI; | ||
if (this._baseURI === null || absoluteURI.test(token.value)) | ||
prefixURI = token.value; | ||
var prefixIRI; | ||
if (this._baseIRI === null || absoluteIRI.test(token.value)) | ||
prefixIRI = token.value; | ||
else | ||
prefixURI = this._resolveURI(token.value); | ||
this._prefixes[this._prefix] = prefixURI; | ||
this._prefixCallback(this._prefix, prefixURI); | ||
prefixIRI = this._resolveIRI(token); | ||
this._prefixes[this._prefix] = prefixIRI; | ||
this._prefixCallback(this._prefix, prefixIRI); | ||
return this._readDeclarationPunctuation; | ||
}, | ||
// ### `_readBaseURI` reads the URI of a base declaration. | ||
_readBaseURI: function (token) { | ||
// ### `_readBaseIRI` reads the IRI of a base declaration. | ||
_readBaseIRI: function (token) { | ||
if (token.type !== 'IRI') | ||
return this._error('Expected IRI to follow base declaration', token); | ||
if (token.value.indexOf('#') > 0) | ||
return this._error('Invalid base URI', token); | ||
if (this._baseURI === null || absoluteURI.test(token.value)) | ||
this._baseURI = token.value; | ||
return this._error('Invalid base IRI', token); | ||
if (this._baseIRI === null || absoluteIRI.test(token.value)) | ||
this._baseIRI = token.value; | ||
else | ||
this._baseURI = this._resolveURI(token.value); | ||
this._baseURIPath = this._baseURI.replace(documentPart, ''); | ||
this._baseURIRoot = this._baseURI.match(rootURI)[0]; | ||
this._baseIRI = this._resolveIRI(token); | ||
this._baseIRIPath = this._baseIRI.replace(documentPart, ''); | ||
this._baseIRIRoot = this._baseIRI.match(rootIRI)[0]; | ||
return this._readDeclarationPunctuation; | ||
}, | ||
// ### `_readNamedGraphLabel` reads the label of a named graph. | ||
_readNamedGraphLabel: function (token) { | ||
switch (token.type) { | ||
case 'IRI': | ||
case 'prefixed': | ||
return this._readSubject(token), this._readGraph; | ||
case '[': | ||
return this._readNamedGraphBlankLabel; | ||
default: | ||
return this._error('Invalid graph label', token); | ||
} | ||
}, | ||
// ### `_readNamedGraphLabel` reads a blank node label of a named graph. | ||
_readNamedGraphBlankLabel: function (token) { | ||
if (token.type !== ']') | ||
return this._error('Invalid graph label', token); | ||
this._subject = '_:b' + blankNodeCount++; | ||
return this._readGraph; | ||
}, | ||
// ### `_readDeclarationPunctuation` reads the punctuation of a declaration. | ||
@@ -481,20 +568,21 @@ _readDeclarationPunctuation: function (token) { | ||
// ### `_resolveURI` resolves a URI against a base path | ||
_resolveURI: function (uri) { | ||
switch (uri[0]) { | ||
// An empty relative URI indicates the base URI | ||
// ### `_resolveIRI` resolves an IRI token against the base path | ||
_resolveIRI: function (token) { | ||
var iri = token.value; | ||
switch (iri[0]) { | ||
// An empty relative IRI indicates the base IRI | ||
case undefined: | ||
return this._baseURI; | ||
// Resolve relative fragment URIs against the base URI | ||
return this._baseIRI; | ||
// Resolve relative fragment IRIs against the base IRI | ||
case '#': | ||
return this._baseURI + uri; | ||
// Resolve relative query string URIs by replacing the query string | ||
return this._baseIRI + iri; | ||
// Resolve relative query string IRIs by replacing the query string | ||
case '?': | ||
return this._baseURI.replace(/(?:\?.*)?$/, uri); | ||
// Resolve root relative URIs at the root of the base URI | ||
return this._baseIRI.replace(/(?:\?.*)?$/, iri); | ||
// Resolve root relative IRIs at the root of the base IRI | ||
case '/': | ||
return this._baseURIRoot + uri; | ||
// Resolve all other URIs at the base URI's path | ||
return this._baseIRIRoot + iri; | ||
// Resolve all other IRIs at the base IRI's path | ||
default: | ||
return this._baseURIPath + uri; | ||
return this._baseIRIPath + iri; | ||
} | ||
@@ -510,3 +598,4 @@ }, | ||
this._readCallback = this._readInTopContext; | ||
this._prefixes = {}; | ||
this._prefixes = Object.create(null); | ||
this._prefixes._ = '_:b' + blankNodePrefix++ + '_'; | ||
@@ -524,8 +613,6 @@ // If the input argument is not given, shift parameters | ||
this._lexer.tokenize(input, function (error, token) { | ||
if (self._readCallback !== undefined) { | ||
if (error !== null) | ||
self._callback(error); | ||
else | ||
self._readCallback = self._readCallback(token); | ||
} | ||
if (error !== null) | ||
self._callback(error), self._callback = noop; | ||
else if (self._readCallback !== undefined) | ||
self._readCallback = self._readCallback(token); | ||
}); | ||
@@ -532,0 +619,0 @@ |
@@ -1,2 +0,2 @@ | ||
// **N3Store** objects store N3 triples with an associated context in memory. | ||
// **N3Store** objects store N3 triples by graph in memory. | ||
@@ -6,14 +6,14 @@ var expandPrefixedName = require('./N3Util').expandPrefixedName; | ||
// ## Constructor | ||
function N3Store(triples, prefixes) { | ||
function N3Store(triples, options) { | ||
if (!(this instanceof N3Store)) | ||
return new N3Store(triples, prefixes); | ||
return new N3Store(triples, options); | ||
// The number of triples is initially zero. | ||
this._size = 0; | ||
// `_contexts` contains subject, predicate, and object indexes per context. | ||
this._contexts = Object.create(null); | ||
// `_graphs` contains subject, predicate, and object indexes per graph. | ||
this._graphs = Object.create(null); | ||
// `_entities` maps entities such as `http://xmlns.com/foaf/0.1/name` to numbers. | ||
// This saves memory, since only the numbers have to be stored in `_contexts`. | ||
// This saves memory, since only the numbers have to be stored in `_graphs`. | ||
this._entities = Object.create(null); | ||
this._entities['>>____unused_item_to_make_first_entity_key_non-falsy____<<'] = 0; | ||
this._entities['><'] = 0; // Dummy entry, so the first actual key is non-zero | ||
this._entityCount = 0; | ||
@@ -24,9 +24,11 @@ // `_blankNodeIndex` is the index of the last created blank node that was automatically named | ||
// Shift parameters if `triples` is not given | ||
if (!prefixes && triples && !triples[0]) | ||
prefixes = triples, triples = null; | ||
if (!options && triples && !triples[0]) | ||
options = triples, triples = null; | ||
// Add triples and prefixes if passed | ||
this._prefixes = Object.create(null); | ||
triples && this.addTriples(triples); | ||
prefixes && this.addPrefixes(prefixes); | ||
if (options && options.prefixes) | ||
this.addPrefixes(options.prefixes); | ||
if (triples) | ||
this.addTriples(triples); | ||
} | ||
@@ -37,7 +39,2 @@ | ||
// `defaultContext` is the default context wherein triples are stored. | ||
get defaultContext() { | ||
return 'n3/contexts#default'; | ||
}, | ||
// ### `size` returns the number of triples in the store. | ||
@@ -51,5 +48,5 @@ get size() { | ||
// Calculate the number of triples by counting to the deepest level. | ||
var contexts = this._contexts, subjects, subject; | ||
for (var contextKey in contexts) | ||
for (var subjectKey in (subjects = contexts[contextKey].subjects)) | ||
var graphs = this._graphs, subjects, subject; | ||
for (var graphKey in graphs) | ||
for (var subjectKey in (subjects = graphs[graphKey].subjects)) | ||
for (var predicateKey in (subject = subjects[subjectKey])) | ||
@@ -90,4 +87,4 @@ size += Object.keys(subject[predicateKey]).length; | ||
// (for instance: _subject_, _predicate_, and _object_). | ||
// Finally, `context` will be the context of the created triples. | ||
_findInIndex: function (index0, key0, key1, key2, name0, name1, name2, context) { | ||
// Finally, `graph` will be the graph of the created triples. | ||
_findInIndex: function (index0, key0, key1, key2, name0, name1, name2, graph) { | ||
var results = [], entityKeys = Object.keys(this._entities), tmp, index1, index2; | ||
@@ -111,3 +108,3 @@ | ||
for (var l = values.length - 1; l >= 0; l--) { | ||
var result = { context: context }; | ||
var result = { subject: '', predicate: '', object: '', graph: graph }; | ||
result[name0] = entity0; | ||
@@ -154,14 +151,14 @@ result[name1] = entity1; | ||
// ### `addTriple` adds a new N3 triple to the store. | ||
addTriple: function (subject, predicate, object, context) { | ||
addTriple: function (subject, predicate, object, graph) { | ||
// Shift arguments if a triple object is given instead of components | ||
if (!predicate) | ||
context = subject.context, object = subject.object, | ||
graph = subject.graph, object = subject.object, | ||
predicate = subject.predicate, subject = subject.subject; | ||
// Find the context that will contain the triple. | ||
context = context || this.defaultContext; | ||
var contextItem = this._contexts[context]; | ||
// Create the context if it doesn't exist yet. | ||
if (!contextItem) { | ||
contextItem = this._contexts[context] = { | ||
// Find the graph that will contain the triple. | ||
graph = graph || ''; | ||
var graphItem = this._graphs[graph]; | ||
// Create the graph if it doesn't exist yet. | ||
if (!graphItem) { | ||
graphItem = this._graphs[graph] = { | ||
subjects: {}, | ||
@@ -171,8 +168,8 @@ predicates: {}, | ||
}; | ||
// Freezing a context helps subsequent `add` performance, | ||
// Freezing a graph helps subsequent `add` performance, | ||
// and properties will never be modified anyway. | ||
Object.freeze(contextItem); | ||
Object.freeze(graphItem); | ||
} | ||
// Since entities can often be long URIs, we avoid storing them in every index. | ||
// Since entities can often be long IRIs, we avoid storing them in every index. | ||
// Instead, we have a separate index that maps entities to numbers, | ||
@@ -185,5 +182,5 @@ // which are then used as keys in the other indexes. | ||
this._addToIndex(contextItem.subjects, subject, predicate, object); | ||
this._addToIndex(contextItem.predicates, predicate, object, subject); | ||
this._addToIndex(contextItem.objects, object, subject, predicate); | ||
this._addToIndex(graphItem.subjects, subject, predicate, object); | ||
this._addToIndex(graphItem.predicates, predicate, object, subject); | ||
this._addToIndex(graphItem.objects, object, subject, predicate); | ||
@@ -201,4 +198,4 @@ // The cached triple count is now invalid. | ||
// ### `addPrefix` adds support for querying with the given prefix | ||
addPrefix: function (prefix, uri) { | ||
this._prefixes[prefix] = uri; | ||
addPrefix: function (prefix, iri) { | ||
this._prefixes[prefix] = iri; | ||
}, | ||
@@ -213,19 +210,19 @@ | ||
// ### `removeTriple` removes an N3 triple from the store if it exists. | ||
removeTriple: function (subject, predicate, object, context) { | ||
removeTriple: function (subject, predicate, object, graph) { | ||
// Shift arguments if a triple object is given instead of components. | ||
if (!predicate) | ||
context = subject.context, object = subject.object, | ||
graph = subject.graph, object = subject.object, | ||
predicate = subject.predicate, subject = subject.subject; | ||
context = context || this.defaultContext; | ||
graph = graph || ''; | ||
// Find internal identifiers for all components. | ||
var contextItem, entities = this._entities, contexts = this._contexts; | ||
var graphItem, entities = this._entities, graphs = this._graphs; | ||
if (!(subject = entities[subject])) return; | ||
if (!(predicate = entities[predicate])) return; | ||
if (!(object = entities[object])) return; | ||
if (!(contextItem = contexts[context])) return; | ||
if (!(graphItem = graphs[graph])) return; | ||
// Verify that the triple exists. | ||
var subjects, predicates; | ||
if (!(subjects = contextItem.subjects[subject])) return; | ||
if (!(subjects = graphItem.subjects[subject])) return; | ||
if (!(predicates = subjects[predicate])) return; | ||
@@ -235,10 +232,10 @@ if (!(object in predicates)) return; | ||
// Remove it from all indexes. | ||
this._removeFromIndex(contextItem.subjects, subject, predicate, object); | ||
this._removeFromIndex(contextItem.predicates, predicate, object, subject); | ||
this._removeFromIndex(contextItem.objects, object, subject, predicate); | ||
this._removeFromIndex(graphItem.subjects, subject, predicate, object); | ||
this._removeFromIndex(graphItem.predicates, predicate, object, subject); | ||
this._removeFromIndex(graphItem.objects, object, subject, predicate); | ||
if (this._size !== null) this._size--; | ||
// Remove the context if it is empty. | ||
for (subject in contextItem.subjects) return; | ||
delete contexts[context]; | ||
// Remove the graph if it is empty. | ||
for (subject in graphItem.subjects) return; | ||
delete graphs[graph]; | ||
}, | ||
@@ -254,24 +251,24 @@ | ||
// Setting `subject`, `predicate`, or `object` to `null` means an _anything_ wildcard. | ||
// Setting `context` to `null` means the default context. | ||
find: function (subject, predicate, object, context) { | ||
// Setting `graph` to `null` means the default graph. | ||
find: function (subject, predicate, object, graph) { | ||
var prefixes = this._prefixes; | ||
return this.findByUri( | ||
return this.findByIRI( | ||
expandPrefixedName(subject, prefixes), | ||
expandPrefixedName(predicate, prefixes), | ||
expandPrefixedName(object, prefixes), | ||
expandPrefixedName(context, prefixes) | ||
expandPrefixedName(graph, prefixes) | ||
); | ||
}, | ||
// ### `findByUri` finds a set of triples matching a pattern. | ||
// Setting `subject`, `predicate`, or `object` to `null` means an _anything_ wildcard. | ||
// Setting `context` to `null` means the default context. | ||
findByUri: function (subject, predicate, object, context) { | ||
context = context || this.defaultContext; | ||
var contextItem = this._contexts[context], entities = this._entities; | ||
// ### `findByIRI` finds a set of triples matching a pattern. | ||
// Setting `subject`, `predicate`, or `object` to a falsy value means an _anything_ wildcard. | ||
// Setting `graph` to a falsy value means the default graph. | ||
findByIRI: function (subject, predicate, object, graph) { | ||
graph = graph || ''; | ||
var graphItem = this._graphs[graph], entities = this._entities; | ||
// If the specified context contain no triples, there are no results. | ||
if (!contextItem) return []; | ||
// If the specified graph contain no triples, there are no results. | ||
if (!graphItem) return []; | ||
// Translate URIs to internal index keys. | ||
// Translate IRIs to internal index keys. | ||
// Optimization: if the entity doesn't exist, no triples with it exist. | ||
@@ -286,21 +283,21 @@ if (subject && !(subject = entities[subject])) return []; | ||
// If subject and object are given, the object index will be the fastest. | ||
return this._findInIndex(contextItem.objects, object, subject, predicate, | ||
'object', 'subject', 'predicate', context); | ||
return this._findInIndex(graphItem.objects, object, subject, predicate, | ||
'object', 'subject', 'predicate', graph); | ||
else | ||
// If only subject and possibly predicate are given, the subject index will be the fastest. | ||
return this._findInIndex(contextItem.subjects, subject, predicate, null, | ||
'subject', 'predicate', 'object', context); | ||
return this._findInIndex(graphItem.subjects, subject, predicate, null, | ||
'subject', 'predicate', 'object', graph); | ||
} | ||
else if (predicate) | ||
// If only predicate and possibly object are given, the predicate index will be the fastest. | ||
return this._findInIndex(contextItem.predicates, predicate, object, null, | ||
'predicate', 'object', 'subject', context); | ||
return this._findInIndex(graphItem.predicates, predicate, object, null, | ||
'predicate', 'object', 'subject', graph); | ||
else if (object) | ||
// If only object is given, the object index will be the fastest. | ||
return this._findInIndex(contextItem.objects, object, null, null, | ||
'object', 'subject', 'predicate', context); | ||
return this._findInIndex(graphItem.objects, object, null, null, | ||
'object', 'subject', 'predicate', graph); | ||
else | ||
// If nothing is given, iterate subjects and predicates first | ||
return this._findInIndex(contextItem.subjects, null, null, null, | ||
'subject', 'predicate', 'object', context); | ||
return this._findInIndex(graphItem.subjects, null, null, null, | ||
'subject', 'predicate', 'object', graph); | ||
}, | ||
@@ -310,24 +307,24 @@ | ||
// Setting `subject`, `predicate`, or `object` to `null` means an _anything_ wildcard. | ||
// Setting `context` to `null` means the default context. | ||
count: function (subject, predicate, object, context) { | ||
// Setting `graph` to `null` means the default graph. | ||
count: function (subject, predicate, object, graph) { | ||
var prefixes = this._prefixes; | ||
return this.countByUri( | ||
return this.countByIRI( | ||
expandPrefixedName(subject, prefixes), | ||
expandPrefixedName(predicate, prefixes), | ||
expandPrefixedName(object, prefixes), | ||
expandPrefixedName(context, prefixes) | ||
expandPrefixedName(graph, prefixes) | ||
); | ||
}, | ||
// ### `countByUri` returns the number of triples matching a pattern. | ||
// ### `countByIRI` returns the number of triples matching a pattern. | ||
// Setting `subject`, `predicate`, or `object` to `null` means an _anything_ wildcard. | ||
// Setting `context` to `null` means the default context. | ||
countByUri: function (subject, predicate, object, context) { | ||
context = context || this.defaultContext; | ||
var contextItem = this._contexts[context], entities = this._entities; | ||
// Setting `graph` to `null` means the default graph. | ||
countByIRI: function (subject, predicate, object, graph) { | ||
graph = graph || ''; | ||
var graphItem = this._graphs[graph], entities = this._entities; | ||
// If the specified context contain no triples, there are no results. | ||
if (!contextItem) return 0; | ||
// If the specified graph contain no triples, there are no results. | ||
if (!graphItem) return 0; | ||
// Translate URIs to internal index keys. | ||
// Translate IRIs to internal index keys. | ||
// Optimization: if the entity doesn't exist, no triples with it exist. | ||
@@ -342,14 +339,14 @@ if (subject && !(subject = entities[subject])) return 0; | ||
// If subject and object are given, the object index will be the fastest. | ||
return this._countInIndex(contextItem.objects, object, subject, predicate); | ||
return this._countInIndex(graphItem.objects, object, subject, predicate); | ||
else | ||
// If only subject and possibly predicate are given, the subject index will be the fastest. | ||
return this._countInIndex(contextItem.subjects, subject, predicate, object); | ||
return this._countInIndex(graphItem.subjects, subject, predicate, object); | ||
} | ||
else if (predicate) { | ||
// If only predicate and possibly object are given, the predicate index will be the fastest. | ||
return this._countInIndex(contextItem.predicates, predicate, object, subject); | ||
return this._countInIndex(graphItem.predicates, predicate, object, subject); | ||
} | ||
else { | ||
// If only object is possibly given, the object index will be the fastest. | ||
return this._countInIndex(contextItem.objects, object, subject, predicate); | ||
return this._countInIndex(graphItem.objects, object, subject, predicate); | ||
} | ||
@@ -368,5 +365,3 @@ }, | ||
else { | ||
do { | ||
name = '_:b' + this._blankNodeIndex++; | ||
} | ||
do { name = '_:b' + this._blankNodeIndex++; } | ||
while (this._entities[name]); | ||
@@ -373,0 +368,0 @@ } |
@@ -7,5 +7,5 @@ // **N3StreamParser** parses an N3 stream into a triple stream | ||
// ## Constructor | ||
function N3StreamParser(config) { | ||
function N3StreamParser(options) { | ||
if (!(this instanceof N3StreamParser)) | ||
return new N3StreamParser(config); | ||
return new N3StreamParser(options); | ||
@@ -17,3 +17,3 @@ // Initialize Transform base class | ||
// Set up parser | ||
var self = this, parser = new N3Parser(config); | ||
var self = this, parser = new N3Parser(options); | ||
parser.parse( | ||
@@ -20,0 +20,0 @@ // Handle triples by pushing them down the pipeline |
@@ -7,5 +7,5 @@ // **N3StreamWriter** serializes a triple stream into an N3 stream | ||
// ## Constructor | ||
function N3StreamWriter(prefixes) { | ||
function N3StreamWriter(options) { | ||
if (!(this instanceof N3StreamWriter)) | ||
return new N3StreamWriter(prefixes); | ||
return new N3StreamWriter(options); | ||
@@ -21,3 +21,3 @@ // Initialize Transform base class | ||
end: function (callback) { self.push(null); callback && callback(); }, | ||
}, prefixes); | ||
}, options); | ||
@@ -24,0 +24,0 @@ // Implement Transform methods on top of writer |
@@ -7,4 +7,4 @@ // **N3Util** provides N3 utility functions | ||
var N3Util = { | ||
// Tests whether the given entity (triple object) represents a URI in the N3 library | ||
isUri: function (entity) { | ||
// Tests whether the given entity (triple object) represents an IRI in the N3 library | ||
isIRI: function (entity) { | ||
if (!entity) | ||
@@ -55,3 +55,3 @@ return entity; | ||
// Expands the prefixed name to a full URI (also when it occurs as a literal's type) | ||
// Expands the prefixed name to a full IRI (also when it occurs as a literal's type) | ||
expandPrefixedName: function (prefixedName, prefixes) { | ||
@@ -58,0 +58,0 @@ var match = /(?:^|"\^\^)([^:\/#"'\^_]*):[^\/]/.exec(prefixedName); |
@@ -11,15 +11,15 @@ // **N3Writer** writes N3 documents. | ||
// Characters in literals that require escaping | ||
var literalEscape = /["\\\t\n\r\b\f]/, | ||
literalEscapeAll = /["\\\t\n\r\b\f]/g, | ||
literalReplacements = { '\\': '\\\\', '"': '\\"', '\t': '\\t', | ||
'\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f' }; | ||
var escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/, | ||
escapeAll = /["\\\t\n\r\b\f\u0000-\u0019]|[\ud800-\udbff][\udc00-\udfff]/g, | ||
escapeReplacements = { '\\': '\\\\', '"': '\\"', '\t': '\\t', | ||
'\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f' }; | ||
// ## Constructor | ||
function N3Writer(outputStream, prefixes) { | ||
function N3Writer(outputStream, options) { | ||
if (!(this instanceof N3Writer)) | ||
return new N3Writer(outputStream, prefixes); | ||
return new N3Writer(outputStream, options); | ||
// Shift arguments if the first argument is not a stream | ||
if (outputStream && typeof outputStream.write !== 'function') | ||
prefixes = outputStream, outputStream = null; | ||
options = outputStream, outputStream = null; | ||
@@ -35,7 +35,15 @@ // If no output stream given, send the output as string through the end callback | ||
} | ||
this._outputStream = outputStream; | ||
this._outputStream = outputStream; | ||
this._prefixURIs = Object.create(null); | ||
if (prefixes) | ||
this.addPrefixes(prefixes); | ||
// Initialize writer, depending on the format | ||
this._subject = null; | ||
options = options || {}; | ||
if (!(/triple|quad/i).test(options.format)) { | ||
this._graph = ''; | ||
this._prefixIRIs = Object.create(null); | ||
options.prefixes && this.addPrefixes(options.prefixes); | ||
} | ||
else { | ||
this._writeTriple = this._writeTripleLine; | ||
} | ||
} | ||
@@ -46,70 +54,104 @@ | ||
// ### `_write` writes the arguments to the output stream (last argument is callback) | ||
_write: function () { | ||
for (var i = 0, l = arguments.length - 2; i <= l; i++) | ||
this._outputStream.write(arguments[i], 'utf8', i === l ? arguments[l + 1] : null); | ||
// ### `_write` writes the argument to the output stream | ||
_write: function (string, callback) { | ||
this._outputStream.write(string, 'utf8', callback); | ||
}, | ||
// ### `_writeUriOrBlankNode` writes a URI or blank node to the output stream | ||
_writeUriOrBlankNode: function (uri, done) { | ||
// Write blank node | ||
if (uri[0] === '_' && uri[1] === ':') | ||
return this._write(uri, done); | ||
// Write prefixed name if possible | ||
var prefixMatch = this._prefixRegex.exec(uri); | ||
if (prefixMatch) | ||
this._write(this._prefixURIs[prefixMatch[1]], prefixMatch[2], done); | ||
// Otherwise, just write the URI | ||
else | ||
this._write('<', uri, '>', done); | ||
// ### `_writeTriple` writes the triple to the output stream | ||
_writeTriple: function (subject, predicate, object, graph, done) { | ||
try { | ||
// Write the graph's label if it has changed | ||
if (this._graph !== graph) { | ||
// Close the previous graph and start the new one | ||
this._write((this._subject === null ? '' : (this._graph ? '\n}\n' : '.\n')) + | ||
(graph ? this._encodeIriOrBlankNode(graph) + ' {\n' : '')); | ||
this._graph = graph, this._subject = null; | ||
} | ||
// Don't repeat the subject if it's the same | ||
if (this._subject === subject) { | ||
// Don't repeat the predicate if it's the same | ||
if (this._predicate === predicate) | ||
this._write(', ' + this._encodeObject(object), done); | ||
// Same subject, different predicate | ||
else | ||
this._write(';\n ' + | ||
this._encodePredicate(this._predicate = predicate) + ' ' + | ||
this._encodeObject(object), done); | ||
} | ||
// Different subject; write the whole triple | ||
else | ||
this._write((this._subject === null ? '' : '.\n') + | ||
this._encodeSubject(this._subject = subject) + ' ' + | ||
this._encodePredicate(this._predicate = predicate) + ' ' + | ||
this._encodeObject(object), done); | ||
} | ||
catch (error) { done && done(error); } | ||
}, | ||
// ### `_writeLiteral` writes a literal to the output stream | ||
_writeLiteral: function (value, type, language, done) { | ||
// Escape the literal if necessary | ||
if (literalEscape.test(value)) { | ||
value = value.replace(literalEscapeAll, function (match) { | ||
return literalReplacements[match]; | ||
}); | ||
// ### `_writeTripleLine` writes the triple or quad to the output stream as a single line | ||
_writeTripleLine: function (subject, predicate, object, graph, done) { | ||
// Don't use prefixes | ||
delete this._prefixMatch; | ||
// Write the triple | ||
try { | ||
this._write(this._encodeIriOrBlankNode(subject) + ' ' + | ||
this._encodeIriOrBlankNode(predicate) + ' ' + | ||
this._encodeObject(object) + | ||
(graph ? ' ' + this._encodeIriOrBlankNode(graph) + '.\n' : '.\n'), done); | ||
} | ||
catch (error) { done && done(error); } | ||
}, | ||
// ### `_encodeIriOrBlankNode` represents an IRI or blank node | ||
_encodeIriOrBlankNode: function (iri) { | ||
// A blank node is represented as-is | ||
if (iri[0] === '_' && iri[1] === ':') return iri; | ||
// Escape special characters | ||
if (escape.test(iri)) | ||
iri = iri.replace(escapeAll, characterReplacer); | ||
// Try to represent the IRI as prefixed name | ||
var prefixMatch = this._prefixRegex.exec(iri); | ||
return prefixMatch ? this._prefixIRIs[prefixMatch[1]] + prefixMatch[2] : '<' + iri + '>'; | ||
}, | ||
// ### `_encodeLiteral` represents a literal | ||
_encodeLiteral: function (value, type, language) { | ||
// Escape special characters | ||
if (escape.test(value)) | ||
value = value.replace(escapeAll, characterReplacer); | ||
// Write the literal, possibly with type or language | ||
this._write('"', value, '"', type || language ? null : done); | ||
if (type) { | ||
this._write('^^', null); | ||
this._writeUriOrBlankNode(type, done); | ||
} | ||
else if (language) { | ||
this._write('@', language, done); | ||
} | ||
if (language) | ||
return '"' + value + '"@' + language; | ||
else if (type) | ||
return '"' + value + '"^^' + this._encodeIriOrBlankNode(type); | ||
else | ||
return '"' + value + '"'; | ||
}, | ||
// ### `_writeSubject` writes a subject to the output stream | ||
_writeSubject: function (subject, done) { | ||
// ### `_encodeSubject` represents a subject | ||
_encodeSubject: function (subject) { | ||
if (subject[0] === '"') | ||
throw new Error('A literal as subject is not allowed: ' + subject); | ||
this._writeUriOrBlankNode(subject, done); | ||
return this._encodeIriOrBlankNode(subject); | ||
}, | ||
// ### `_writePredicate` writes a predicate to the output stream | ||
_writePredicate: function (predicate, done) { | ||
// ### `_encodePredicate` represents a predicate | ||
_encodePredicate: function (predicate) { | ||
if (predicate[0] === '"') | ||
throw new Error('A literal as predicate is not allowed: ' + predicate); | ||
if (predicate === RDF_TYPE) | ||
this._write('a', done); | ||
else | ||
this._writeUriOrBlankNode(predicate, done); | ||
return predicate === RDF_TYPE ? 'a' : this._encodeIriOrBlankNode(predicate); | ||
}, | ||
// ### `_writeObject` writes an object to the output stream | ||
_writeObject: function (object, done) { | ||
var literalMatch = N3LiteralMatcher.exec(object); | ||
if (literalMatch !== null) | ||
this._writeLiteral(literalMatch[1], literalMatch[2], literalMatch[3], done); | ||
else | ||
this._writeUriOrBlankNode(object, done); | ||
// ### `_encodeObject` represents an object | ||
_encodeObject: function (object) { | ||
// Represent an IRI or blank node | ||
if (object[0] !== '"') | ||
return this._encodeIriOrBlankNode(object); | ||
// Represent a literal | ||
var match = N3LiteralMatcher.exec(object); | ||
if (!match) throw new Error('Invalid literal: ' + object); | ||
return this._encodeLiteral(match[1], match[2], match[3]); | ||
}, | ||
// ### `_blockedWrite` replaces `_writer_ after the writer has been closed | ||
// ### `_blockedWrite` replaces `_write` after the writer has been closed | ||
_blockedWrite: function () { | ||
@@ -120,46 +162,13 @@ throw new Error('Cannot write because the writer has been closed.'); | ||
// ### `addTriple` adds the triple to the output stream | ||
addTriple: function (subject, predicate, object, done) { | ||
// If the triple was given as a triple object, shift parameters | ||
if (!object) { | ||
done = predicate; | ||
object = subject.object; | ||
predicate = subject.predicate; | ||
subject = subject.subject; | ||
} | ||
// Write the triple | ||
try { | ||
// Don't repeat the subject if it's the same | ||
if (this._prevSubject === subject) { | ||
// Don't repeat the predicate if it's the same | ||
if (this._prevPredicate === predicate) { | ||
this._write(', ', null); | ||
} | ||
// Same subject, different predicate | ||
else { | ||
this._write(';\n ', null); | ||
this._writePredicate(predicate); | ||
this._write(' ', null); | ||
this._prevPredicate = predicate; | ||
} | ||
} | ||
// Different subject; write the whole triple | ||
else { | ||
// End a possible previous triple | ||
if ('_prevSubject' in this) | ||
this._write('.\n', null); | ||
this._writeSubject(subject); | ||
this._write(' ', null); | ||
this._writePredicate(predicate); | ||
this._write(' ', null); | ||
this._prevSubject = subject; | ||
this._prevPredicate = predicate; | ||
} | ||
// In all cases, write the object | ||
this._writeObject(object, done); | ||
} | ||
catch (error) { | ||
done && done(error); | ||
} | ||
addTriple: function (subject, predicate, object, graph, done) { | ||
// The triple was given as a triple object, so shift parameters | ||
if (!object) | ||
this._writeTriple(subject.subject, subject.predicate, subject.object, | ||
subject.graph || '', predicate); | ||
// The optional `graph` parameter was not provided | ||
else if (typeof graph !== 'string') | ||
this._writeTriple(subject, predicate, object, '', graph); | ||
// The `graph` parameter was provided | ||
else | ||
this._writeTriple(subject, predicate, object, graph, done); | ||
}, | ||
@@ -174,5 +183,5 @@ | ||
// ### `addPrefix` adds the prefix to the output stream | ||
addPrefix: function (prefix, uri, done) { | ||
addPrefix: function (prefix, iri, done) { | ||
var prefixes = {}; | ||
prefixes[prefix] = uri; | ||
prefixes[prefix] = iri; | ||
this.addPrefixes(prefixes, done); | ||
@@ -187,13 +196,13 @@ }, | ||
// Verify whether the prefix can be used and does not exist yet | ||
var uri = prefixes[prefix]; | ||
if (/[#\/]$/.test(uri) && this._prefixURIs[uri] !== (prefix += ':')) { | ||
var iri = prefixes[prefix]; | ||
if (/[#\/]$/.test(iri) && this._prefixIRIs[iri] !== (prefix += ':')) { | ||
hasPrefixes = true; | ||
this._prefixURIs[uri] = prefix; | ||
this._prefixIRIs[iri] = prefix; | ||
// Finish a possible pending triple | ||
if ('_prevSubject' in this) { | ||
this._write('.\n', null); | ||
delete this._prevSubject; | ||
if (this._subject !== null) { | ||
this._write(this._graph ? '\n}\n' : '.\n'); | ||
this._subject = null, this._graph = ''; | ||
} | ||
// Write prefix | ||
this._write('@prefix ', prefix, ' <', uri, '>.\n', null); | ||
this._write('@prefix ' + prefix + ' <' + iri + '>.\n'); | ||
} | ||
@@ -203,7 +212,7 @@ } | ||
if (hasPrefixes) { | ||
var prefixURIs = ''; | ||
for (var prefixURI in this._prefixURIs) | ||
prefixURIs += prefixURIs ? '|' + prefixURI : prefixURI; | ||
prefixURIs = prefixURIs.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&'); | ||
this._prefixRegex = new RegExp('^(' + prefixURIs + ')([a-zA-Z][\\-_a-zA-Z0-9]*)$'); | ||
var prefixIRIs = ''; | ||
for (var prefixIRI in this._prefixIRIs) | ||
prefixIRIs += prefixIRIs ? '|' + prefixIRI : prefixIRI; | ||
prefixIRIs = prefixIRIs.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&'); | ||
this._prefixRegex = new RegExp('^(' + prefixIRIs + ')([a-zA-Z][\\-_a-zA-Z0-9]*)$'); | ||
} | ||
@@ -214,4 +223,4 @@ // End a prefix block with a newline | ||
// ### `_prefixRegex` matches an URL that begins with one of the added prefixes | ||
_prefixRegex: { exec: function () { return null; } }, | ||
// ### `_prefixRegex` matches an IRI that begins with one of the added prefixes | ||
_prefixRegex: /$0^/, | ||
@@ -221,5 +230,5 @@ // ### `end` signals the end of the output stream | ||
// Finish a possible pending triple | ||
if ('_prevSubject' in this) { | ||
this._write('.\n', null); | ||
delete this._prevSubject; | ||
if (this._subject !== null) { | ||
this._write(this._graph ? '\n}\n' : '.\n'); | ||
this._subject = null; | ||
} | ||
@@ -242,2 +251,22 @@ // Disallow further writing | ||
// Replaces a character by its escaped version | ||
function characterReplacer(character) { | ||
// Replace a single character by its escaped version | ||
var result = escapeReplacements[character]; | ||
if (result === undefined) { | ||
// Replace a single character with its 4-bit unicode escape sequence | ||
if (character.length === 1) { | ||
result = character.charCodeAt(0).toString(16); | ||
result = '\\u0000'.substr(0, 6 - result.length) + result; | ||
} | ||
// Replace a surrogate pair with its 8-bit unicode escape sequence | ||
else { | ||
result = ((character.charCodeAt(0) - 0xD800) * 0x400 + | ||
character.charCodeAt(1) + 0x2400).toString(16); | ||
result = '\\U00000000'.substr(0, 10 - result.length) + result; | ||
} | ||
} | ||
return result; | ||
} | ||
// ## Exports | ||
@@ -244,0 +273,0 @@ |
{ | ||
"name": "n3", | ||
"version": "0.3.6", | ||
"version": "0.4.0", | ||
"description": "Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.", | ||
@@ -19,3 +19,3 @@ "author": "Ruben Verborgh <ruben.verborgh@gmail.com>", | ||
"devDependencies": { | ||
"async": "~0.1.22", | ||
"async": "~0.9.0", | ||
"chai": "~1.4.2", | ||
@@ -27,6 +27,6 @@ "chai-things": "~0.1.1", | ||
"request": "~2.22.0", | ||
"mocha": ">=1.15.0", | ||
"mocha": "~1.15.0", | ||
"istanbul": "~0.3.0", | ||
"uglify-js": "~2.4.3", | ||
"browserify": ">=3.x", | ||
"browserify": "~3.x", | ||
"pre-commit": "~0.0.9" | ||
@@ -36,7 +36,7 @@ }, | ||
"test": "mocha", | ||
"hint": "jshint lib perf test", | ||
"hint": "jshint lib perf test spec", | ||
"browser": "node browser/build-browser-versions", | ||
"coverage": "istanbul cover node_modules/.bin/_mocha -- -R spec --timeout 100", | ||
"spec": "node spec/turtle-spec.js", | ||
"spec-clean": "rm -r spec/turtle", | ||
"spec": "node spec/turtle-spec && node spec/trig-spec && node spec/ntriples-spec && node spec/nquads-spec", | ||
"spec-clean": "rm -r spec/turtle spec/trig", | ||
"docs": "docco lib/*.js" | ||
@@ -43,0 +43,0 @@ }, |
#!/usr/bin/env node | ||
var n3 = require('../N3'); | ||
var N3 = require('../N3'); | ||
var fs = require('fs'), | ||
@@ -15,3 +15,3 @@ assert = require('assert'); | ||
var count = 0; | ||
new n3.Lexer().tokenize(fs.createReadStream(filename), function (error, token) { | ||
new N3.Lexer().tokenize(fs.createReadStream(filename), function (error, token) { | ||
assert(!error, error); | ||
@@ -18,0 +18,0 @@ count++; |
#!/usr/bin/env node | ||
var n3 = require('../N3'); | ||
var N3 = require('../N3'); | ||
var fs = require('fs'), | ||
@@ -15,3 +15,3 @@ assert = require('assert'); | ||
var count = 0; | ||
new n3.Parser().parse(fs.createReadStream(filename), function (error, triple) { | ||
new N3.Parser().parse(fs.createReadStream(filename), function (error, triple) { | ||
assert(!error, error); | ||
@@ -18,0 +18,0 @@ if (triple) { |
#!/usr/bin/env node | ||
var n3 = require('../N3'); | ||
var N3 = require('../N3'); | ||
var assert = require('assert'); | ||
@@ -13,3 +13,3 @@ | ||
var store = new n3.Store(); | ||
var store = new N3.Store(); | ||
@@ -16,0 +16,0 @@ TEST = '- Adding ' + dimCubed + ' triples'; |
227
README.md
@@ -1,20 +0,25 @@ | ||
# Lightning fast, asynchronous, streaming Turtle for JavaScript | ||
# Lightning fast, asynchronous, streaming RDF for JavaScript | ||
The N3.js library lets you handle [Turtle](http://www.w3.org/TR/turtle/) and [RDF](http://www.w3.org/TR/rdf-primer/) in JavaScript _([Node](http://nodejs.org/) and browser)_ easily. | ||
The N3.js library lets you handle [RDF](http://www.w3.org/TR/rdf-primer/) in JavaScript easily, in [Node.js](http://nodejs.org/) and the browser. | ||
It offers: | ||
- [**Turtle parsing**](#parsing) _([fully compliant](https://github.com/RubenVerborgh/N3.js/tree/master/spec) with the [Turtle standard](http://www.w3.org/TR/turtle/))_ | ||
- [**in-memory storage and manipulation**](#storing) | ||
- [**Turtle writing**](#writing) | ||
- [**Parsing**](#parsing) triples/quads from | ||
[Turtle](http://www.w3.org/TR/turtle/), | ||
[TriG](http://www.w3.org/TR/trig/), | ||
[N-Triples](http://www.w3.org/TR/ntriples/) | ||
and [N-Quads](http://www.w3.org/TR/nquads/). | ||
- [**Writing**](#Writing) triples/quads to | ||
[Turtle](http://www.w3.org/TR/turtle/), | ||
[TriG](http://www.w3.org/TR/trig/), | ||
[N-Triples](http://www.w3.org/TR/ntriples/) | ||
and [N-Quads](http://www.w3.org/TR/nquads/). | ||
- **Storage** of triples/quads in memory | ||
It has the following characteristics: | ||
- extreme performance – by far the [fastest parser in JavaScript](https://github.com/RubenVerborgh/N3.js/tree/master/perf) | ||
- asynchronous – triples arrive as soon as possible | ||
- streaming – streams are parsed as data comes in, so you can easily parse files that don't fit into 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** – by far the [fastest parser in JavaScript](https://github.com/RubenVerborgh/N3.js/tree/master/perf) | ||
At a later stage, this library will support [Notation3 (N3)](http://www.w3.org/TeamSubmission/n3/), | ||
a Turtle superset. | ||
## Installation | ||
N3.js comes as an [npm package](https://npmjs.org/package/n3). | ||
For Node.js, N3.js comes as an [npm package](https://npmjs.org/package/n3). | ||
@@ -29,8 +34,6 @@ ``` bash | ||
It is also fully compatible with [browserify](http://browserify.org/). | ||
<br> | ||
Alternatively, it offers a minimal browser version (without Node stream support). | ||
N3.js seamlessly works in browsers. Generate a browser version as follows: | ||
``` bash | ||
$ cd n3 | ||
$ cd N3.js | ||
$ npm install | ||
@@ -44,9 +47,10 @@ $ npm run browser | ||
In addition, N3.js is fully compatible with [browserify](http://browserify.org/), | ||
so you can write code for Node.js and deploy it to browsers. | ||
## Triple representation | ||
For maximum performance and easy of use, | ||
triples are represented as simple objects. | ||
<br> | ||
Since URIs are most common when dealing with RDF, | ||
they are represented as simple strings. | ||
triples are simple objects with string properties. | ||
**URLs, URIs and IRIs are simple strings.** For example, parsing this RDF document: | ||
``` Turtle | ||
@@ -56,3 +60,3 @@ @prefix c: <http://example.org/cartoons#>. | ||
``` | ||
is represented as | ||
results in this JavaScript object: | ||
``` js | ||
@@ -66,8 +70,7 @@ { | ||
Literals are represented as double quoted strings. | ||
**Literals are represented as double quoted strings.** For example, parsing this RDF document: | ||
``` Turtle | ||
c:Tom c:name "Tom". | ||
``` | ||
is represented as | ||
results in this JavaScript object: | ||
``` js | ||
@@ -87,16 +90,19 @@ { | ||
The [Utility](#utility) section details entity representation in more depth. | ||
An optional fourth element signals the graph to which a triple belongs: | ||
``` js | ||
{ | ||
subject: 'http://example.org/cartoons#Tom', | ||
predicate: 'http://example.org/cartoons#name', | ||
object: '"Tom"', | ||
graph: 'http://example.org/mycartoon' | ||
} | ||
``` | ||
The N3.js [Utility](#utility) (`N3.Util`) can help you with these representations. | ||
## Parsing | ||
### From a Turtle string to triples | ||
### From an RDF document to triples | ||
`N3.Parser` parses strings into triples using a callback. | ||
<br> | ||
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` as `triple` value | ||
and a hash of prefixes as the third argument. | ||
`N3.Parser` transforms Turtle, TriG, N-Triples or N-Quads document into triples through a callback: | ||
``` js | ||
@@ -115,9 +121,24 @@ var parser = N3.Parser(); | ||
``` | ||
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. | ||
<br> | ||
Pass a second callback to `parse` to retrieve prefixes as they are read. | ||
Addionally, a second callback `function (prefix, uri)` can be passed to `parse`. | ||
By default, `N3.Parser` parses a permissive superset of Turtle, TriG, N-Triples and N-Quads. | ||
<br> | ||
For strict compatibility with any of those languages, pass a `format` argument upon creation: | ||
### From Turtle fragments to triples | ||
``` js | ||
var parser1 = N3.Parser({ format: 'N-Triples' }); | ||
var parser2 = N3.Parser({ format: 'application/trig' }); | ||
``` | ||
`N3.Parser` can also parse triples from a Turtle document that arrives in fragments. | ||
### 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. | ||
``` js | ||
@@ -137,23 +158,24 @@ var parser = N3.Parser(), triples = []; | ||
### From a Turtle stream to triples | ||
### From an RDF stream to triples | ||
`N3.Parser` can parse streams as they grow, returning triples as soon as they're ready. | ||
`N3.Parser` can parse [Node.js streams](http://nodejs.org/api/stream.html) as they grow, | ||
returning triples as soon as they're ready. | ||
<br> | ||
This behavior sets N3.js apart from most other Turtle libraries. | ||
This behavior sets N3.js apart from most other libraries. | ||
``` js | ||
var parser = N3.Parser(), | ||
turtleStream = fs.createReadStream('cartoons.ttl'); | ||
parser.parse(turtleStream, console.log); | ||
rdfStream = fs.createReadStream('cartoons.ttl'); | ||
parser.parse(rdfStream, console.log); | ||
``` | ||
In addition, `N3.StreamParser` offers a [Node Stream](http://nodejs.org/api/stream.html) implementation, | ||
so you can transform Turtle streams and pipe them to anywhere. | ||
In addition, `N3.StreamParser` offers a [Node.js stream](http://nodejs.org/api/stream.html) implementation, | ||
so you can transform RDF streams and pipe them to anywhere. | ||
This solution is ideal if your consumer is slower, | ||
as it avoids parser backpressure. | ||
since source data is only read when the consumer is ready. | ||
``` js | ||
var streamParser = N3.StreamParser(), | ||
turtleStream = fs.createReadStream('cartoons.ttl'); | ||
turtleStream.pipe(streamParser); | ||
rdfStream = fs.createReadStream('cartoons.ttl'); | ||
rdfStream.pipe(streamParser); | ||
streamParser.pipe(new SlowConsumer()); | ||
@@ -171,18 +193,4 @@ | ||
A dedicated `prefix` event signals every prefix with `prefix` and `uri` arguments. | ||
A dedicated `prefix` event signals every prefix with `prefix` and `iri` arguments. | ||
## Storing | ||
In this example below, 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. | ||
``` js | ||
var store = N3.Store(); | ||
store.addTriple('http://example.org/Pluto', 'a', 'http://example.org/Dog'); | ||
store.addTriple('http://example.org/Mickey', 'a', 'http://example.org/Mouse'); | ||
var mickey = store.find('http://example.org/Mickey', null, null)[0]; | ||
console.log(mickey.subject, mickey.predicate, mickey.object, '.'); | ||
``` | ||
## Writing | ||
@@ -192,6 +200,7 @@ | ||
`N3.Writer` can serialize triples as a Turtle string. | ||
`N3.Writer` serializes triples as an RDF document. | ||
Write triples through `addTriple`. | ||
``` js | ||
var writer = N3.Writer({ 'c': 'http://example.org/cartoons#' }); | ||
var writer = N3.Writer({ prefixes: { 'c': 'http://example.org/cartoons#' } }); | ||
writer.addTriple('http://example.org/cartoons#Tom', | ||
@@ -208,8 +217,17 @@ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', | ||
### From triples to a Turtle stream | ||
By default, `N3.Writer` writes Turtle (or TriG for triples with a `graph` property). | ||
<br> | ||
To write N-Triples (or N-Quads) instead, pass a `format` argument upon creation: | ||
`N3.Writer` can also write triples to an output stream. | ||
``` js | ||
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. | ||
``` js | ||
var writer = N3.Writer(process.stdout, { 'c': 'http://example.org/cartoons#' }); | ||
var writer = N3.Writer(process.stdout, { prefixes: { 'c': 'http://example.org/cartoons#' } }); | ||
writer.addTriple('http://example.org/cartoons#Tom', | ||
@@ -226,5 +244,5 @@ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', | ||
### From a triple stream to a Turtle stream | ||
### From a triple stream to an RDF stream | ||
`N3.StreamWriter` is a Turtle writer implementation as a [Node Stream](http://nodejs.org/api/stream.html). | ||
`N3.StreamWriter` is a writer implementation as a Node.js stream. | ||
@@ -240,9 +258,26 @@ ``` js | ||
## 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`. | ||
<br> | ||
Then, we find a triple with `:Mickey` as subject. | ||
``` js | ||
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 URI and literal representations. | ||
`N3.Util` offers helpers for IRI and literal representations. | ||
<br> | ||
As URIs are most common, they are represented as simple strings: | ||
As IRIs are most common, they are represented as simple strings: | ||
``` js | ||
var N3Util = N3.Util; | ||
N3Util.isUri('http://example.org/cartoons#Mickey'); // true | ||
N3Util.isIRI('http://example.org/cartoons#Mickey'); // true | ||
``` | ||
@@ -260,5 +295,5 @@ **Literals** are represented as double quoted strings: | ||
``` | ||
Note the difference between `'http://example.org/'` (URI) and `'"http://example.org/"'` (literal). | ||
Note the difference between `'http://example.org/'` (IRI) and `'"http://example.org/"'` (literal). | ||
<br> | ||
Also note that the double quoted literals are _not_ raw Turtle syntax: | ||
Also note that the double quoted literals are _not_ raw Turtle/TriG syntax: | ||
``` js | ||
@@ -269,4 +304,4 @@ N3Util.isLiteral('"This word is "quoted"!"'); // true | ||
The above string represents the string _This word is "quoted"!_, | ||
even though the correct Turtle syntax for that is `"This word is \"quoted\"!"` | ||
N3.js thus always parses literals, but adds quotes to differentiate from URIs: | ||
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: | ||
``` js | ||
@@ -280,3 +315,3 @@ new N3.Parser().parse('<a> <b> "This word is \\"quoted\\"!".', console.log); | ||
N3Util.isBlank('_:b1'); // true | ||
N3Util.isUri('_:b1'); // false | ||
N3Util.isIRI('_:b1'); // false | ||
N3Util.isLiteral('_:b1'); // false | ||
@@ -293,17 +328,41 @@ ``` | ||
### Loading the utility globally | ||
For convenience, `N3Util` can also be loaded globally: | ||
For convenience, `N3Util` can be loaded globally: | ||
``` js | ||
require('n3').Util(global); | ||
isUri('http://example.org/cartoons#Mickey'); // true | ||
isIRI('http://example.org/cartoons#Mickey'); // true | ||
isLiteral('"Mickey Mouse"'); // true | ||
``` | ||
If desired, the methods can even be added directly on all strings: | ||
If desired, its methods can even be added directly on all strings: | ||
``` js | ||
require('n3').Util(String, true); | ||
'http://example.org/cartoons#Mickey'.isUri(); // true | ||
'http://example.org/cartoons#Mickey'.isIRI(); // true | ||
'"Mickey Mouse"'.isLiteral(); // true | ||
``` | ||
# License, status and contributions | ||
## Compatibility | ||
### Specifications | ||
The N3.js parser and writer is fully compatible with the following W3C specifications: | ||
- [RDF 1.1 Turtle](http://www.w3.org/TR/turtle/) | ||
– [EARL report](https://raw.githubusercontent.com/RubenVerborgh/N3.js/earl/n3js-earl-report-turtle.ttl) | ||
- [RDF 1.1 TriG](http://www.w3.org/TR/trig/) | ||
– [EARL report](https://raw.githubusercontent.com/RubenVerborgh/N3.js/earl/n3js-earl-report-trig.ttl) | ||
- [RDF 1.1 N-Triples](http://www.w3.org/TR/n-triples/) | ||
– [EARL report](https://raw.githubusercontent.com/RubenVerborgh/N3.js/earl/n3js-earl-report-ntriples.ttl) | ||
- [RDF 1.1 N-Quads](http://www.w3.org/TR/n-quads/) | ||
– [EARL report](https://raw.githubusercontent.com/RubenVerborgh/N3.js/earl/n3js-earl-report-nquads.ttl) | ||
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](http://www.w3.org/TeamSubmission/n3/) 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](http://ruben.verborgh.org/) | ||
@@ -317,2 +376,2 @@ and released under the [MIT License](https://github.com/RubenVerborgh/N3.js/blob/master/LICENSE.md). | ||
Contributions are welcome, and bug reports or pull requests are always helpful. | ||
If you plan to implement larger features, it's best to contact me first. | ||
If you plan to implement a larger feature, it's best to contact me first. |
#!/usr/bin/env node | ||
var N3Parser = require('../lib/N3Parser.js'), | ||
N3Store = require('../lib/N3Store.js'); | ||
var fs = require('fs'), | ||
path = require('path'), | ||
request = require('request'), | ||
exec = require('child_process').exec, | ||
colors = require('colors'), | ||
async = require('async'); | ||
// Should the tests run in parallel? | ||
var parallel = false; | ||
// Path to the tests and the tests' manifest | ||
var testPath = "http://www.w3.org/2013/TurtleTests/", | ||
manifest = "manifest.ttl"; | ||
// Prefixes | ||
var prefixes = { | ||
mf: "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#", | ||
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", | ||
rdfs: "http://www.w3.org/2000/01/rdf-schema#", | ||
rdft: "http://www.w3.org/ns/rdftest#", | ||
dc: "http://purl.org/dc/terms/", | ||
doap: "http://usefulinc.com/ns/doap#", | ||
earl: "http://www.w3.org/ns/earl#", | ||
foaf: "http://xmlns.com/foaf/0.1/", | ||
xsd: "http://www.w3.org/2001/XMLSchema#", | ||
manifest: testPath + manifest + '#', | ||
}; | ||
// List predicates | ||
var first = prefixes.rdf + "first", | ||
rest = prefixes.rdf + "rest", | ||
nil = prefixes.rdf + "nil"; | ||
// Create the folders that will contain the spec files and results | ||
var specFolder = __dirname, | ||
testFolder = path.join(specFolder, 'turtle/'), | ||
outputFolder = path.join(testFolder, 'results/'); | ||
[specFolder, testFolder, outputFolder].forEach(function (folder) { | ||
if (!fs.existsSync(folder)) | ||
fs.mkdirSync(folder); | ||
}); | ||
runSpecTest(); | ||
/************************************************* | ||
Test suite execution | ||
**************************************************/ | ||
// Fetches the manifest, executes all tests, and reports results | ||
function runSpecTest() { | ||
console.log("Turtle Terse RDF Triple Language Test Cases".bold); | ||
async.waterfall([ | ||
// Fetch and parse the manifest | ||
fetch.bind(null, manifest), | ||
parseManifest, | ||
// Perform the tests in the manifest | ||
function performTests(manifest, callback) { | ||
async[parallel ? 'map' : 'mapSeries'](manifest.tests, function (test, callback) { | ||
async.parallel({ actionTurtle: fetch.bind(null, test.action), | ||
resultTurtle: fetch.bind(null, test.result) }, | ||
function (err, results) { | ||
performTest(test, results.actionTurtle, callback); | ||
}); | ||
}, | ||
// Show the summary of the performed tests | ||
function showSummary(error, tests) { | ||
var score = tests.reduce(function (sum, test) { return sum + test.success; }, 0); | ||
console.log(("* passed " + score + " out of " + manifest.tests.length + " tests").bold); | ||
callback(error, tests); | ||
}); | ||
}, | ||
// Generate the EARL report | ||
generateEarlReport, | ||
], | ||
function (error) { | ||
if (error) { | ||
console.error("ERROR".red); | ||
console.error(error.red); | ||
process.exit(1); | ||
} | ||
}); | ||
} | ||
// Parses the tests manifest into tests | ||
function parseManifest(manifestContents, callback) { | ||
var manifest = {}, | ||
testStore = new N3Store(); | ||
// Parse the manifest into triples | ||
new N3Parser().parse(manifestContents, function (err, triple) { | ||
if (err) | ||
return callback(err); | ||
// Store triples until there are no more | ||
if (triple) | ||
return testStore.addTriple(triple.subject, triple.predicate, triple.object); | ||
// Once all triples are there, get the first item of the test list | ||
var tests = manifest.tests = [], | ||
itemHead = testStore.find('', prefixes.mf + 'entries', null)[0].object; | ||
// Loop through all test items | ||
while (itemHead && itemHead !== nil) { | ||
// Find and store the item's properties | ||
var itemValue = testStore.find(itemHead, first, null)[0].object, | ||
itemTriples = testStore.find(itemValue, null, null), | ||
test = { id: itemValue.replace(/^#/, '') }; | ||
itemTriples.forEach(function (triple) { | ||
var propertyMatch = triple.predicate.match(/#(.+)/); | ||
if (propertyMatch) | ||
test[propertyMatch[1]] = triple.object; | ||
}); | ||
tests.push(test); | ||
// Find the next test item | ||
itemHead = testStore.find(itemHead, rest, null)[0].object; | ||
} | ||
return callback(null, manifest); | ||
}); | ||
} | ||
// Fetches and caches the specified file, or retrieves it from disk | ||
function fetch(testFile, callback) { | ||
if (!testFile) | ||
return callback(null, null); | ||
var localFile = path.join(testFolder, testFile); | ||
fs.exists(localFile, function (exists) { | ||
if (exists) | ||
fs.readFile(localFile, 'utf8', callback); | ||
else | ||
request.get(testPath + testFile, | ||
function (error, response, body) { callback(error, body); }) | ||
.pipe(fs.createWriteStream(localFile)); | ||
}); | ||
} | ||
/************************************************* | ||
Individual test execution | ||
**************************************************/ | ||
// Performs the specified test by parsing the specified Turtle document | ||
function performTest(test, actionTurtle, callback) { | ||
// Create the results file | ||
var resultFile = path.join(outputFolder, test.action.replace(/\.ttl$/, '.nt')), | ||
resultStream = fs.createWriteStream(resultFile); | ||
resultStream.once('open', function () { | ||
// Try to parse the specified document | ||
var config = { documentURI: testPath + test.action }; | ||
new N3Parser(config).parse(actionTurtle, | ||
function (error, triple) { | ||
if (error) | ||
test.error = error; | ||
// Write the triple to the results file, or end if none are left | ||
if (triple) | ||
resultStream.write(toNTriple(triple)); | ||
else | ||
resultStream.end(); | ||
}); | ||
}); | ||
// Verify the result if the result has been written | ||
resultStream.once('close', function () { | ||
verifyResult(test, resultFile, test.result && (testFolder + test.result), callback); | ||
}); | ||
} | ||
// Verifies and reports the test result | ||
function verifyResult(test, resultFile, correctFile, callback) { | ||
// Negative tests are successful if an error occurred | ||
var negativeTest = /TestTurtleNegative/.test(test.type); | ||
if (negativeTest) { | ||
displayResult(null, !!test.error); | ||
} | ||
// Positive tests are successful if the results are equal, | ||
// or if the correct solution is not given but no error occurred | ||
else { | ||
if (!correctFile) | ||
displayResult(null, !test.error); | ||
else if (resultFile) | ||
compareGraphs(resultFile, correctFile, displayResult); | ||
else | ||
displayResult(null, false); | ||
} | ||
// Display the test result | ||
function displayResult(error, success, comparison) { | ||
console.log(unString(test.name).bold + ':', unString(test.comment), | ||
(success ? 'OK'.green : 'FAIL'.red).bold); | ||
if (error || !success) { | ||
console.log((correctFile ? fs.readFileSync(correctFile, 'utf8') : '(empty)').grey); | ||
console.log(' was expected, but got'.bold.grey); | ||
console.log((resultFile ? fs.readFileSync(resultFile, 'utf8') : '(empty)').grey); | ||
console.log((' error: '.bold + (test.error || '(none)')).grey); | ||
if (comparison) | ||
console.log((' comparison: ' + comparison).grey); | ||
} | ||
test.success = success; | ||
callback(null, test); | ||
} | ||
} | ||
// Verifies whether the two graphs are equal | ||
function compareGraphs(actual, expected, callback) { | ||
// Try a full-text comparison (fastest) | ||
async.parallel({ | ||
actualContents: fs.readFile.bind(fs, actual, 'utf8'), | ||
expectedContents: fs.readFile.bind(fs, expected, 'utf8'), | ||
}, | ||
function (error, results) { | ||
// If the full-text comparison was successful, graphs are certainly equal | ||
if (results.actualContents === results.expectedContents) | ||
callback(error, !error); | ||
// If not, we check for proper graph equality with SWObjects | ||
else | ||
exec('sparql -d ' + expected + ' --compare ' + actual, function (error, stdout, stderr) { | ||
callback(error, /^matched\s*$/.test(stdout), stdout); | ||
}); | ||
}); | ||
} | ||
/************************************************* | ||
Conversion routines | ||
**************************************************/ | ||
// Converts the triple to NTriples format (primitive and incomplete) | ||
function toNTriple(triple) { | ||
var subject = triple.subject, | ||
predicate = triple.predicate, | ||
object = triple.object; | ||
if (/^".*"$/.test(object)) | ||
object = escapeString(object); | ||
else | ||
object = escape(object).replace(/"\^\^(.*)$/, '"^^<$1>'); | ||
return (subject.match(/^_/) ? subject : '<' + subject + '>') + ' ' + | ||
(predicate.match(/^_/) ? predicate : '<' + predicate + '>') + ' ' + | ||
(object.match(/^_|^"/) ? object : '<' + object + '>') + ' .\n'; | ||
} | ||
// Removes the quotes around a string | ||
function unString(value) { | ||
return value ? value.replace(/^("""|")(.*)\1$/, '$2') : ''; | ||
} | ||
// Escapes unicode characters in a URI | ||
function escape(value) { | ||
var result = ''; | ||
// Add all characters, converting to an unicode escape code if necessary | ||
for (var i = 0; i < value.length; i++) { | ||
var code = value.charCodeAt(i); | ||
if (code >= 32 && code < 128) { | ||
result += value[i]; | ||
} | ||
else { | ||
var hexCode = code.toString(16); | ||
while (hexCode.length < 4) | ||
hexCode = '0' + hexCode; | ||
result += '\\u' + hexCode; | ||
} | ||
} | ||
// Convert surrogate pairs to actual unicode (http://mathiasbynens.be/notes/javascript-encoding) | ||
result = result.replace(/\\u([a-z0-9]{4})\\u([a-z0-9]{4})/gi, function (all, high, low) { | ||
high = parseInt(high, 16); | ||
low = parseInt(low, 16); | ||
if (high >= 0xD800 && high <= 0xDBFF && low >= 0xDC00 && low <= 0xDFFF) { | ||
var result = (high - 0xD800) * 0x400 + low - 0xDC00 + 0x10000; | ||
result = result.toString(16); | ||
while (result.length < 8) | ||
result = '0' + result; | ||
return '\\U' + result; | ||
} | ||
return all; | ||
}); | ||
return result; | ||
} | ||
// Escapes characters in a string | ||
function escapeString(value) { | ||
value = value.replace(/\\/g, '\\\\'); | ||
value = escape(unString(value)); | ||
value = value.replace(/"/g, '\\"'); | ||
return '"' + value + '"'; | ||
} | ||
/************************************************* | ||
EARL report generation | ||
**************************************************/ | ||
var homepage = 'https://github.com/RubenVerborgh/N3.js', | ||
application = homepage + '#n3js', | ||
developer = 'http://ruben.verborgh.org/#me'; | ||
function generateEarlReport(tests, callback) { | ||
// Create the report file | ||
var reportFile = path.join(outputFolder, 'earl-report.ttl'), | ||
report = fs.createWriteStream(reportFile), | ||
date = new Date().toISOString(); | ||
report.once('open', function () { | ||
for (var prefix in prefixes) | ||
writeln('@prefix ', prefix, ': <', prefixes[prefix], '>.'); | ||
writeln(); | ||
writeln('<> foaf:primaryTopic <', application, '>;'); | ||
writeln(' dc:issued "', date, '"^^xsd:dateTime;'); | ||
writeln(' foaf:maker <', developer, '>.'); | ||
writeln(); | ||
writeln('<', application, '> a earl:Software, earl:TestSubject, doap:Project;'); | ||
writeln(' doap:name "N3.js";'); | ||
writeln(' doap:homepage <', homepage, '>;'); | ||
writeln(' doap:license <http://opensource.org/licenses/MIT>;'); | ||
writeln(' doap:programming-language "JavaScript";'); | ||
writeln(' doap:implements <http://www.w3.org/TR/turtle/>;'); | ||
writeln(' doap:category <http://dbpedia.org/resource/Resource_Description_Framework>;'); | ||
writeln(' doap:download-page <https://npmjs.org/package/n3>;'); | ||
writeln(' doap:bug-database <', homepage, '/issues>;'); | ||
writeln(' doap:blog <http://ruben.verborgh.org/blog/>;'); | ||
writeln(' doap:developer <', developer, '>;'); | ||
writeln(' doap:maintainer <', developer, '>;'); | ||
writeln(' doap:documenter <', developer, '>;'); | ||
writeln(' doap:maker <', developer, '>;'); | ||
writeln(' dc:title "N3.js";'); | ||
writeln(' dc:description "N3.js is an asynchronous, streaming Turtle parser for JavaScript."@en;'); | ||
writeln(' doap:description "N3.js is an asynchronous, streaming Turtle parser for JavaScript."@en;'); | ||
writeln(' dc:creator <', developer, '>.'); | ||
writeln(); | ||
writeln('<', developer, '> a foaf:Person, earl:Assertor;'); | ||
writeln(' foaf:name "Ruben Verborgh";'); | ||
writeln(' foaf:homepage <http://ruben.verborgh.org/>;'); | ||
writeln(' foaf:primaryTopicOf <http://ruben.verborgh.org/profile/>;'); | ||
writeln(' rdfs:isDefinedBy <http://ruben.verborgh.org/profile/>.'); | ||
tests.forEach(function (test) { | ||
writeln(); | ||
writeln('manifest:', test.id, ' a earl:TestCriterion, earl:TestCase;'); | ||
writeln(' dc:title ', escapeString(unString(test.name)), ';'); | ||
writeln(' dc:description ', escapeString(unString(test.comment)), ';'); | ||
writeln(' mf:action <', testPath, test.action, '>;'); | ||
if (test.result) | ||
writeln(' mf:result <', testPath, test.result, '>;'); | ||
writeln(' earl:assertions ('); | ||
writeln(' [ a earl:Assertion;'); | ||
writeln(' earl:assertedBy <', developer, '>;'); | ||
writeln(' earl:test manifest:', test.id, ';'); | ||
writeln(' earl:subject <', application, '>;'); | ||
writeln(' earl:mode earl:automatic;'); | ||
writeln(' earl:result [ a earl:TestResult; ', | ||
'earl:outcome earl:', (test.success ? 'passed' : 'failed'), '; ', | ||
'dc:date "', date, '"^^xsd:dateTime', | ||
' ]]'); | ||
writeln(' ).'); | ||
}); | ||
report.end(); | ||
}); | ||
report.once('close', callback); | ||
function writeln() { | ||
for(var i = 0; i < arguments.length; i++) | ||
report.write(arguments[i]); | ||
report.write('\n'); | ||
} | ||
} | ||
var SpecTester = require('./SpecTester'); | ||
new SpecTester({ | ||
name: 'turtle', | ||
title: 'RDF 1.1 Turtle – Terse RDF Triple Language Test Cases', | ||
manifest: 'http://www.w3.org/2013/TurtleTests/manifest.ttl', | ||
}).run(); |
@@ -180,3 +180,3 @@ var N3Lexer = require('../N3').Lexer; | ||
shouldNotTokenize('ex:bla"foo', | ||
'Syntax error: unexpected "ex:bla"foo" on line 1.')); | ||
'Syntax error: unexpected ""foo" on line 1.')); | ||
@@ -464,20 +464,24 @@ it('should not tokenize a prefixed name with escaped characters', | ||
it('should tokenize @prefix declarations', | ||
shouldTokenize('@prefix : <http://uri.org/#>.\n@prefix abc:<http://uri.org/#>.', | ||
shouldTokenize('@prefix : <http://iri.org/#>.\n@prefix abc:<http://iri.org/#>.', | ||
{ type: '@prefix', line: 1 }, | ||
{ type: 'prefix', value: '', line: 1 }, | ||
{ type: 'IRI', value: 'http://uri.org/#', line: 1 }, | ||
{ type: 'IRI', value: 'http://iri.org/#', line: 1 }, | ||
{ type: '.', line: 1 }, | ||
{ type: '@prefix', line: 2 }, | ||
{ type: 'prefix', value: 'abc', line: 2 }, | ||
{ type: 'IRI', value: 'http://uri.org/#', line: 2 }, | ||
{ type: 'IRI', value: 'http://iri.org/#', line: 2 }, | ||
{ type: '.', line: 2 }, | ||
{ type: 'eof', line: 2 })); | ||
it('should not tokenize prefixes that end with a dot', | ||
shouldNotTokenize('@prefix abc.: <def>.', | ||
'Syntax error: unexpected "abc.:" on line 1.')); | ||
it('should tokenize @base declarations', | ||
shouldTokenize('@base <http://uri.org/#>.\n@base <http://uri.org/#>.', | ||
shouldTokenize('@base <http://iri.org/#>.\n@base <http://iri.org/#>.', | ||
{ type: '@base', line: 1 }, | ||
{ type: 'IRI', value: 'http://uri.org/#', line: 1 }, | ||
{ type: 'IRI', value: 'http://iri.org/#', line: 1 }, | ||
{ type: '.', line: 1 }, | ||
{ type: '@base', line: 2 }, | ||
{ type: 'IRI', value: 'http://uri.org/#', line: 2 }, | ||
{ type: 'IRI', value: 'http://iri.org/#', line: 2 }, | ||
{ type: '.', line: 2 }, | ||
@@ -487,21 +491,21 @@ { type: 'eof', line: 2 })); | ||
it('should tokenize PREFIX declarations', | ||
shouldTokenize('PREFIX : <http://uri.org/#>\npreFiX abc: <http://uri.org/#>', | ||
shouldTokenize('PREFIX : <http://iri.org/#>\npreFiX abc: <http://iri.org/#>', | ||
{ type: 'PREFIX', line: 1 }, | ||
{ type: 'prefix', value: '', line: 1 }, | ||
{ type: 'IRI', value: 'http://uri.org/#', line: 1 }, | ||
{ type: 'IRI', value: 'http://iri.org/#', line: 1 }, | ||
{ type: 'PREFIX', line: 2 }, | ||
{ type: 'prefix', value: 'abc', line: 2 }, | ||
{ type: 'IRI', value: 'http://uri.org/#', line: 2 }, | ||
{ type: 'IRI', value: 'http://iri.org/#', line: 2 }, | ||
{ type: 'eof', line: 2 })); | ||
it('should tokenize BASE declarations', | ||
shouldTokenize('BASE <http://uri.org/#>\nbAsE <http://uri.org/#>', | ||
shouldTokenize('BASE <http://iri.org/#>\nbAsE <http://iri.org/#>', | ||
{ type: 'BASE', line: 1 }, | ||
{ type: 'IRI', value: 'http://uri.org/#', line: 1 }, | ||
{ type: 'IRI', value: 'http://iri.org/#', line: 1 }, | ||
{ type: 'BASE', line: 2 }, | ||
{ type: 'IRI', value: 'http://uri.org/#', line: 2 }, | ||
{ type: 'IRI', value: 'http://iri.org/#', line: 2 }, | ||
{ type: 'eof', line: 2 })); | ||
it('should tokenize blank nodes', | ||
shouldTokenize('[] [<a> <b>] [a:b "c"^^d:e][a:b[]]', | ||
shouldTokenize('[] [<a> <b>] [a:b "c"^^d:e][a:b[]] _:a:b.', | ||
{ type: '[', line: 1 }, | ||
@@ -523,4 +527,11 @@ { type: ']', line: 1 }, | ||
{ type: ']', line: 1 }, | ||
{ type: 'prefixed', prefix: '_', value: 'a', line: 1 }, | ||
{ type: 'prefixed', prefix: '', value: 'b', line: 1 }, | ||
{ type: '.', line: 1 }, | ||
{ type: 'eof', line: 1 })); | ||
it('should not tokenize invalid blank nodes', | ||
shouldNotTokenize('_::', | ||
'Syntax error: unexpected "_::" on line 1.')); | ||
it('should tokenize lists', | ||
@@ -566,2 +577,70 @@ shouldTokenize('() (<a>) (<a> <b>)', | ||
it('should tokenize an empty default graph', | ||
shouldTokenize('{}', | ||
{ type: '{', line: 1 }, | ||
{ type: '}', line: 1 }, | ||
{ type: 'eof', line: 1 })); | ||
it('should tokenize a non-empty default graph', | ||
shouldTokenize('{<a> <b> c:d}', | ||
{ type: '{', line: 1 }, | ||
{ type: 'IRI', value: 'a', line: 1 }, | ||
{ type: 'IRI', value: 'b', line: 1 }, | ||
{ type: 'prefixed', prefix: 'c', value: 'd', line: 1 }, | ||
{ type: '}', line: 1 }, | ||
{ type: 'eof', line: 1 })); | ||
it('should tokenize an empty graph identified by an IRI', | ||
shouldTokenize('<g>{}', | ||
{ type: 'IRI', value: 'g', line: 1 }, | ||
{ type: '{', line: 1 }, | ||
{ type: '}', line: 1 }, | ||
{ type: 'eof', line: 1 })); | ||
it('should tokenize a non-empty graph identified by an IRI', | ||
shouldTokenize('<g> {<a> <b> c:d}', | ||
{ type: 'IRI', value: 'g', line: 1 }, | ||
{ type: '{', line: 1 }, | ||
{ type: 'IRI', value: 'a', line: 1 }, | ||
{ type: 'IRI', value: 'b', line: 1 }, | ||
{ type: 'prefixed', prefix: 'c', value: 'd', line: 1 }, | ||
{ type: '}', line: 1 }, | ||
{ type: 'eof', line: 1 })); | ||
it('should tokenize an empty graph identified by a blank node', | ||
shouldTokenize('_:g{}', | ||
{ type: 'prefixed', prefix: '_', value: 'g', line: 1 }, | ||
{ type: '{', line: 1 }, | ||
{ type: '}', line: 1 }, | ||
{ type: 'eof', line: 1 })); | ||
it('should tokenize a non-empty graph identified by a blank node', | ||
shouldTokenize('_:g {<a> <b> c:d}', | ||
{ type: 'prefixed', prefix: '_', value: 'g', line: 1 }, | ||
{ type: '{', line: 1 }, | ||
{ type: 'IRI', value: 'a', line: 1 }, | ||
{ type: 'IRI', value: 'b', line: 1 }, | ||
{ type: 'prefixed', prefix: 'c', value: 'd', line: 1 }, | ||
{ type: '}', line: 1 }, | ||
{ type: 'eof', line: 1 })); | ||
it('should tokenize an empty graph with the GRAPH keyword', | ||
shouldTokenize('GRAPH<g>{}', | ||
{ type: 'GRAPH', line: 1 }, | ||
{ type: 'IRI', value: 'g', line: 1 }, | ||
{ type: '{', line: 1 }, | ||
{ type: '}', line: 1 }, | ||
{ type: 'eof', line: 1 })); | ||
it('should tokenize a non-empty graph with the GRAPH keyword', | ||
shouldTokenize('graph <g> {<a> <b> c:d}', | ||
{ type: 'GRAPH', line: 1 }, | ||
{ type: 'IRI', value: 'g', line: 1 }, | ||
{ type: '{', line: 1 }, | ||
{ type: 'IRI', value: 'a', line: 1 }, | ||
{ type: 'IRI', value: 'b', line: 1 }, | ||
{ type: 'prefixed', prefix: 'c', value: 'd', line: 1 }, | ||
{ type: '}', line: 1 }, | ||
{ type: 'eof', line: 1 })); | ||
it('should not tokenize an invalid document', | ||
@@ -568,0 +647,0 @@ shouldNotTokenize(' \n @!', 'Syntax error: unexpected "@!" on line 2.')); |
@@ -71,3 +71,3 @@ var N3Parser = require('../N3').Parser; | ||
it('should parse a triple with a literal and a URI type', | ||
it('should parse a triple with a literal and an IRI type', | ||
shouldParse('<a> <b> "string"^^<type>.', | ||
@@ -158,5 +158,5 @@ ['a', 'b', '"string"^^type'])); | ||
shouldParse('_:a <b> _:c.', | ||
['_:b0', 'b', '_:b1'])); | ||
['_:b0_a', 'b', '_:b0_c'])); | ||
it('should not parse statements with named blank nodes', | ||
it('should not parse statements with blank predicates', | ||
shouldNotParse('<a> _:b <c>.', | ||
@@ -212,2 +212,6 @@ 'Disallowed blank node as predicate at line 1.')); | ||
it('should not parse a blank node with only a semicolon', | ||
shouldNotParse('<a> <b> [;].', | ||
'Unexpected ] at line 1.')); | ||
it('should parse a multi-statement blank node with trailing semicolon', | ||
@@ -286,7 +290,7 @@ shouldParse('<a> <b> [ <u> <v>; <w> <z>; ].', | ||
shouldParse('<a> <b> (_:x _:y).', | ||
['a', 'b', '_:b1'], | ||
['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0'], | ||
['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b3'], | ||
['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b2'], | ||
['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', | ||
['a', 'b', '_:b0'], | ||
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0_x'], | ||
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1' ], | ||
['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0_y'], | ||
['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', | ||
'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); | ||
@@ -352,3 +356,3 @@ | ||
it('should resolve URIs against @base', | ||
it('should resolve IRIs against @base', | ||
shouldParse('@base <http://ex.org/>.\n' + | ||
@@ -361,3 +365,3 @@ '<a> <b> <c>.\n' + | ||
it('should resolve URIs against SPARQL base', | ||
it('should resolve IRIs against SPARQL base', | ||
shouldParse('BASE <http://ex.org/>\n' + | ||
@@ -370,3 +374,3 @@ '<a> <b> <c>. ' + | ||
it('should resolve URIs against a @base with query string', | ||
it('should resolve IRIs against a @base with query string', | ||
shouldParse('@base <http://ex.org/?foo>.\n' + | ||
@@ -379,3 +383,3 @@ '<> <b> <c>.\n' + | ||
it('should resolve URIs with query string against @base', | ||
it('should resolve IRIs with query string against @base', | ||
shouldParse('@base <http://ex.org/>.\n' + | ||
@@ -391,3 +395,3 @@ '<?> <?a> <?a=b>.\n' + | ||
it('should not resolve URIs with colons', | ||
it('should not resolve IRIs with colons', | ||
shouldParse('@base <http://ex.org/>.\n' + | ||
@@ -401,2 +405,205 @@ '<a> <b> <c>.\n' + | ||
it('should resolve datatype IRIs against @base', | ||
shouldParse('@base <http://ex.org/>.\n' + | ||
'<a> <b> "c"^^<d>.\n' + | ||
'@base <d/>.\n' + | ||
'<e> <f> "g"^^<h>.', | ||
['http://ex.org/a', 'http://ex.org/b', '"c"^^http://ex.org/d'], | ||
['http://ex.org/d/e', 'http://ex.org/d/f', '"g"^^http://ex.org/d/h'])); | ||
it('should parse an empty default graph', | ||
shouldParse('{}')); | ||
it('should parse a one-triple default graph ending without a dot', | ||
shouldParse('{<a> <b> <c>}', | ||
['a', 'b', 'c'])); | ||
it('should parse a one-triple default graph ending with a dot', | ||
shouldParse('{<a> <b> <c>.}', | ||
['a', 'b', 'c'])); | ||
it('should parse a three-triple default graph ending without a dot', | ||
shouldParse('{<a> <b> <c>;<d> <e>,<f>}', | ||
['a', 'b', 'c'], | ||
['a', 'd', 'e'], | ||
['a', 'd', 'f'])); | ||
it('should parse a three-triple default graph ending with a dot', | ||
shouldParse('{<a> <b> <c>;<d> <e>,<f>.}', | ||
['a', 'b', 'c'], | ||
['a', 'd', 'e'], | ||
['a', 'd', 'f'])); | ||
it('should parse a three-triple default graph ending with a semicolon', | ||
shouldParse('{<a> <b> <c>;<d> <e>,<f>;}', | ||
['a', 'b', 'c'], | ||
['a', 'd', 'e'], | ||
['a', 'd', 'f'])); | ||
it('should parse an empty named graph with an IRI', | ||
shouldParse('<g>{}')); | ||
it('should parse a one-triple named graph with an IRI ending without a dot', | ||
shouldParse('<g> {<a> <b> <c>}', | ||
['a', 'b', 'c', 'g'])); | ||
it('should parse a one-triple named graph with an IRI ending with a dot', | ||
shouldParse('<g>{<a> <b> <c>.}', | ||
['a', 'b', 'c', 'g'])); | ||
it('should parse a three-triple named graph with an IRI ending without a dot', | ||
shouldParse('<g> {<a> <b> <c>;<d> <e>,<f>}', | ||
['a', 'b', 'c', 'g'], | ||
['a', 'd', 'e', 'g'], | ||
['a', 'd', 'f', 'g'])); | ||
it('should parse a three-triple named graph with an IRI ending with a dot', | ||
shouldParse('<g>{<a> <b> <c>;<d> <e>,<f>.}', | ||
['a', 'b', 'c', 'g'], | ||
['a', 'd', 'e', 'g'], | ||
['a', 'd', 'f', 'g'])); | ||
it('should parse an empty named graph with a prefixed name', | ||
shouldParse('@prefix g: <g#>.\ng:h {}')); | ||
it('should parse a one-triple named graph with a prefixed name ending without a dot', | ||
shouldParse('@prefix g: <g#>.\ng:h {<a> <b> <c>}', | ||
['a', 'b', 'c', 'g#h'])); | ||
it('should parse a one-triple named graph with a prefixed name ending with a dot', | ||
shouldParse('@prefix g: <g#>.\ng:h{<a> <b> <c>.}', | ||
['a', 'b', 'c', 'g#h'])); | ||
it('should parse a three-triple named graph with a prefixed name ending without a dot', | ||
shouldParse('@prefix g: <g#>.\ng:h {<a> <b> <c>;<d> <e>,<f>}', | ||
['a', 'b', 'c', 'g#h'], | ||
['a', 'd', 'e', 'g#h'], | ||
['a', 'd', 'f', 'g#h'])); | ||
it('should parse a three-triple named graph with a prefixed name ending with a dot', | ||
shouldParse('@prefix g: <g#>.\ng:h{<a> <b> <c>;<d> <e>,<f>.}', | ||
['a', 'b', 'c', 'g#h'], | ||
['a', 'd', 'e', 'g#h'], | ||
['a', 'd', 'f', 'g#h'])); | ||
it('should parse an empty anonymous graph', | ||
shouldParse('[] {}')); | ||
it('should parse a one-triple anonymous graph ending without a dot', | ||
shouldParse('[] {<a> <b> <c>}', | ||
['a', 'b', 'c', '_:b0'])); | ||
it('should parse a one-triple anonymous graph ending with a dot', | ||
shouldParse('[]{<a> <b> <c>.}', | ||
['a', 'b', 'c', '_:b0'])); | ||
it('should parse a three-triple anonymous graph ending without a dot', | ||
shouldParse('[] {<a> <b> <c>;<d> <e>,<f>}', | ||
['a', 'b', 'c', '_:b0'], | ||
['a', 'd', 'e', '_:b0'], | ||
['a', 'd', 'f', '_:b0'])); | ||
it('should parse a three-triple anonymous graph ending with a dot', | ||
shouldParse('[]{<a> <b> <c>;<d> <e>,<f>.}', | ||
['a', 'b', 'c', '_:b0'], | ||
['a', 'd', 'e', '_:b0'], | ||
['a', 'd', 'f', '_:b0'])); | ||
it('should parse an empty named graph with an IRI and the GRAPH keyword', | ||
shouldParse('GRAPH <g> {}')); | ||
it('should parse an empty named graph with a prefixed name and the GRAPH keyword', | ||
shouldParse('@prefix g: <g#>.\nGRAPH g:h {}')); | ||
it('should parse an empty anonymous graph and the GRAPH keyword', | ||
shouldParse('GRAPH [] {}')); | ||
it('should parse a one-triple named graph with an IRI and the GRAPH keyword', | ||
shouldParse('GRAPH <g> {<a> <b> <c>}', | ||
['a', 'b', 'c', 'g'])); | ||
it('should parse a one-triple named graph with a prefixed name and the GRAPH keyword', | ||
shouldParse('@prefix g: <g#>.\nGRAPH g:h {<a> <b> <c>}', | ||
['a', 'b', 'c', 'g#h'])); | ||
it('should parse a one-triple anonymous graph and the GRAPH keyword', | ||
shouldParse('GRAPH [] {<a> <b> <c>}', | ||
['a', 'b', 'c', '_:b0'])); | ||
it('should parse a graph with 8-bit unicode escape sequences', | ||
shouldParse('<\\U0001d400> {\n<\\U0001d400> <\\U0001d400> "\\U0001d400"^^<\\U0001d400>\n}\n', | ||
['\ud835\udC00', '\ud835\udc00', '"\ud835\udc00"^^\ud835\udc00', '\ud835\udc00'])); | ||
it('should not parse a single closing brace', | ||
shouldNotParse('}', | ||
'Unexpected graph closing at line 1.')); | ||
it('should not parse a single opening brace', | ||
shouldNotParse('{', | ||
'Expected subject but got eof at line 1.')); | ||
it('should not parse a superfluous closing brace ', | ||
shouldNotParse('{}}', | ||
'Unexpected graph closing at line 1.')); | ||
it('should not parse a graph with only a dot', | ||
shouldNotParse('{.}', | ||
'Expected subject but got . at line 1.')); | ||
it('should not parse a graph with only a semicolon', | ||
shouldNotParse('{;}', | ||
'Expected subject but got ; at line 1.')); | ||
it('should not parse an unclosed graph', | ||
shouldNotParse('{<a> <b> <c>.', | ||
'Unclosed graph at line 1.')); | ||
it('should not parse a named graph with a list node as label', | ||
shouldNotParse('() {}', | ||
'Expected predicate to follow "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil" at line 1.')); | ||
it('should not parse a named graph with a non-empty blank node as label', | ||
shouldNotParse('[<a> <b>] {}', | ||
'Expected predicate to follow "_:b0" at line 1.')); | ||
it('should not parse a named graph with the GRAPH keyword and a non-empty blank node as label', | ||
shouldNotParse('GRAPH [<a> <b>] {}', | ||
'Invalid graph label at line 1.')); | ||
it('should not parse a triple after the GRAPH keyword', | ||
shouldNotParse('GRAPH <a> <b> <c>.', | ||
'Expected graph but got IRI at line 1.')); | ||
it('should not parse repeated GRAPH keywords', | ||
shouldNotParse('GRAPH GRAPH <g> {}', | ||
'Invalid graph label at line 1.')); | ||
it('should parse a quad with 4 IRIs', | ||
shouldParse('<a> <b> <c> <g>.', | ||
['a', 'b', 'c', 'g'])); | ||
it('should parse a quad with 4 prefixed names', | ||
shouldParse('@prefix p: <p#>.\np:a p:b p:c p:g.', | ||
['p#a', 'p#b', 'p#c', 'p#g'])); | ||
it('should not parse a quad with an undefined prefix', | ||
shouldNotParse('<a> <b> <c> p:g.', | ||
'Undefined prefix "p:" at line 1.')); | ||
it('should parse a quad with 3 IRIs and a literal', | ||
shouldParse('<a> <b> "c"^^<d> <g>.', | ||
['a', 'b', '"c"^^d', 'g'])); | ||
it('should parse a quad with 2 blank nodes and a literal', | ||
shouldParse('_:a <b> "c"^^<d> _:g.', | ||
['_:b0_a', 'b', '"c"^^d', '_:b0_g'])); | ||
it('should not parse a quad in a graph', | ||
shouldNotParse('{<a> <b> <c> <g>.}', | ||
'Expected punctuation to follow "c" at line 1.')); | ||
it('should not parse a quad with different punctuation', | ||
shouldNotParse('<a> <b> <c> <g>;', | ||
'Expected dot to follow quad at line 1.')); | ||
it('should not parse base declarations without IRI', | ||
@@ -409,4 +616,8 @@ shouldNotParse('@base a: ', | ||
'<a> <b> <c>.\n', | ||
'Invalid base URI at line 1.')); | ||
'Invalid base IRI at line 1.')); | ||
it('should not parse improperly nested parentheses and brackets', | ||
shouldNotParse('<a> <b> [<c> (<d>]).', | ||
'Expected list item instead of "]" at line 1.')); | ||
it('should not parse improperly nested square brackets', | ||
@@ -434,3 +645,3 @@ shouldNotParse('<a> <b> [<c> <d>]].', | ||
shouldNotParse('<a> .', | ||
'Unexpected dot at line 1.')); | ||
'Unexpected . at line 1.')); | ||
@@ -447,3 +658,3 @@ it('should error if an unexpected token follows a subject', | ||
var prefixes = {}; | ||
new N3Parser().parse('@prefix a: <URIa>. a:a a:b a:c. @prefix b: <URIb>.', | ||
new N3Parser().parse('@prefix a: <IRIa>. a:a a:b a:c. @prefix b: <IRIb>.', | ||
tripleCallback, prefixCallback); | ||
@@ -455,4 +666,4 @@ | ||
Object.keys(prefixes).should.have.length(2); | ||
prefixes.should.have.property('a', 'URIa'); | ||
prefixes.should.have.property('b', 'URIb'); | ||
expect(prefixes).to.have.property('a', 'IRIa'); | ||
expect(prefixes).to.have.property('b', 'IRIb'); | ||
done(); | ||
@@ -462,6 +673,6 @@ } | ||
function prefixCallback(prefix, uri) { | ||
function prefixCallback(prefix, iri) { | ||
expect(prefix).to.exist; | ||
expect(uri).to.exist; | ||
prefixes[prefix] = uri; | ||
expect(iri).to.exist; | ||
prefixes[prefix] = iri; | ||
} | ||
@@ -471,3 +682,3 @@ }); | ||
it('should return prefixes at the last triple callback', function (done) { | ||
new N3Parser().parse('@prefix a: <URIa>. a:a a:b a:c. @prefix b: <URIb>.', tripleCallback); | ||
new N3Parser().parse('@prefix a: <IRIa>. a:a a:b a:c. @prefix b: <IRIb>.', tripleCallback); | ||
@@ -481,4 +692,4 @@ function tripleCallback(error, triple, prefixes) { | ||
Object.keys(prefixes).should.have.length(2); | ||
prefixes.should.have.property('a', 'URIa'); | ||
prefixes.should.have.property('b', 'URIb'); | ||
expect(prefixes).to.have.property('a', 'IRIa'); | ||
expect(prefixes).to.have.property('b', 'IRIb'); | ||
done(); | ||
@@ -502,14 +713,14 @@ } | ||
describe('An N3Parser instance with a document URI', function () { | ||
var parser = new N3Parser({ documentURI: 'http://ex.org/doc/f.ttl' }); | ||
describe('An N3Parser instance with a document IRI', function () { | ||
var parser = new N3Parser({ documentIRI: 'http://ex.org/doc/f.ttl' }); | ||
it('should resolve URIs against the document URI', | ||
it('should resolve IRIs against the document IRI', | ||
shouldParse(parser, | ||
'@prefix : <#>.\n' + | ||
'<a> <b> <c>.\n' + | ||
':e :f :g.', | ||
['http://ex.org/doc/a', 'http://ex.org/doc/b', 'http://ex.org/doc/c'], | ||
['http://ex.org/doc/f.ttl#e', 'http://ex.org/doc/f.ttl#f', 'http://ex.org/doc/f.ttl#g'])); | ||
'<a> <b> <c> <g>.\n' + | ||
':d :e :f :g.', | ||
['http://ex.org/doc/a', 'http://ex.org/doc/b', 'http://ex.org/doc/c', 'http://ex.org/doc/g'], | ||
['http://ex.org/doc/f.ttl#d', 'http://ex.org/doc/f.ttl#e', 'http://ex.org/doc/f.ttl#f', 'http://ex.org/doc/f.ttl#g'])); | ||
it('should resolve URIs with a trailing slashes against the document URI', | ||
it('should resolve IRIs with a trailing slashes against the document IRI', | ||
shouldParse(parser, | ||
@@ -519,2 +730,7 @@ '</a> </a/b> </a/b/c>.\n', | ||
it('should resolve datatype IRIs against the document IRI', | ||
shouldParse(parser, | ||
'<a> <b> "c"^^<d>.', | ||
['http://ex.org/doc/a', 'http://ex.org/doc/b', '"c"^^http://ex.org/doc/d'])); | ||
it('should respect @base statements', | ||
@@ -535,9 +751,9 @@ shouldParse(parser, | ||
describe('An N3Parser instance with an invalid document URI', function () { | ||
describe('An N3Parser instance with an invalid document IRI', function () { | ||
it('cannot be created', function (done) { | ||
try { | ||
new N3Parser({ documentURI: 'http://ex.org/doc/f#' }); | ||
new N3Parser({ documentIRI: 'http://ex.org/doc/f#' }); | ||
} | ||
catch (error) { | ||
error.message.should.equal('Invalid document URI'); | ||
error.message.should.equal('Invalid document IRI'); | ||
done(); | ||
@@ -547,2 +763,86 @@ } | ||
}); | ||
describe('An N3Parser instance with a non-string format', function () { | ||
var parser = new N3Parser({ format: 1 }); | ||
it('should parse a single triple', | ||
shouldParse(parser, '<a> <b> <c>.', ['a', 'b', 'c'])); | ||
it('should parse a graph', | ||
shouldParse(parser, '{<a> <b> <c>}', ['a', 'b', 'c'])); | ||
}); | ||
describe('An N3Parser instance for the Turtle format', function () { | ||
var parser = new N3Parser({ format: 'Turtle' }); | ||
it('should parse a single triple', | ||
shouldParse(parser, '<a> <b> <c>.', ['a', 'b', 'c'])); | ||
it('should not parse a default graph', | ||
shouldNotParse(parser, '{}', 'Expected subject but got { at line 1.')); | ||
it('should not parse a named graph', | ||
shouldNotParse(parser, '<g> {}', 'Expected predicate to follow "g" at line 1.')); | ||
it('should not parse a named graph with the GRAPH keyword', | ||
shouldNotParse(parser, 'GRAPH <g> {}', 'Expected subject but got GRAPH at line 1.')); | ||
it('should not parse a quad', | ||
shouldNotParse(parser, '<a> <b> <c> <d>.', 'Expected punctuation to follow "c" at line 1.')); | ||
}); | ||
describe('An N3Parser instance for the TriG format', function () { | ||
var parser = new N3Parser({ format: 'TriG' }); | ||
it('should parse a single triple', | ||
shouldParse(parser, '<a> <b> <c>.', ['a', 'b', 'c'])); | ||
it('should parse a default graph', | ||
shouldParse(parser, '{}')); | ||
it('should parse a named graph', | ||
shouldParse(parser, '<g> {}')); | ||
it('should parse a named graph with the GRAPH keyword', | ||
shouldParse(parser, 'GRAPH <g> {}')); | ||
it('should not parse a quad', | ||
shouldNotParse(parser, '<a> <b> <c> <d>.', 'Expected punctuation to follow "c" at line 1.')); | ||
}); | ||
describe('An N3Parser instance for the N-Triples format', function () { | ||
var parser = new N3Parser({ format: 'N-Triples' }); | ||
it('should parse a single triple', | ||
shouldParse(parser, '<http://ex.org/a> <http://ex.org/b> "c".', | ||
['http://ex.org/a', 'http://ex.org/b', '"c"'])); | ||
it('should not parse a single quad', | ||
shouldNotParse(parser, '<http://ex.org/a> <http://ex.org/b> "c" <http://ex.org/g>.', | ||
'Expected punctuation to follow ""c"" at line 1.')); | ||
it('should not parse relative IRIs', | ||
shouldNotParse(parser, '<a> <b> <c>.', 'Disallowed relative IRI at line 1.')); | ||
it('should not parse a prefix declaration', | ||
shouldNotParse(parser, '@prefix : <p#>.', 'Syntax error: unexpected "@prefix" on line 1.')); | ||
}); | ||
describe('An N3Parser instance for the N-Quads format', function () { | ||
var parser = new N3Parser({ format: 'N-Quads' }); | ||
it('should parse a single triple', | ||
shouldParse(parser, '<http://ex.org/a> <http://ex.org/b> <http://ex.org/c>.', | ||
['http://ex.org/a', 'http://ex.org/b', 'http://ex.org/c'])); | ||
it('should parse a single quad', | ||
shouldParse(parser, '<http://ex.org/a> <http://ex.org/b> "c" <http://ex.org/g>.', | ||
['http://ex.org/a', 'http://ex.org/b', '"c"', 'http://ex.org/g'])); | ||
it('should not parse relative IRIs', | ||
shouldNotParse(parser, '<a> <b> <c>.', 'Disallowed relative IRI at line 1.')); | ||
it('should not parse a prefix declaration', | ||
shouldNotParse(parser, '@prefix : <p#>.', 'Syntax error: unexpected "@prefix" on line 1.')); | ||
}); | ||
}); | ||
@@ -554,5 +854,5 @@ | ||
items = expected.map(function (item) { | ||
return { subject: item[0], predicate: item[1], object: item[2], | ||
context: item[3] || 'n3/contexts#default' }; | ||
return { subject: item[0], predicate: item[1], object: item[2], graph: item[3] || '' }; | ||
}); | ||
N3Parser._resetBlankNodeIds(); | ||
// Shift parameters if necessary | ||
@@ -578,5 +878,9 @@ if (!hasParser) | ||
function shouldNotParse(input, expectedError) { | ||
function shouldNotParse(parser, input, expectedError) { | ||
// Shift parameters if necessary | ||
if (!(parser instanceof N3Parser)) | ||
expectedError = input, input = parser, parser = new N3Parser(); | ||
return function (done) { | ||
new N3Parser().parse(input, function (error, triple) { | ||
parser.parse(input, function (error, triple) { | ||
if (error) { | ||
@@ -583,0 +887,0 @@ expect(triple).not.to.exist; |
@@ -23,28 +23,24 @@ var N3Store = require('../N3').Store; | ||
describe('An empty N3Store', function () { | ||
var n3Store = new N3Store(); | ||
var store = new N3Store(); | ||
it('should have size 0', function () { | ||
expect(n3Store.size).to.eql(0); // special test format for IE9 | ||
expect(store.size).to.eql(0); | ||
}); | ||
it('should be empty', function () { | ||
n3Store.find().should.be.empty; | ||
store.find().should.be.empty; | ||
}); | ||
it('should have a default context', function () { | ||
n3Store.defaultContext.should.eql('n3/contexts#default'); | ||
}); | ||
it('should be able to create unnamed blank nodes', function () { | ||
n3Store.createBlankNode().should.eql('_:b0'); | ||
n3Store.createBlankNode().should.eql('_:b1'); | ||
store.createBlankNode().should.eql('_:b0'); | ||
store.createBlankNode().should.eql('_:b1'); | ||
n3Store.addTriple('_:b0', '_:b1', '_:b2'); | ||
n3Store.createBlankNode().should.eql('_:b3'); | ||
store.addTriple('_:b0', '_:b1', '_:b2'); | ||
store.createBlankNode().should.eql('_:b3'); | ||
}); | ||
it('should be able to create named blank nodes', function () { | ||
n3Store.createBlankNode('blank').should.eql('_:blank'); | ||
n3Store.createBlankNode('blank').should.eql('_:blank1'); | ||
n3Store.createBlankNode('blank').should.eql('_:blank2'); | ||
store.createBlankNode('blank').should.eql('_:blank'); | ||
store.createBlankNode('blank').should.eql('_:blank1'); | ||
store.createBlankNode('blank').should.eql('_:blank2'); | ||
}); | ||
@@ -54,3 +50,3 @@ }); | ||
describe('An N3Store with initialized with 3 elements', function () { | ||
var n3Store = new N3Store([ | ||
var store = new N3Store([ | ||
{ subject: 's1', predicate: 'p1', object: 'o1'}, | ||
@@ -62,3 +58,3 @@ { subject: 's1', predicate: 'p1', object: 'o2'}, | ||
it('should have size 3', function () { | ||
n3Store.size.should.eql(3); | ||
store.size.should.eql(3); | ||
}); | ||
@@ -68,18 +64,18 @@ }); | ||
describe('An N3Store with 5 elements', function () { | ||
var n3Store = new N3Store(); | ||
n3Store.addTriple('s1', 'p1', 'o1'); | ||
n3Store.addTriple({ subject: 's1', predicate: 'p1', object: 'o2'}); | ||
n3Store.addTriples([ | ||
var store = new N3Store(); | ||
store.addTriple('s1', 'p1', 'o1'); | ||
store.addTriple({ subject: 's1', predicate: 'p1', object: 'o2'}); | ||
store.addTriples([ | ||
{ subject: 's1', predicate: 'p2', object: 'o2'}, | ||
{ subject: 's2', predicate: 'p1', object: 'o1'}, | ||
]); | ||
n3Store.addTriple('s1', 'p2', 'o3', 'c4'); | ||
store.addTriple('s1', 'p2', 'o3', 'c4'); | ||
it('should have size 5', function () { | ||
n3Store.size.should.eql(5); | ||
store.size.should.eql(5); | ||
}); | ||
describe('when searched without parameters', function () { | ||
it('should return all items in the default context', | ||
shouldIncludeAll(n3Store.find(), | ||
it('should return all items in the default graph', | ||
shouldIncludeAll(store.find(), | ||
['s1', 'p1', 'o1'], ['s1', 'p1', 'o2'], ['s1', 'p2', 'o2'], ['s2', 'p1', 'o1'])); | ||
@@ -89,4 +85,4 @@ }); | ||
describe('when searched with an existing subject parameter', function () { | ||
it('should return all items with this subject in the default context', | ||
shouldIncludeAll(n3Store.find('s1', null, null), | ||
it('should return all items with this subject in the default graph', | ||
shouldIncludeAll(store.find('s1', null, null), | ||
['s1', 'p1', 'o1'], ['s1', 'p1', 'o2'], ['s1', 'p2', 'o2'])); | ||
@@ -96,12 +92,12 @@ }); | ||
describe('when searched with a non-existing subject parameter', function () { | ||
itShouldBeEmpty(n3Store.find('s3', null, null)); | ||
itShouldBeEmpty(store.find('s3', null, null)); | ||
}); | ||
describe('when searched with a non-existing subject parameter that exists elsewhere', function () { | ||
itShouldBeEmpty(n3Store.find('p1', null, null)); | ||
itShouldBeEmpty(store.find('p1', null, null)); | ||
}); | ||
describe('when searched with an existing predicate parameter', function () { | ||
it('should return all items with this predicate in the default context', | ||
shouldIncludeAll(n3Store.find(null, 'p1', null), | ||
it('should return all items with this predicate in the default graph', | ||
shouldIncludeAll(store.find(null, 'p1', null), | ||
['s1', 'p1', 'o1'], ['s1', 'p1', 'o2'], ['s2', 'p1', 'o1'])); | ||
@@ -111,68 +107,68 @@ }); | ||
describe('when searched with a non-existing predicate parameter', function () { | ||
itShouldBeEmpty(n3Store.find(null, 'p3', null)); | ||
itShouldBeEmpty(store.find(null, 'p3', null)); | ||
}); | ||
describe('when searched with an existing object parameter', function () { | ||
it('should return all items with this object in the default context', | ||
shouldIncludeAll(n3Store.find(null, null, 'o1'), ['s1', 'p1', 'o1'], ['s2', 'p1', 'o1'])); | ||
it('should return all items with this object in the default graph', | ||
shouldIncludeAll(store.find(null, null, 'o1'), ['s1', 'p1', 'o1'], ['s2', 'p1', 'o1'])); | ||
}); | ||
describe('when searched with a non-existing object parameter', function () { | ||
itShouldBeEmpty(n3Store.find(null, null, 'o4')); | ||
itShouldBeEmpty(store.find(null, null, 'o4')); | ||
}); | ||
describe('when searched with existing subject and predicate parameters', function () { | ||
it('should return all items with this subject and predicate in the default context', | ||
shouldIncludeAll(n3Store.find('s1', 'p1', null), ['s1', 'p1', 'o1'], ['s1', 'p1', 'o2'])); | ||
it('should return all items with this subject and predicate in the default graph', | ||
shouldIncludeAll(store.find('s1', 'p1', null), ['s1', 'p1', 'o1'], ['s1', 'p1', 'o2'])); | ||
}); | ||
describe('when searched with non-existing subject and predicate parameters', function () { | ||
itShouldBeEmpty(n3Store.find('s2', 'p2', null)); | ||
itShouldBeEmpty(store.find('s2', 'p2', null)); | ||
}); | ||
describe('when searched with existing subject and object parameters', function () { | ||
it('should return all items with this subject and object in the default context', | ||
shouldIncludeAll(n3Store.find('s1', null, 'o2'), ['s1', 'p1', 'o2'], ['s1', 'p2', 'o2'])); | ||
it('should return all items with this subject and object in the default graph', | ||
shouldIncludeAll(store.find('s1', null, 'o2'), ['s1', 'p1', 'o2'], ['s1', 'p2', 'o2'])); | ||
}); | ||
describe('when searched with non-existing subject and object parameters', function () { | ||
itShouldBeEmpty(n3Store.find('s2', 'p2', null)); | ||
itShouldBeEmpty(store.find('s2', 'p2', null)); | ||
}); | ||
describe('when searched with existing predicate and object parameters', function () { | ||
it('should return all items with this predicate and object in the default context', | ||
shouldIncludeAll(n3Store.find(null, 'p1', 'o1'), ['s1', 'p1', 'o1'], ['s2', 'p1', 'o1'])); | ||
it('should return all items with this predicate and object in the default graph', | ||
shouldIncludeAll(store.find(null, 'p1', 'o1'), ['s1', 'p1', 'o1'], ['s2', 'p1', 'o1'])); | ||
}); | ||
describe('when searched with non-existing predicate and object parameters', function () { | ||
itShouldBeEmpty(n3Store.find(null, 'p2', 'o3')); | ||
itShouldBeEmpty(store.find(null, 'p2', 'o3')); | ||
}); | ||
describe('when searched with existing subject, predicate, and object parameters', function () { | ||
it('should return all items with this subject, predicate, and object in the default context', | ||
shouldIncludeAll(n3Store.find('s1', 'p1', 'o1'), ['s1', 'p1', 'o1'])); | ||
it('should return all items with this subject, predicate, and object in the default graph', | ||
shouldIncludeAll(store.find('s1', 'p1', 'o1'), ['s1', 'p1', 'o1'])); | ||
}); | ||
describe('when searched with a non-existing triple', function () { | ||
itShouldBeEmpty(n3Store.find('s2', 'p2', 'o1')); | ||
itShouldBeEmpty(store.find('s2', 'p2', 'o1')); | ||
}); | ||
describe('when searched with the default context parameter', function () { | ||
it('should return all items in the default context', | ||
shouldIncludeAll(n3Store.find(), | ||
describe('when searched with the default graph parameter', function () { | ||
it('should return all items in the default graph', | ||
shouldIncludeAll(store.find(), | ||
['s1', 'p1', 'o1'], ['s1', 'p1', 'o2'], ['s1', 'p2', 'o2'], ['s2', 'p1', 'o1'])); | ||
}); | ||
describe('when searched with an existing non-default context parameter', function () { | ||
it('should return all items in that context', | ||
shouldIncludeAll(n3Store.find(null, null, null, 'c4'), ['s1', 'p2', 'o3', 'c4'])); | ||
describe('when searched with an existing non-default graph parameter', function () { | ||
it('should return all items in that graph', | ||
shouldIncludeAll(store.find(null, null, null, 'c4'), ['s1', 'p2', 'o3', 'c4'])); | ||
}); | ||
describe('when searched with a non-existing non-default context parameter', function () { | ||
itShouldBeEmpty(n3Store.find(null, null, null, 'c5')); | ||
describe('when searched with a non-existing non-default graph parameter', function () { | ||
itShouldBeEmpty(store.find(null, null, null, 'c5')); | ||
}); | ||
describe('when counted without parameters', function () { | ||
it('should count all items in the default context', function () { | ||
n3Store.count().should.equal(4); | ||
it('should count all items in the default graph', function () { | ||
store.count().should.equal(4); | ||
}); | ||
@@ -182,4 +178,4 @@ }); | ||
describe('when counted with an existing subject parameter', function () { | ||
it('should count all items with this subject in the default context', function () { | ||
n3Store.count('s1', null, null).should.equal(3); | ||
it('should count all items with this subject in the default graph', function () { | ||
store.count('s1', null, null).should.equal(3); | ||
}); | ||
@@ -190,3 +186,3 @@ }); | ||
it('should be empty', function () { | ||
n3Store.count('s3', null, null).should.equal(0); | ||
store.count('s3', null, null).should.equal(0); | ||
}); | ||
@@ -197,3 +193,3 @@ }); | ||
it('should be empty', function () { | ||
n3Store.count('p1', null, null).should.equal(0); | ||
store.count('p1', null, null).should.equal(0); | ||
}); | ||
@@ -203,4 +199,4 @@ }); | ||
describe('when counted with an existing predicate parameter', function () { | ||
it('should count all items with this predicate in the default context', function () { | ||
n3Store.count(null, 'p1', null).should.equal(3); | ||
it('should count all items with this predicate in the default graph', function () { | ||
store.count(null, 'p1', null).should.equal(3); | ||
}); | ||
@@ -211,3 +207,3 @@ }); | ||
it('should be empty', function () { | ||
n3Store.count(null, 'p3', null).should.equal(0); | ||
store.count(null, 'p3', null).should.equal(0); | ||
}); | ||
@@ -217,4 +213,4 @@ }); | ||
describe('when counted with an existing object parameter', function () { | ||
it('should count all items with this object in the default context', function () { | ||
n3Store.count(null, null, 'o1').should.equal(2); | ||
it('should count all items with this object in the default graph', function () { | ||
store.count(null, null, 'o1').should.equal(2); | ||
}); | ||
@@ -225,3 +221,3 @@ }); | ||
it('should be empty', function () { | ||
n3Store.count(null, null, 'o4').should.equal(0); | ||
store.count(null, null, 'o4').should.equal(0); | ||
}); | ||
@@ -231,4 +227,4 @@ }); | ||
describe('when counted with existing subject and predicate parameters', function () { | ||
it('should count all items with this subject and predicate in the default context', function () { | ||
n3Store.count('s1', 'p1', null).should.equal(2); | ||
it('should count all items with this subject and predicate in the default graph', function () { | ||
store.count('s1', 'p1', null).should.equal(2); | ||
}); | ||
@@ -239,3 +235,3 @@ }); | ||
it('should be empty', function () { | ||
n3Store.count('s2', 'p2', null).should.equal(0); | ||
store.count('s2', 'p2', null).should.equal(0); | ||
}); | ||
@@ -245,4 +241,4 @@ }); | ||
describe('when counted with existing subject and object parameters', function () { | ||
it('should count all items with this subject and object in the default context', function () { | ||
n3Store.count('s1', null, 'o2').should.equal(2); | ||
it('should count all items with this subject and object in the default graph', function () { | ||
store.count('s1', null, 'o2').should.equal(2); | ||
}); | ||
@@ -253,3 +249,3 @@ }); | ||
it('should be empty', function () { | ||
n3Store.count('s2', 'p2', null).should.equal(0); | ||
store.count('s2', 'p2', null).should.equal(0); | ||
}); | ||
@@ -259,4 +255,4 @@ }); | ||
describe('when counted with existing predicate and object parameters', function () { | ||
it('should count all items with this predicate and object in the default context', function () { | ||
n3Store.count(null, 'p1', 'o1').should.equal(2); | ||
it('should count all items with this predicate and object in the default graph', function () { | ||
store.count(null, 'p1', 'o1').should.equal(2); | ||
}); | ||
@@ -267,3 +263,3 @@ }); | ||
it('should be empty', function () { | ||
n3Store.count(null, 'p2', 'o3').should.equal(0); | ||
store.count(null, 'p2', 'o3').should.equal(0); | ||
}); | ||
@@ -273,4 +269,4 @@ }); | ||
describe('when counted with existing subject, predicate, and object parameters', function () { | ||
it('should count all items with this subject, predicate, and object in the default context', function () { | ||
n3Store.count('s1', 'p1', 'o1').should.equal(1); | ||
it('should count all items with this subject, predicate, and object in the default graph', function () { | ||
store.count('s1', 'p1', 'o1').should.equal(1); | ||
}); | ||
@@ -281,21 +277,21 @@ }); | ||
it('should be empty', function () { | ||
n3Store.count('s2', 'p2', 'o1').should.equal(0); | ||
store.count('s2', 'p2', 'o1').should.equal(0); | ||
}); | ||
}); | ||
describe('when counted with the default context parameter', function () { | ||
it('should count all items in the default context', function () { | ||
n3Store.count().should.equal(4); | ||
describe('when counted with the default graph parameter', function () { | ||
it('should count all items in the default graph', function () { | ||
store.count().should.equal(4); | ||
}); | ||
}); | ||
describe('when counted with an existing non-default context parameter', function () { | ||
it('should count all items in that context', function () { | ||
n3Store.count(null, null, null, 'c4').should.equal(1); | ||
describe('when counted with an existing non-default graph parameter', function () { | ||
it('should count all items in that graph', function () { | ||
store.count(null, null, null, 'c4').should.equal(1); | ||
}); | ||
}); | ||
describe('when counted with a non-existing non-default context parameter', function () { | ||
describe('when counted with a non-existing non-default graph parameter', function () { | ||
it('should be empty', function () { | ||
n3Store.count(null, null, null, 'c5').should.equal(0); | ||
store.count(null, null, null, 'c5').should.equal(0); | ||
}); | ||
@@ -305,62 +301,62 @@ }); | ||
describe('when trying to remove a triple with a non-existing subject', function () { | ||
before(function () { n3Store.removeTriple('s0', 'p1', 'o1'); }); | ||
it('should still have size 5', function () { n3Store.size.should.eql(5); }); | ||
before(function () { store.removeTriple('s0', 'p1', 'o1'); }); | ||
it('should still have size 5', function () { store.size.should.eql(5); }); | ||
}); | ||
describe('when trying to remove a triple with a non-existing predicate', function () { | ||
before(function () { n3Store.removeTriple('s1', 'p0', 'o1'); }); | ||
it('should still have size 5', function () { n3Store.size.should.eql(5); }); | ||
before(function () { store.removeTriple('s1', 'p0', 'o1'); }); | ||
it('should still have size 5', function () { store.size.should.eql(5); }); | ||
}); | ||
describe('when trying to remove a triple with a non-existing object', function () { | ||
before(function () { n3Store.removeTriple('s1', 'p1', 'o0'); }); | ||
it('should still have size 5', function () { n3Store.size.should.eql(5); }); | ||
before(function () { store.removeTriple('s1', 'p1', 'o0'); }); | ||
it('should still have size 5', function () { store.size.should.eql(5); }); | ||
}); | ||
describe('when trying to remove a triple for which no subjects exist', function () { | ||
before(function () { n3Store.removeTriple('o1', 'p1', 'o1'); }); | ||
it('should still have size 5', function () { n3Store.size.should.eql(5); }); | ||
before(function () { store.removeTriple('o1', 'p1', 'o1'); }); | ||
it('should still have size 5', function () { store.size.should.eql(5); }); | ||
}); | ||
describe('when trying to remove a triple for which no predicates exist', function () { | ||
before(function () { n3Store.removeTriple('s1', 's1', 'o1'); }); | ||
it('should still have size 5', function () { n3Store.size.should.eql(5); }); | ||
before(function () { store.removeTriple('s1', 's1', 'o1'); }); | ||
it('should still have size 5', function () { store.size.should.eql(5); }); | ||
}); | ||
describe('when trying to remove a triple for which no objects exist', function () { | ||
before(function () { n3Store.removeTriple('s1', 'p1', 's1'); }); | ||
it('should still have size 5', function () { n3Store.size.should.eql(5); }); | ||
before(function () { store.removeTriple('s1', 'p1', 's1'); }); | ||
it('should still have size 5', function () { store.size.should.eql(5); }); | ||
}); | ||
describe('when trying to remove a triple that does not exist', function () { | ||
before(function () { n3Store.removeTriple('s1', 'p2', 'o1'); }); | ||
it('should still have size 5', function () { n3Store.size.should.eql(5); }); | ||
before(function () { store.removeTriple('s1', 'p2', 'o1'); }); | ||
it('should still have size 5', function () { store.size.should.eql(5); }); | ||
}); | ||
describe('when trying to remove an incomplete triple', function () { | ||
before(function () { n3Store.removeTriple('s1', null, null); }); | ||
it('should still have size 5', function () { n3Store.size.should.eql(5); }); | ||
before(function () { store.removeTriple('s1', null, null); }); | ||
it('should still have size 5', function () { store.size.should.eql(5); }); | ||
}); | ||
describe('when trying to remove a triple with a non-existing context', function () { | ||
before(function () { n3Store.removeTriple('s1', 'p1', 'o1', 'c0'); }); | ||
it('should still have size 5', function () { n3Store.size.should.eql(5); }); | ||
describe('when trying to remove a triple with a non-existing graph', function () { | ||
before(function () { store.removeTriple('s1', 'p1', 'o1', 'c0'); }); | ||
it('should still have size 5', function () { store.size.should.eql(5); }); | ||
}); | ||
describe('when removing an existing triple', function () { | ||
before(function () { n3Store.removeTriple('s1', 'p1', 'o1'); }); | ||
before(function () { store.removeTriple('s1', 'p1', 'o1'); }); | ||
it('should have size 4', function () { n3Store.size.should.eql(4); }); | ||
it('should have size 4', function () { store.size.should.eql(4); }); | ||
it('should not contain that triple anymore', | ||
shouldIncludeAll(function () { return n3Store.find(); }, | ||
shouldIncludeAll(function () { return store.find(); }, | ||
['s1', 'p1', 'o2'], ['s1', 'p2', 'o2'], ['s2', 'p1', 'o1'])); | ||
}); | ||
describe('when removing an existing triple from a non-default context', function () { | ||
before(function () { n3Store.removeTriple('s1', 'p2', 'o3', 'c4'); }); | ||
describe('when removing an existing triple from a non-default graph', function () { | ||
before(function () { store.removeTriple('s1', 'p2', 'o3', 'c4'); }); | ||
it('should have size 3', function () { n3Store.size.should.eql(3); }); | ||
it('should have size 3', function () { store.size.should.eql(3); }); | ||
itShouldBeEmpty(function () { return n3Store.find(null, null, null, 'c4'); }); | ||
itShouldBeEmpty(function () { return store.find(null, null, null, 'c4'); }); | ||
}); | ||
@@ -370,3 +366,3 @@ | ||
before(function () { | ||
n3Store.removeTriples([ | ||
store.removeTriples([ | ||
{ subject: 's1', predicate: 'p2', object: 'o2'}, | ||
@@ -377,6 +373,6 @@ { subject: 's2', predicate: 'p1', object: 'o1'}, | ||
it('should have size 1', function () { n3Store.size.should.eql(1); }); | ||
it('should have size 1', function () { store.size.should.eql(1); }); | ||
it('should not contain those triples anymore', | ||
shouldIncludeAll(function () { return n3Store.find(); }, | ||
shouldIncludeAll(function () { return store.find(); }, | ||
['s1', 'p1', 'o2'])); | ||
@@ -387,7 +383,7 @@ }); | ||
before(function () { | ||
n3Store.addTriple('a', 'b', 'c'); | ||
n3Store.removeTriple('a', 'b', 'c'); | ||
store.addTriple('a', 'b', 'c'); | ||
store.removeTriple('a', 'b', 'c'); | ||
}); | ||
it('should have an unchanged size', function () { n3Store.size.should.eql(1); }); | ||
it('should have an unchanged size', function () { store.size.should.eql(1); }); | ||
}); | ||
@@ -397,3 +393,3 @@ }); | ||
describe('An N3Store initialized with prefixes', function () { | ||
var n3Store = new N3Store([ | ||
var store = new N3Store([ | ||
{ subject: 'http://foo.org/#s1', predicate: 'http://bar.org/p1', object: 'http://foo.org/#o1' }, | ||
@@ -403,12 +399,9 @@ { subject: 'http://foo.org/#s1', predicate: 'http://bar.org/p2', object: 'http://foo.org/#o1' }, | ||
{ subject: 'http://foo.org/#s3', predicate: 'http://bar.org/p3', object: '"a"^^http://foo.org/#t1' }, | ||
{ subject: 'http://foo.org/#s1', predicate: 'http://bar.org/p1', object: 'http://foo.org/#o1', graph: 'http://graphs.org/#g1' }, | ||
], | ||
{ | ||
'a': 'http://foo.org/#', | ||
'b': 'http://bar.org/', | ||
'ctx': 'n3/contexts#', | ||
}); | ||
{ prefixes: { 'a': 'http://foo.org/#', 'b': 'http://bar.org/', 'g': 'http://graphs.org/#' } }); | ||
describe('should allow to query subjects with prefixes', function () { | ||
it('should return all triples with that subject', | ||
shouldIncludeAll(n3Store.find('a:s1', null, null), | ||
shouldIncludeAll(store.find('a:s1', null, null), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1'], | ||
@@ -420,3 +413,3 @@ ['http://foo.org/#s1', 'http://bar.org/p2', 'http://foo.org/#o1'])); | ||
it('should return all triples with that predicate', | ||
shouldIncludeAll(n3Store.find(null, 'b:p1', null), | ||
shouldIncludeAll(store.find(null, 'b:p1', null), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1'], | ||
@@ -428,3 +421,3 @@ ['http://foo.org/#s2', 'http://bar.org/p1', 'http://foo.org/#o2'])); | ||
it('should return all triples with that object', | ||
shouldIncludeAll(n3Store.find(null, null, 'a:o1'), | ||
shouldIncludeAll(store.find(null, null, 'a:o1'), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1'], | ||
@@ -434,9 +427,6 @@ ['http://foo.org/#s1', 'http://bar.org/p2', 'http://foo.org/#o1'])); | ||
describe('should allow to query contexts with prefixes', function () { | ||
it('should return all triples with that context', | ||
shouldIncludeAll(n3Store.find(null, null, null, 'ctx:default'), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1'], | ||
['http://foo.org/#s1', 'http://bar.org/p2', 'http://foo.org/#o1'], | ||
['http://foo.org/#s2', 'http://bar.org/p1', 'http://foo.org/#o2'], | ||
['http://foo.org/#s3', 'http://bar.org/p3', '"a"^^http://foo.org/#t1'])); | ||
describe('should allow to query graphs with prefixes', function () { | ||
it('should return all triples with that graph', | ||
shouldIncludeAll(store.find(null, null, null, 'http://graphs.org/#g1'), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1', 'http://graphs.org/#g1'])); | ||
}); | ||
@@ -446,14 +436,15 @@ }); | ||
describe('An N3Store with prefixes added later on', function () { | ||
var n3Store = new N3Store([ | ||
var store = new N3Store([ | ||
{ subject: 'http://foo.org/#s1', predicate: 'http://bar.org/p1', object: 'http://foo.org/#o1' }, | ||
{ subject: 'http://foo.org/#s1', predicate: 'http://bar.org/p2', object: 'http://foo.org/#o1' }, | ||
{ subject: 'http://foo.org/#s2', predicate: 'http://bar.org/p1', object: 'http://foo.org/#o2' }, | ||
{ subject: 'http://foo.org/#s1', predicate: 'http://bar.org/p1', object: 'http://foo.org/#o1', graph: 'http://graphs.org/#g1' }, | ||
]); | ||
n3Store.addPrefix('a', 'http://foo.org/#'); | ||
n3Store.addPrefixes({ 'b': 'http://bar.org/', 'ctx': 'n3/contexts#'}); | ||
store.addPrefix('a', 'http://foo.org/#'); | ||
store.addPrefixes({ 'b': 'http://bar.org/', 'g': 'http://graphs.org/#' }); | ||
describe('should allow to query subjects with prefixes', function () { | ||
it('should return all triples with that subject', | ||
shouldIncludeAll(n3Store.find('a:s1', null, null), | ||
shouldIncludeAll(store.find('a:s1', null, null), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1'], | ||
@@ -465,3 +456,3 @@ ['http://foo.org/#s1', 'http://bar.org/p2', 'http://foo.org/#o1'])); | ||
it('should return all triples with that predicate', | ||
shouldIncludeAll(n3Store.find(null, 'b:p1', null), | ||
shouldIncludeAll(store.find(null, 'b:p1', null), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1'], | ||
@@ -473,3 +464,3 @@ ['http://foo.org/#s2', 'http://bar.org/p1', 'http://foo.org/#o2'])); | ||
it('should return all triples with that object', | ||
shouldIncludeAll(n3Store.find(null, null, 'a:o1'), | ||
shouldIncludeAll(store.find(null, null, 'a:o1'), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1'], | ||
@@ -479,8 +470,6 @@ ['http://foo.org/#s1', 'http://bar.org/p2', 'http://foo.org/#o1'])); | ||
describe('should allow to query contexts with prefixes', function () { | ||
it('should return all triples with that context', | ||
shouldIncludeAll(n3Store.find(null, null, null, 'ctx:default'), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1'], | ||
['http://foo.org/#s1', 'http://bar.org/p2', 'http://foo.org/#o1'], | ||
['http://foo.org/#s2', 'http://bar.org/p1', 'http://foo.org/#o2'])); | ||
describe('should allow to query graphs with prefixes', function () { | ||
it('should return all triples with that graph', | ||
shouldIncludeAll(store.find(null, null, null, 'http://graphs.org/#g1'), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1', 'http://graphs.org/#g1'])); | ||
}); | ||
@@ -490,3 +479,3 @@ }); | ||
describe('An N3Store with the http prefix', function () { | ||
var n3Store = new N3Store([ | ||
var store = new N3Store([ | ||
{ subject: 'http://foo.org/#s1', predicate: 'http://bar.org/p1', object: 'http://foo.org/#o1' }, | ||
@@ -496,9 +485,7 @@ { subject: 'http://foo.org/#s1', predicate: 'http://bar.org/p2', object: 'http://foo.org/#o1' }, | ||
], | ||
{ | ||
'http': 'http://www.w3.org/2006/http#' | ||
}); | ||
{ prefixes: { 'http': 'http://www.w3.org/2006/http#' } }); | ||
describe('should allow to query subjects without prefixes', function () { | ||
it('should return all triples with that subject', | ||
shouldIncludeAll(n3Store.find('http://foo.org/#s1', null, null), | ||
shouldIncludeAll(store.find('http://foo.org/#s1', null, null), | ||
['http://foo.org/#s1', 'http://bar.org/p1', 'http://foo.org/#o1'], | ||
@@ -510,8 +497,8 @@ ['http://foo.org/#s1', 'http://bar.org/p2', 'http://foo.org/#o1'])); | ||
describe('An N3Store created without triples but with prefixes', function () { | ||
var n3Store = new N3Store({ 'http': 'http://www.w3.org/2006/http#' }); | ||
n3Store.addTriple('a', 'http://www.w3.org/2006/http#b', 'c'); | ||
var store = new N3Store({ prefixes: { 'http': 'http://www.w3.org/2006/http#' } }); | ||
store.addTriple('a', 'http://www.w3.org/2006/http#b', 'c'); | ||
describe('should allow to query predicates with prefixes', function () { | ||
it('should return all triples with that predicate', | ||
shouldIncludeAll(n3Store.find(null, 'http:b', null), | ||
shouldIncludeAll(store.find(null, 'http:b', null), | ||
['a', 'http://www.w3.org/2006/http#b', 'c'])); | ||
@@ -522,3 +509,3 @@ }); | ||
describe('An N3Store', function () { | ||
var n3Store = new N3Store(); | ||
var store = new N3Store(); | ||
@@ -528,4 +515,4 @@ // Test inspired by http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/. | ||
it('should be able to contain entities with JavaScript object property names', function () { | ||
n3Store.addTriple('toString', 'valueOf', 'toLocaleString', 'hasOwnProperty'); | ||
shouldIncludeAll(n3Store.find(null, null, null, 'hasOwnProperty'), | ||
store.addTriple('toString', 'valueOf', 'toLocaleString', 'hasOwnProperty'); | ||
shouldIncludeAll(store.find(null, null, null, 'hasOwnProperty'), | ||
['toString', 'valueOf', 'toLocaleString', 'hasOwnProperty'])(); | ||
@@ -535,4 +522,4 @@ }); | ||
it('should be able to contain entities named "null"', function () { | ||
n3Store.addTriple('null', 'null', 'null', 'null'); | ||
shouldIncludeAll(n3Store.find(null, null, null, 'null'), ['null', 'null', 'null', 'null'])(); | ||
store.addTriple('null', 'null', 'null', 'null'); | ||
shouldIncludeAll(store.find(null, null, null, 'null'), ['null', 'null', 'null', 'null'])(); | ||
}); | ||
@@ -551,4 +538,3 @@ }); | ||
var items = Array.prototype.slice.call(arguments, 1).map(function (arg) { | ||
return { subject: arg[0], predicate: arg[1], object: arg[2], | ||
context: arg[3] || 'n3/contexts#default' }; | ||
return { subject: arg[0], predicate: arg[1], object: arg[2], graph: arg[3] || '' }; | ||
}); | ||
@@ -555,0 +541,0 @@ return function () { |
@@ -35,4 +35,4 @@ var N3StreamParser = require('../N3').StreamParser; | ||
it('emits "prefix" events', | ||
shouldEmitPrefixes(['@prefix a: <URIa>. a:a a:b a:c. @prefix b: <URIb>.'], | ||
{ a: 'URIa', b: 'URIb' })); | ||
shouldEmitPrefixes(['@prefix a: <IRIa>. a:a a:b a:c. @prefix b: <IRIb>.'], | ||
{ a: 'IRIa', b: 'IRIb' })); | ||
}); | ||
@@ -80,3 +80,3 @@ }); | ||
transform.on('data', function () {}); | ||
transform.on('prefix', function (prefix, uri) { prefixes[prefix] = uri; }); | ||
transform.on('prefix', function (prefix, iri) { prefixes[prefix] = iri; }); | ||
transform.on('error', done); | ||
@@ -83,0 +83,0 @@ transform.on('end', function (error) { |
@@ -54,5 +54,3 @@ var N3StreamWriter = require('../N3').StreamWriter; | ||
it('should use prefixes when possible', | ||
shouldSerialize({ a: 'http://a.org/', | ||
b: 'http://a.org/b#', | ||
c: 'http://a.org/b' }, | ||
shouldSerialize({ prefixes: { a: 'http://a.org/', b: 'http://a.org/b#', c: 'http://a.org/b' } }, | ||
[['http://a.org/bc', 'http://a.org/b#ef', 'http://a.org/bhi'], | ||
@@ -70,8 +68,8 @@ ['http://a.org/bc/de', 'http://a.org/b#e#f', 'http://a.org/b#x/t'], | ||
function shouldSerialize(prefixes, tripleArrays, expectedResult) { | ||
function shouldSerialize(options, tripleArrays, expectedResult) { | ||
if (!expectedResult) | ||
expectedResult = tripleArrays, tripleArrays = prefixes, prefixes = null; | ||
expectedResult = tripleArrays, tripleArrays = options, options = null; | ||
return function (done) { | ||
var inputStream = new ArrayReader(tripleArrays), | ||
transform = new N3StreamWriter(prefixes), | ||
transform = new N3StreamWriter(options), | ||
outputStream = new StringWriter(); | ||
@@ -78,0 +76,0 @@ inputStream.pipe(transform); |
@@ -15,3 +15,3 @@ var N3Util = require('../N3').Util; | ||
N3Util(host).should.equal(host); | ||
host.isUri.should.be.a('function'); | ||
host.isIRI.should.be.a('function'); | ||
host.isLiteral.should.be.a('function'); | ||
@@ -25,3 +25,3 @@ host.isPrefixedName('a:b').should.be.true; | ||
N3Util(Constructor, true).should.equal(Constructor); | ||
Constructor.prototype.isUri.should.be.a('function'); | ||
Constructor.prototype.isIRI.should.be.a('function'); | ||
Constructor.prototype.isLiteral.should.be.a('function'); | ||
@@ -34,21 +34,21 @@ | ||
describe('isUri', function () { | ||
it('matches a URI', function () { | ||
N3Util.isUri('http://example.org/').should.be.true; | ||
describe('isIRI', function () { | ||
it('matches an IRI', function () { | ||
N3Util.isIRI('http://example.org/').should.be.true; | ||
}); | ||
it('does not match a literal', function () { | ||
N3Util.isUri('"http://example.org/"').should.be.false; | ||
N3Util.isIRI('"http://example.org/"').should.be.false; | ||
}); | ||
it('does not match a blank node', function () { | ||
N3Util.isUri('_:x').should.be.false; | ||
N3Util.isIRI('_:x').should.be.false; | ||
}); | ||
it('does not match null', function () { | ||
expect(N3Util.isUri(null)).to.be.null; | ||
expect(N3Util.isIRI(null)).to.be.null; | ||
}); | ||
it('does not match undefined', function () { | ||
expect(N3Util.isUri(undefined)).to.be.undefined; | ||
expect(N3Util.isIRI(undefined)).to.be.undefined; | ||
}); | ||
@@ -82,3 +82,3 @@ }); | ||
it('does not match a URI', function () { | ||
it('does not match an IRI', function () { | ||
N3Util.isLiteral('http://example.org/').should.be.false; | ||
@@ -105,3 +105,3 @@ }); | ||
it('does not match a URI', function () { | ||
it('does not match an IRI', function () { | ||
N3Util.isBlank('http://example.org/').should.be.false; | ||
@@ -246,3 +246,3 @@ }); | ||
it('does not match a URI', function () { | ||
it('does not match an IRI', function () { | ||
N3Util.isPrefixedName('http://example.org/').should.be.false; | ||
@@ -249,0 +249,0 @@ }); |
@@ -94,2 +94,6 @@ var N3Writer = require('../N3').Writer; | ||
it('should serialize a literal containing special unicode characters', | ||
shouldSerialize([['a', 'b', '"c\u0000\u0001"']], | ||
'<a> <b> "c\\u0000\\u0001".\n')); | ||
it('should serialize blank nodes', | ||
@@ -100,9 +104,13 @@ shouldSerialize([['_:a', 'b', '_:c']], | ||
it('should not serialize a literal in the subject', | ||
shouldNotSerialize([['"a"', 'b', '"c']], | ||
shouldNotSerialize([['"a"', 'b', '"c"']], | ||
'A literal as subject is not allowed: "a"')); | ||
it('should not serialize a literal in the predicate', | ||
shouldNotSerialize([['a', '"b"', '"c']], | ||
shouldNotSerialize([['a', '"b"', '"c"']], | ||
'A literal as predicate is not allowed: "b"')); | ||
it('should not serialize an invalid object literal', | ||
shouldNotSerialize([['a', 'b', '"c']], | ||
'Invalid literal: "c')); | ||
it('should not leave leading whitespace if the prefix set is empty', | ||
@@ -114,5 +122,3 @@ shouldSerialize({}, | ||
it('should serialize valid prefixes', | ||
shouldSerialize({ a: 'http://a.org/', | ||
b: 'http://a.org/b#', | ||
c: 'http://a.org/b' }, | ||
shouldSerialize({ prefixes: { a: 'http://a.org/', b: 'http://a.org/b#', c: 'http://a.org/b' } }, | ||
[], | ||
@@ -123,5 +129,3 @@ '@prefix a: <http://a.org/>.\n' + | ||
it('should use prefixes when possible', | ||
shouldSerialize({ a: 'http://a.org/', | ||
b: 'http://a.org/b#', | ||
c: 'http://a.org/b' }, | ||
shouldSerialize({ prefixes: { a: 'http://a.org/', b: 'http://a.org/b#', c: 'http://a.org/b' } }, | ||
[['http://a.org/bc', 'http://a.org/b#ef', 'http://a.org/bhi'], | ||
@@ -158,2 +162,34 @@ ['http://a.org/bc/de', 'http://a.org/b#e#f', 'http://a.org/b#x/t'], | ||
it('should serialize a graph with 1 triple', | ||
shouldSerialize([['abc', 'def', 'ghi', 'xyz']], | ||
'<xyz> {\n' + | ||
'<abc> <def> <ghi>\n' + | ||
'}\n')); | ||
it('should serialize a graph with 3 triples', | ||
shouldSerialize([['abc', 'def', 'ghi', 'xyz'], | ||
['jkl', 'mno', 'pqr', 'xyz'], | ||
['stu', 'vwx', 'yz', 'xyz']], | ||
'<xyz> {\n' + | ||
'<abc> <def> <ghi>.\n' + | ||
'<jkl> <mno> <pqr>.\n' + | ||
'<stu> <vwx> <yz>\n' + | ||
'}\n')); | ||
it('should serialize three graphs', | ||
shouldSerialize([['abc', 'def', 'ghi', 'xyz'], | ||
['jkl', 'mno', 'pqr', ''], | ||
['stu', 'vwx', 'yz', 'abc']], | ||
'<xyz> {\n<abc> <def> <ghi>\n}\n' + | ||
'<jkl> <mno> <pqr>.\n' + | ||
'<abc> {\n<stu> <vwx> <yz>\n}\n')); | ||
it('should output 8-bit unicode characters as escape sequences', | ||
shouldSerialize([['\ud835\udc00', '\ud835\udc00', '"\ud835\udc00"^^\ud835\udc00', '\ud835\udc00']], | ||
'<\\U0001d400> {\n<\\U0001d400> <\\U0001d400> "\\U0001d400"^^<\\U0001d400>\n}\n')); | ||
it('should not use escape sequences in blank nodes', | ||
shouldSerialize([['_:\ud835\udc00', '_:\ud835\udc00', '_:\ud835\udc00', '_:\ud835\udc00']], | ||
'_:\ud835\udc00 {\n_:\ud835\udc00 _:\ud835\udc00 _:\ud835\udc00\n}\n')); | ||
it('calls the done callback when ending the outputstream errors', function (done) { | ||
@@ -177,3 +213,3 @@ var writer = new N3Writer({ | ||
it('respects the prefixes argument when no stream argument is given', function (done) { | ||
var writer = new N3Writer({ a: 'b#' }); | ||
var writer = new N3Writer({ prefixes: { a: 'b#' } }); | ||
writer.addTriple({ subject: 'b#a', predicate: 'b#b', object: 'b#c' }); | ||
@@ -202,2 +238,15 @@ writer.end(function (error, output) { | ||
it('serializes triples of a graph with a prefix declaration in between', function (done) { | ||
var writer = new N3Writer(); | ||
writer.addPrefix('a', 'b#'); | ||
writer.addTriple({ subject: 'b#a', predicate: 'b#b', object: 'b#c', graph: 'b#g' }); | ||
writer.addPrefix('d', 'e#'); | ||
writer.addTriple({ subject: 'b#a', predicate: 'b#b', object: 'b#d', graph: 'b#g' }); | ||
writer.end(function (error, output) { | ||
output.should.equal('@prefix a: <b#>.\n\na:g {\na:a a:b a:c\n}\n' + | ||
'@prefix d: <e#>.\n\na:g {\na:a a:b a:d\n}\n'); | ||
done(error); | ||
}); | ||
}); | ||
it('should accept triples with separated components', function (done) { | ||
@@ -213,2 +262,12 @@ var writer = N3Writer(); | ||
it('should accept quads with separated components', function (done) { | ||
var writer = N3Writer(); | ||
writer.addTriple('a', 'b', 'c', 'g'); | ||
writer.addTriple('a', 'b', 'd', 'g'); | ||
writer.end(function (error, output) { | ||
output.should.equal('<g> {\n<a> <b> <c>, <d>\n}\n'); | ||
done(error); | ||
}); | ||
}); | ||
it('should accept triples in bulk', function (done) { | ||
@@ -234,2 +293,40 @@ var writer = N3Writer(); | ||
}); | ||
it('should write simple triples in N-Triples mode', function (done) { | ||
var writer = N3Writer({ format: 'N-Triples' }); | ||
writer.addTriple('a', 'b', 'c'); | ||
writer.addTriple('a', 'b', 'd'); | ||
writer.end(function (error, output) { | ||
output.should.equal('<a> <b> <c>.\n<a> <b> <d>.\n'); | ||
done(error); | ||
}); | ||
}); | ||
it('should not write an invalid literal in N-Triples mode', function (done) { | ||
var writer = N3Writer({ format: 'N-Triples' }); | ||
writer.addTriple('a', 'b', '"c', function (error) { | ||
error.should.be.an.instanceof(Error); | ||
error.should.have.property('message', 'Invalid literal: "c'); | ||
done(); | ||
}); | ||
}); | ||
it('should write simple quads in N-Quads mode', function (done) { | ||
var writer = N3Writer({ format: 'N-Quads' }); | ||
writer.addTriple('a', 'b', 'c'); | ||
writer.addTriple('a', 'b', 'd', 'g'); | ||
writer.end(function (error, output) { | ||
output.should.equal('<a> <b> <c>.\n<a> <b> <d> <g>.\n'); | ||
done(error); | ||
}); | ||
}); | ||
it('should not write an invalid literal in N-Quads mode', function (done) { | ||
var writer = N3Writer({ format: 'N-Triples' }); | ||
writer.addTriple('a', 'b', '"c', function (error) { | ||
error.should.be.an.instanceof(Error); | ||
error.should.have.property('message', 'Invalid literal: "c'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -247,3 +344,3 @@ }); | ||
if (item) | ||
writer.addTriple({ subject: item[0], predicate: item[1], object: item[2] }, next); | ||
writer.addTriple({ subject: item[0], predicate: item[1], object: item[2], graph: item[3] }, next); | ||
else | ||
@@ -268,3 +365,3 @@ writer.end(function (error) { | ||
item = tripleArrays.shift(); | ||
writer.addTriple({ subject: item[0], predicate: item[1], object: item[2] }, | ||
writer.addTriple({ subject: item[0], predicate: item[1], object: item[2], graph: item[3] }, | ||
function (error) { | ||
@@ -271,0 +368,0 @@ if (error) { |
Sorry, the diff of this file is not supported yet
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
229038
31
4473
361
1