rdf
Version:
RDF datatype integration, RDF Interfaces API, and utility functions
437 lines (429 loc) • 17.4 kB
JavaScript
;
var parsers = exports;
var IRI = require('iri').IRI;
var env = require('./environment.js').environment;
function rdfns(v){return 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'+v;}
function xsdns(v){return 'http://www.w3.org/2001/XMLSchema#'+v;}
parsers.u8 = new RegExp("\\\\U([0-9A-Fa-f]{8})", "g");
parsers.u4 = new RegExp("\\\\u([0-9A-Fa-f]{4})", "g");
parsers.hexToChar = function hexToChar(hex) {
var result = "";
var n = parseInt(hex, 16);
if(n <= 65535) {
result += String.fromCharCode(n);
} else if(n <= 1114111) {
n -= 65536;
result += String.fromCharCode(55296 + (n >> 10), 56320 + (n & 1023));
} else {
throw new Error("code point isn't known: " + n);
}
return result;
};
parsers.decodeString = function decodeString(str) {
str = str.replace(parsers.u8, function(matchstr, parens) { return parsers.hexToChar(parens); });
str = str.replace(parsers.u4, function(matchstr, parens) { return parsers.hexToChar(parens); });
str = str.replace(new RegExp("\\\\t", "g"), "\t");
str = str.replace(new RegExp("\\\\b", "g"), "\b");
str = str.replace(new RegExp("\\\\n", "g"), "\n");
str = str.replace(new RegExp("\\\\r", "g"), "\r");
str = str.replace(new RegExp("\\\\f", "g"), "\f");
str = str.replace(new RegExp('\\\\"', "g"), '"');
str = str.replace(new RegExp("\\\\\\\\", "g"), "\\");
return str;
};
parsers.decodePrefixedName = function(str){
var decoded = '';
for(var i=0; i<str.length; i++){
if(str[i]=='\\'){
decoded += str[++i];
}else{
decoded += str[i];
}
}
return decoded;
};
parsers.decodeIRIREF = function decodeIRIREF(str) {
str = str.replace(parsers.u8, function(matchstr, parens) { return parsers.hexToChar(parens); });
str = str.replace(parsers.u4, function(matchstr, parens) { return parsers.hexToChar(parens); });
return str;
};
/**
* Turtle implements DataParser
* doc param of parse() and process() must be a string
*/
function Turtle(environment) {
if(!environment) environment = env.createProfile();
this.environment = environment;
this.base = new IRI('');
this.bnHash = {};
this.filter = null;
this.processor = null;
this.quick = null;
this.graph = null;
}
parsers.Turtle = Turtle;
Turtle.parse = function parse(document, base, event){
var parser = new Turtle();
if(typeof event=='function'){
parser.quick = true;
parser.processor = event;
}
parser.parse(document, null, base);
if(typeof event=='function'){
event(null, null);
}
return parser;
};
Turtle.isWhitespace = new RegExp("^[ \t\r\n#]+", "");
Turtle.initialWhitespace = new RegExp("^[ \t\r\n]+", "");
Turtle.initialComment = new RegExp("^#[^\r\n]*", "");
Turtle.simpleToken = new RegExp("^[^ \t\r\n](\\.*[^ \t\r\n,;\\.])*", "");
Turtle.simpleObjectToken = /^(\\[_\~\.\-\!\$&'()*+,;=\/?#@%]|%[0-9A-Fa-f]{2}|[^ \t\r\n;,\[\]()])+/;
Turtle.tokenInteger = new RegExp("^(-|\\+)?[0-9]+", "");
Turtle.tokenDouble = new RegExp("^(-|\\+)?(([0-9]+\\.[0-9]*[eE]{1}(-|\\+)?[0-9]+)|(\\.[0-9]+[eE]{1}(-|\\+)?[0-9]+)|([0-9]+[eE]{1}(-|\\+)?[0-9]+))", "");
Turtle.tokenDecimal = new RegExp("^(-|\\+)?[0-9]*\\.[0-9]+?", "");
Turtle.PrefixedName = /^(([A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD](([\-.0-9A-Z_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0300-\u036F\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD])*[\-0-9A-Z_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0300-\u036F\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD])?)?:([A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD]|[0-9:]|%[0-9A-Fa-f][0-9A-Fa-f]|\\[!#$%&'()*+,\-.\/;=?@_~])((([\-0-9A-Z_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0300-\u036F\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD]|[.0-9:]|%[0-9A-Fa-f][0-9A-Fa-f]|\\[!#$%&'()*+,\-.\/;=?@_~]))*([\-0-9A-Z_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0300-\u036F\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD]|[0-9:]|%[0-9A-Fa-f][0-9A-Fa-f]|\\[!#$%&'()*+,\-.\/;=?@_~]))?|([A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD](([\-.0-9A-Z_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0300-\u036F\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD])*[\-0-9A-Z_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0300-\u036F\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD])?)?:)/;
Turtle.prototype.parse = function parse(doc, callback, base, filter, graph) {
this.graph = graph==null ? env.createGraph() : graph;
if(base) this.base = new IRI(base.toString());
this.filter = filter;
this.parseStatements(new String(doc));
if((typeof callback)=="function") callback(this.graph);
return true;
};
Turtle.prototype.t = function t(){ return {o:null}; };
Turtle.prototype.expect = function(s, t) {
if(typeof t.test=='function'){
if(t.test(s)) return;
}else if(typeof t=='string'){
if(s.substring(0, t.length) == t) return;
}
throw new Error("Expected token: " + t + " at " + JSON.stringify(s.substring(0, 50)));
};
Turtle.prototype.parseStatements = function(s) {
s = s.toString();
while(s.length > 0) {
s = this.skipWS(s);
if(s.length == 0) return true;
s = (s.charAt(0)=="@" || s.substring(0,4).toUpperCase()=='BASE' || s.substring(0,6).toUpperCase()=='PREFIX') ? this.consumeDirective(s) : this.consumeStatement(s);
s = this.skipWS(s);
}
return true;
};
Turtle.prototype.createTriple = function createTriple(s, p, o){
return env.createTriple(s, p, o);
};
Turtle.prototype.createNamedNode = function createNamedNode(v){
return env.createNamedNode(v);
};
Turtle.prototype.createBlankNode = function createBlankNode(){
return env.createBlankNode();
};
Turtle.prototype.createLiteral = function createLiteral(v, l, t){
return env.createLiteral(v, l, t);
};
Turtle.prototype.add = function(t) {
var $use = true;
if(this.filter != null) $use = this.filter(t, null, null);
if(!$use) return;
this.processor ? this.processor(null, t) : this.graph.add(t);
};
Turtle.prototype.consumeBlankNode = function(s, t) {
t.o = this.createBlankNode();
s = this.skipWS(s.slice(1));
if(s.charAt(0) == "]") return s.slice(1);
s = this.skipWS(this.consumePredicateObjectList(s, t));
this.expect(s, "]");
return this.skipWS(s.slice(1));
};
Turtle.prototype.consumeCollection = function(s, subject) {
subject.o = this.createBlankNode();
var listject = this.t();
listject.o = subject.o;
s = this.skipWS(s.slice(1));
var cont = s.charAt(0) != ")";
if(!cont) { subject.o = this.createNamedNode(rdfns("nil")); }
while(cont) {
var o = this.t();
switch(s.charAt(0)) {
case "[": s = this.consumeBlankNode(s, o); break;
case "_": s = this.consumeKnownBlankNode(s, o); break;
case "(": s = this.consumeCollection(s, o); break;
case "<": s = this.consumeURI(s, o); break;
case '"': case "'": s = this.consumeLiteral(s, o); break;
case '+': case '-': case '.':
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
var token;
if(token = Turtle.tokenDouble.exec(s)){
token = token[0];
o.o = this.createLiteral(token, null, xsdns("double"));
} else if(token = Turtle.tokenDecimal.exec(s)){
token = token[0];
o.o = this.createLiteral(token, null, xsdns("decimal"));
} else if(token = Turtle.tokenInteger.exec(s)){
token = token[0];
o.o = this.createLiteral(token, null, xsdns("integer"));
} else {
throw new Error("Expected NumericLiteral");
}
s = s.slice(token.length);
break;
default:
var token = s.match(Turtle.simpleObjectToken).shift();
if(token.charAt(token.length - 1) == ")") {
token = token.substring(0, token.length - 1);
}
if(token==="false" || token==="true"){
o.o = this.createLiteral(token, null, xsdns("boolean"));
}else if(token.indexOf(":") >= 0) {
o.o = this.createNamedNode(this.environment.resolve(token));
}
s = s.slice(token.length);
break;
}
this.add(this.createTriple(listject.o, this.createNamedNode(rdfns("first")), o.o));
s = this.skipWS(s);
cont = s.charAt(0) != ")";
if(cont) {
this.add(this.createTriple(listject.o, this.createNamedNode(rdfns("rest")), listject.o = this.createBlankNode()));
} else {
this.add(this.createTriple(listject.o, this.createNamedNode(rdfns("rest")), this.createNamedNode(rdfns("nil"))));
}
}
return this.skipWS(s.slice(1));
};
Turtle.prototype.consumeDirective = function(s) {
var p = 0;
if(s.substring(1, 7) == "prefix") {
s = this.skipWS(s.slice(7));
p = s.indexOf(":");
var prefix = s.substring(0, p);
s = this.skipWS(s.slice(++p));
this.expect(s, "<");
this.environment.setPrefix(prefix, this.base.resolveReference(parsers.decodeIRIREF(s.substring(1, p = s.indexOf(">")))).toString());
s = this.skipWS(s.slice(++p));
this.expect(s, ".");
s = s.slice(1);
} else if(s.substring(0, 6).toUpperCase() == "PREFIX") {
// SPARQL-style
s = this.skipWS(s.slice(7));
p = s.indexOf(":");
var prefix = s.substring(0, p);
s = this.skipWS(s.slice(++p));
this.expect(s, "<");
this.environment.setPrefix(prefix, this.base.resolveReference(parsers.decodeIRIREF(s.substring(1, p = s.indexOf(">")))).toString());
s = this.skipWS(s.slice(++p));
} else if(s.substring(1, 5) == "base") {
s = this.skipWS(s.slice(5));
this.expect(s, "<");
this.base = this.base.resolveReference(parsers.decodeIRIREF(s.substring(1, p = s.indexOf(">"))));
s = this.skipWS(s.slice(++p));
this.expect(s, ".");
s = s.slice(1);
} else if(s.substring(0, 4).toUpperCase() == "BASE") {
// SPARQL-style
s = this.skipWS(s.slice(5));
this.expect(s, "<");
this.base = this.base.resolveReference(parsers.decodeIRIREF(s.substring(1, p = s.indexOf(">"))));
s = this.skipWS(s.slice(++p));
} else {
throw new Error("Unknown directive: " + s.substring(0, 50));
}
return s;
};
Turtle.prototype.consumeKnownBlankNode = function(s, t) {
this.expect(s, "_:");
var bname = s.slice(2).match(Turtle.simpleToken).shift();
t.o = this.getBlankNode(bname);
return s.slice(bname.length + 2);
};
Turtle.prototype.consumeLiteral = function(s, o) {
var char = s[0];
var value = "";
var end = 0;
var longchar = char+char+char;
if(s.substring(0, 3) == longchar) {
for(end=3; end<s.length; end++){
if(s[end]=='\\') end++;
else if(s[end]==char && s[end+1]==char && s[end+2]==char) break;
}
value = s.substring(3, end);
s = s.slice(value.length + 6);
} else {
for(end=1; end<s.length; end++){
if(s[end]=='\\') end++;
else if(s[end]==char) break;
}
value = s.substring(1, end);
s = s.slice(value.length + 2);
}
value = parsers.decodeString(value);
switch(s.charAt(0)) {
case "@":
var langtag = s.match(Turtle.simpleObjectToken).shift();
o.o = this.createLiteral(value, langtag.slice(1), null);
s = s.slice(langtag.length);
break;
case "^":
this.expect(s, "^^");
s = s.substring(2);
var iri_val;
if(s.charAt(0) == "<"){
var iri_end = s.indexOf(">");
if(iri_end<0) throw new Error('Could not find terminating ">"');
var iri_esc = s.substring(1, iri_end);
iri_val = this.createNamedNode(this.base.resolveReference(parsers.decodeIRIREF(iri_esc)).toString());
s = this.skipWS(s.substring(iri_end+1));
}else{
var prefixedName = Turtle.PrefixedName.exec(s);
if(!prefixedName) throw new Error('Expected PrefixedName');
prefixedName = prefixedName[0];
iri_val = this.environment.resolve(parsers.decodePrefixedName(prefixedName));
if(!iri_val) throw new Error('Could not resolve PrefixedName '+JSON.stringify(parsers.decodePrefixedName(prefixedName)));
s = this.skipWS(s.slice(prefixedName.length));
}
o.o = this.createLiteral(value, null, iri_val);
break;
default:
o.o = this.createLiteral(value, null, null);
break;
}
return s;
};
Turtle.prototype.consumeObjectList = function(s, subject, property) {
var cont = true;
while(cont) {
var o = this.t();
switch(s.charAt(0)) {
case "[": s = this.consumeBlankNode(s, o); break;
case "_": s = this.consumeKnownBlankNode(s, o); break;
case "(": s = this.consumeCollection(s, o); break;
case "<": s = this.consumeURI(s, o); break;
case '"': case "'": s = this.consumeLiteral(s, o); break;
case '+': case '-': case '.':
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
var token;
if(token = Turtle.tokenDouble.exec(s)){
token = token[0];
o.o = this.createLiteral(token, null, xsdns("double"));
} else if(token = Turtle.tokenDecimal.exec(s)){
token = token[0];
o.o = this.createLiteral(token, null, xsdns("decimal"));
} else if(token = Turtle.tokenInteger.exec(s)){
token = token[0];
o.o = this.createLiteral(token, null, xsdns("integer"));
} else {
throw new Error("Expected NumericLiteral");
}
s = s.slice(token.length);
break;
default:
var token = s.match(Turtle.simpleObjectToken);
var prefixedName;
token = token&&token[0] || "";
if(token.charAt(token.length - 1) == ".") {
token = token.substring(0, token.length - 1);
}
if(token==="true" || token==="false"){
o.o = this.createLiteral(token, null, xsdns("boolean"));
s = s.slice(token.length);
}else if(prefixedName=Turtle.PrefixedName.exec(token)) {
var prefixedName = prefixedName[0];
var iri = this.environment.resolve(parsers.decodePrefixedName(prefixedName));
if(!iri) throw new Error('Could not resolve PrefixedName '+JSON.stringify(parsers.decodePrefixedName(prefixedName)));
o.o = this.createNamedNode(iri);
if(!o.o) throw new Error('Prefix not defined for '+token);
s = s.slice(prefixedName.length);
} else {
throw new Error("Unrecognized token in ObjectList: " + token);
}
break;
}
s = this.skipWS(s);
this.add(this.createTriple(subject.o, property, o.o));
cont = s.charAt(0)==",";
if(cont) { s = this.skipWS(s.slice(1)); }
}
return s;
};
Turtle.prototype.consumePredicateObjectList = function(s, subject) {
var cont = true;
while(cont) {
var predicate = s.match(Turtle.PrefixedName);
if(predicate){
predicate = predicate.shift();
var iri = this.environment.resolve(parsers.decodePrefixedName(predicate));
if(!iri) throw new Error('Could not resolve PrefixedName '+JSON.stringify(parsers.decodePrefixedName(predicate)));
property = this.createNamedNode(iri);
s = this.skipWS(s.slice(predicate.length));
s = this.consumeObjectList(s, subject, property);
continue;
}
switch(s.charAt(0)) {
case "a":
var property = this.createNamedNode(rdfns("type"));
s = this.skipWS(s.substring(1));
break;
case "<":
var iri_end = s.indexOf(">");
var iri = s.substring(1, iri_end);
property = this.createNamedNode(this.base.resolveReference(parsers.decodeIRIREF(iri)).toString());
s = this.skipWS(s.substring(iri_end+1));
break;
case "]": return s;
case ".": return s;
case ";":
// empty predicate, skip
s = this.skipWS(s.substring(1));
continue;
default:
throw new Error('Expected PrefixedName');
}
s = this.consumeObjectList(s, subject, property);
cont = s.charAt(0)==";";
if(cont) { s = this.skipWS(s.slice(1)); }
}
return s;
};
Turtle.prototype.consumePrefixedName = function(s, t) {
var name = s.match(Turtle.PrefixedName).shift();
var iri = this.environment.resolve(parsers.decodePrefixedName(name));
if(!iri) throw new Error('Could not resolve '+JSON.stringify(parsers.decodePrefixedName(name)));
t.o = this.createNamedNode(iri);
return s.slice(name.length);
};
Turtle.prototype.consumeStatement = function(s) {
var t = this.t();
switch(s.charAt(0)) {
case "[":
s = this.consumeBlankNode(s, t);
if(s.charAt(0) == ".") return s.slice(1);
break;
case "_": s = this.consumeKnownBlankNode(s, t); break;
case "(": s = this.consumeCollection(s, t); break;
case "<": s = this.consumeURI(s, t); break;
default: s = this.consumePrefixedName(s, t); break;
}
s = this.consumePredicateObjectList(this.skipWS(s), t);
this.expect(s, ".");
return s.slice(1);
};
Turtle.prototype.consumeURI = function(s, t) {
this.expect(s, "<");
var p = 0;
t.o = this.createNamedNode(this.base.resolveReference(parsers.decodeIRIREF(s.substring(1, p=s.indexOf(">")))).toString());
return s.slice(++p);
};
Turtle.prototype.getBlankNode = function(id) {
if(this.bnHash[id]) return this.bnHash[id];
return this.bnHash[id]=this.createBlankNode();
};
Turtle.prototype.skipWS = function(s) {
while(Turtle.isWhitespace.test(s.charAt(0))) {
s = s.replace(Turtle.initialWhitespace, "");
if(s.charAt(0) == "#") {
s = s.replace(Turtle.initialComment, "");
}
}
return s;
};