Skip to content

Instantly share code, notes, and snippets.

@snj14
Created November 22, 2008 15:21
Show Gist options
  • Save snj14/27851 to your computer and use it in GitHub Desktop.
Save snj14/27851 to your computer and use it in GitHub Desktop.
RDF Query library for firefox extension
// this is library for RDF
// usage:
//
// var rdf = RDF("chrome://hoge/content/test.rdf"); // note: this is synchronous. not asynchronous.
//
// rdf.getAsXML() // => "<xml>....</xml>"
//
// rdf.query()
// .prefix({
// rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
// , rdfs: "http://www.w3.org/2000/01/rdf-schema#"
// , ex: "http://example.org/term#"
// })
// .where('<http://example.org/> ex:x ?x')
// .where('?x ex:foo ?foo')
// .where('?x ex:bar ?bar')
// .where('?x ?baz "baz"')
// .select('?foo ?bar ?baz');
// res // => [[foo1, foo2, foo3], [bar1, bar2], [baz1]]
//
// rdf.query()
// .where('<http://example.org/> ex:x ?x')
// .add('?x ex:foo "foooooo!"')
// .add("?x ex:foo 'Foooooo!'")
// .add("?x ex:foo \"foofoofoo\"")
//
// // by using XHR.js
// XHR({
// url:'http://example.org/example.rdf'
// onload:function(response){
// var rdf = RDF(response.responseXML); // note: responseXML is document object
// rdf.query()
// .prefix('')
// .where('')
// .where('')
// .select('');
// }
// });
// todo:
//
// delete triple query
//
function RDF(aValue){ // url(string) or xml or document
if(typeof Cc == 'undefined') const Cc = Components.classes;
if(typeof Ci == 'undefined') const Ci = Components.interfaces;
var RDFService = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
// [aSource] --[aProperty]--> [aTarget] // mozilla
// [aSubject] --[aPredicate]--> [aObject] // this library
function RDFWrapper(){return (this instanceof RDFWrapper) ? this : new RDFWrapper()};
RDFWrapper.FromURL = function(aURL){
var ds = RDFService.GetDataSourceBlocking(aURL);
[Ci.nsIRDFDataSource, Ci.nsIRDFRemoteDataSource, Ci.nsIRDFXMLSink].forEach(function(ifc){
if(ds instanceof ifc);
});
ds.Refresh(false); // to enable dataSource.Assert() but why???
return new RDFWrapper().init({dataSource:ds});
}
RDFWrapper.FromDocument = function(aXMLDocument){
var aRDFString = (((typeof aXMLDocument) == 'xml') ? ("<?xml version=\"1.0\"?>\n" + aXMLDocument.toString()) :
(new XMLSerializer()).serializeToString(aXMLDocument)
);
var rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].createInstance(Ci.nsIRDFXMLParser);
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
var aBaseURI = ios.newURI(((typeof aXMLDocument == 'xml') ?
"http://www.w3.org/1999/02/22-rdf-syntax-ns#" : // is this ok?
aXMLDocument.baseURI),
null, null);
var aDataSource = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].createInstance(Ci.nsIRDFDataSource);
var res = rdfParser.parseString(aDataSource, aBaseURI, aRDFString);
[Components.interfaces.nsIRDFDataSource,
Components.interfaces.nsIRDFRemoteDataSource,
Components.interfaces.nsIRDFXMLSink].forEach(function(ifc){
if(aDataSource instanceof ifc);
});
return new RDFWrapper().init({dataSource:aDataSource});
}
RDFWrapper.prototype = {
init: function(opts){
this.dataSource = opts.dataSource;
return this;
}
, query: function(){return RDFQuery().init(this, this.dataSource)}
, filterEnumerator: function(enumerator, fn){
var arr = [];
while(enumerator.hasMoreElements()){
var item = enumerator.getNext();
if(item) [Ci.nsIRDFResource, Ci.nsIRDFLiteral].forEach(function(ifc){if((item instanceof ifc));});
if(item && (item = fn(item)) && item && item.length){
if(item[0] instanceof Ci.nsIRDFResource){
arr.push(item);
}else{// fn return Array item when call filterEnumerator recursively
item.forEach(function(i){
arr.push(i)
})
}
}
}
return arr;
}
// [] -> AnonymousResource
// URI -> Resource
// Number -> IntLiteral
// other -> Literal
// null -> same as unspecified
, getResource: function(aValue){
if((aValue instanceof Array) && (aValue.length == 0)) return null;
return (((aValue instanceof Ci.nsIRDFResource) && aValue) ||
((typeof aValue == 'number') && RDFService.GetIntLiteral(aValue)) ||
((typeof aValue == 'string') && aValue.match(/[a-zA-Z]+:(\/\/)?.+/) && RDFService.GetResource(aValue)) ||
((typeof aValue == 'string') && RDFService.GetLiteral(aValue)) ||
(((aValue == null) || (typeof aValue == 'undefined')) && undefined)
)
}
, getTriples: function(object){// aSubject, aPredicate, aObject
var self = this;
var aSubject, aPredicate, aObject; // they are Resource
[aSubject, aPredicate, aObject] = (Array.slice(arguments)).map(function(e){return self.getResource(e)});
// if aObject === null is true, aObject is specified anonymous resource([]) but it may not exist
var sa = (aSubject === null); // aSubject is Anonymous resource
var pa = (aPredicate === null);
var oa = (aObject === null);
var triples = ((aSubject && aPredicate) ? this.getTriplesFromSP(aSubject, aPredicate, oa) :
(aPredicate && aObject) ? this.getTriplesFromPO(sa, aPredicate, aObject) :
(aSubject && aObject) ? this.getTriplesFromSO(aSubject, pa, aObject) :
aSubject ? this.getTriplesFromS(aSubject, pa, oa) :
aObject ? this.getTriplesFromO(sa, pa, aObject) :
aPredicate ? this.getTriplesFromP(sa, aPredicate, oa) :
(sa || pa || oa) ? this.getTriplesFromAnonymous(sa, pa, oa) :
[])
return triples
}
, getTriplesFromSP: function(aSubject, aPredicate, aObjectIsUnknownAnonymous){
var self = this;
return self.filterEnumerator(self.getObjects(aSubject, aPredicate), function(aObject){
if(aObjectIsUnknownAnonymous && !RDFService.IsAnonymousResource(aObject)) return [];
return [aSubject, aPredicate, aObject]
});
}
, getTriplesFromPO: function(aSubjectIsUnknownAnonymous, aPredicate, aObject){
var self = this;
return self.filterEnumerator(self.getSubjects(aPredicate, aObject), function(aSubject){
if(aSubjectIsUnknownAnonymous && !RDFService.IsAnonymousResource(aSubject)) return [];
return [aSubject, aPredicate, aObject]
})
}
, getTriplesFromSO: function(aSubject, aPredicateIsUnknownAnonymous, aObject){
var self = this;
return self.filterEnumerator(self.getPredicatesFromSubject(aSubject), function(aPredicate){
if(aPredicateIsUnknownAnonymous && !RDFService.IsAnonymousResource(aPredicate)) return [];
return self.filterEnumerator(self.getObjects(aSubject, aPredicate), function(aFoundObject){
if(aFoundObject.Value != aObject.Value) return [];
return [aSubject, aPredicate ,aObject]
})
})
}
, getTriplesFromS: function(aSubject, aPredicateIsUnknownAnonymous, aObjectIsUnknownAnonymous){
var self = this;
return self.filterEnumerator(self.getPredicatesFromSubject(aSubject), function(aPredicate){
if(aPredicateIsUnknownAnonymous && !RDFService.IsAnonymousResource(aPredicate)) return [];
return self.filterEnumerator(self.getObjects(aSubject, aPredicate), function(aObject){
if(aObjectIsUnknownAnonymous && !RDFService.IsAnonymousResource(aObject)) return [];
return [aSubject, aPredicate, aObject]
})
})
}
, getTriplesFromO: function(aSubjectIsUnknownAnonymous, aObject, aPredicateIsUnknownAnonymous){
var self = this;
return self.filterEnumerator(self.getPredicatesFromObject(aObject), function(aPredicate){
if(aPredicateIsUnknownAnonymous && !RDFService.IsAnonymousResource(aPredicate)) return [];
return self.filterEnumerator(self.getSubjects(aPredicate, aObject), function(aSubject){
if(aSubjectIsUnknownAnonymous && !RDFService.IsAnonymousResource(aSubject)) return [];
return [aSubject, aPredicate, aObject]
})
})
}
, getTriplesFromP: function(aSubjectIsUnknownAnonymous, aPredicate, aObjectIsUnknownAnonymous){
var self = this;
return self.filterEnumerator(self.getAllResources(), function(aSubject){
if(aSubjectIsUnknownAnonymous && !RDFService.IsAnonymousResource(aSubject)) return [];
return self.filterEnumerator(self.getPredicatesFromSubject(aSubject), function(aFoundPredicate){
if(aFoundPredicate.Value != aPredicate.Value) return [];
return self.filterEnumerator(self.getObjects(aSubject, aPredicate), function(aObject){
if(aObjectIsUnknownAnonymous && !RDFService.IsAnonymousResource(aObject)) return [];
return [aSubject, aFoundPredicate, aObject]
})
})
})
}
, getTriplesFromAnonymous: function(aSubjectIsUnknownAnonymous, aPredicateIsUnknownAnonymous, aObjectIsUnknownAnonymous){
var self = this;
return self.filterEnumerator(self.getAllResources(), function(aSubject){
if(aSubjectIsUnknownAnonymous && !RDFService.IsAnonymousResource(aSubject)) return [];
return self.filterEnumerator(self.getPredicatesFromSubject(aSubject), function(aPredicate){
if(aPredicateIsUnknownAnonymous && !RDFService.IsAnonymousResource(aPredicate)) return [];
return self.filterEnumerator(self.getObjects(aSubject, aPredicate), function(aObject){
if(aObjectIsUnknownAnonymous && !RDFService.IsAnonymousResource(aObject)) return [];
return [aSubject, aPredicate, aObject]
})
})
})
}
, setTriple: function(aSubject, aPredicate, aObject){
var self = this;
[aSubject, aPredicate, aObject] = [aSubject, aPredicate, aObject].map(function(e){return self.getResource(e)});
var added = !this.hasTriple(aSubject, aPredicate, aObject);
this.assertTriple(aSubject, aPredicate, aObject);
return added;
}
// not yet
, saveAsXML: function(){
var onload = function(){
log(outputStream.data)
}
this.serializeToStream({onwrite:onwrite});
}
, getAsXML: function(){
return this.asXML();
}
// mozilla interface
, getSubjects: function(aPredicate, aObject){return this.dataSource.GetSources(aPredicate, aObject, true)}
, getPredicatesFromSubject: function(aSubject){return this.dataSource.ArcLabelsOut(aSubject)}
, getPredicatesFromObject: function(aObject){return this.dataSource.ArcLabelsIn(aObject)}
, getObjects: function(aSubject, aPredicate){return this.dataSource.GetTargets(aSubject, aPredicate, true)}
, getAllResources: function(){return this.dataSource.GetAllResources()}
, modifySubject: function(){return this.dataSource.Move(aOldSubject, aNewSubject, aPredicate, aOldSource)}
, modifyObject: function(){return this.dataSource.Change(aSubject, aPredicate, aOldObject, aNewObject)}
, assertTriple: function(aSubject, aPredicate, aObject){return this.dataSource.Assert(aSubject, aPredicate, aObject, true)}
, removeTriple: function(aSubject, aPredicate, aObject){return this.dataSource.Unassert(aSubject, aPredicate, aObject)}
, hasTriple: function(aSubject, aPredicate, aObject){return this.dataSource.HasAssertion(aSubject, aPredicate, aObject, true)}
, hasSP: function(aSubject, aPredicate){return this.dataSource.hasArcIn(aSubject, aPredicate)}
, hasPO: function(aPredicate, aObject){return this.dataSource.hasArcOut(aObject, aPredicate)}
, asXML: function(onWrite){
var outputStream = {
data: "",
close : function(){},
flush : function(){},
write : function (buffer,count){
onWrite ? onWrite(buffer) : this.data += buffer;
return count;
},
writeFrom : function (stream,count){},
isNonBlocking: false
}
var serialize = Cc["@mozilla.org/rdf/xml-serializer;1"].createInstance(Ci.nsIRDFXMLSerializer);
serialize.init(this.dataSource);
serialize.QueryInterface(Ci.nsIRDFXMLSource).Serialize(outputStream);
return (onWrite ? null : outputStream.data);
}
};
function RDFQuery(){ return (this instanceof RDFQuery) ? this : new RDFQuery()};
RDFQuery.prototype = {
init: function(aRDF, aDataSource){
this.rdf = aRDF;
this.dataSource = aDataSource;
this.namespace = {}; // {'rdf:' : 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', }
this.variable = {}; // {'foo' : [RDFResource1, RDFResource2 ,,,], }
this.queryTripleMap = []; // [{ qurey: [null, null, "foo"] // null is not variable
// , triples: [triple1,triple2]
// }, olderOjbect,,, ]
return this;
}
, parseIRI: function(aIRI){ // <IRI> => IRI
return (aIRI.match(/^<.+>$/) && aIRI.slice(1,-1))
}
, parsePrefixedIRI: function(aIRI){ // "rdf:foo" => [http://www.w3.org/1999/02/22-rdf-syntax-ns#foo]
var res = aIRI.match(/^(\w.*:)([a-zA-Z]+)/);
if(!res) return false;
var prefix = res[1];
var name = res[2];
var uri = this.namespace[prefix];
if(!uri) throw(Error("RDFQuery: specified prefix [" + prefix + "] is not declared."));
return uri + name;
}
, parseVariable: function(aVariable){ // ?foo or $foo. they are same variables.
var matched = aVariable.match(/^($|\?)(.+)/);
if(!matched) return false;
var aVariableName = matched[2];
return {name: aVariableName,
resources: this.variable[aVariableName]}
}
, parseanonymous: function(str){
return (str == "[]") ? str : false;
}
, parseStringLiteral: function(aString){
return ((aString.slice(0,1) == '"') && (aString.slice(-1) == '"')) ? aString.slice(1,-1) : false;
}
, parseIntLiteral: function(aInteger){
return aInteger.match('^[0-9]+$') ? parseInt(aInteger) : false;
}
, optimizeVariables: function(){// 後から追加された条件による変数の最適化を行わないと変数が指すリソースが増えまくる
var self = this;
this.queryTripleMap.map(function(obj){
obj.query.forEach(function(aVariableName,n){ // just 3 times
if(aVariableName){
if(self.variable[aVariableName].length > 0){
var aNewObjTriples = [];
self.variable[aVariableName] = self.variable[aVariableName].filter(function(aStragedResource){
return obj.triples.some(function(aTriple){
return ((aTriple[n] == aStragedResource) && aNewObjTriples.push(aTriple) && true)
})
})
obj.triples = aNewObjTriples;
}else{
obj.triples.forEach(function(aTriple){
if(!self.variable[aVariableName].some(function(aStragedTriple){
return aStragedTriple == aTriple[n]
})){
self.variable[aVariableName].push(aTriple[n]);
}
})
}
}
})
})
}
, splitQueryTriple: function(aQuery){
var quoted = new RegExp('\\s*((["\'])([^\\2]*)\\2)\\s*');
var whitespace = new RegExp('\\s+');
var remained = aQuery.replace(quoted, "");
var aQuotedString = RegExp.$1
var res = remained.split(whitespace);
if((remained != aQuery) && aQuotedString){
res.push(aQuotedString);
}
// log('splitQueryTriple:',[aQuery, aQuotedString])
return res;
}
// interface for using RDFQuery
, prefix: function(aNamespace){this.namespace = aNamespace; return this}
, where: function(aQuery){
var self = this;
// aSubject, aPredicate, aObject
var resources = [null, null, null];
var params = [null, null, null];
var variable = [null, null, null]; // for fist time variable
var queries = this.splitQueryTriple(aQuery);
queries.forEach(function(aValue,n){
var anonymous;
var aInteger,aString,aIRI;
if(typeof(aInteger = self.parseIntLiteral(aValue)) == 'number'){
params[n] = aInteger;
}else if((aString = self.parseStringLiteral(aValue))){
params[n] = aString;
}else if((aIRI = self.parseIRI(aValue) || self.parsePrefixedIRI(aValue))){
params[n] = aIRI;
}else if((anonymous = self.parseanonymous(aValue))){
params[n] = [];
}else{ // variable
var res = self.parseVariable(aValue);
variable[n] = res.name;
if(!res.resources){
self.variable[res.name] = [];
}else if(res.resources.length == 0){
throw(Error("RDFQuery: specified variable [" + res.name + "] is pointing empty resources list."));
}else if(res.resources.length == 1){
params[n] = res.resources
}else if(res.resources.length > 1){
params[n] = null;
resources[n] = res.resources
}
}
})
var triples = this.rdf.getTriples.apply(this.rdf, params);
resources.forEach(function(resourceList, n){
if(resourceList){
triples = triples.filter(function(aTriple){
return resourceList.filter(function(resource){
return aTriple[n] == resource
})
})
}
})
this.queryTripleMap.unshift({
query: variable
, 'triples': triples
});
this.optimizeVariables();
return this
}
, select: function(aQuery){
var self = this;
var queries = aQuery.split(" ");
var result = [];
queries.forEach(function(query){
var obj = self.parseVariable(query);
if(!obj.resources) throw(Error("RDFQuery: specified variable [" + obj.name + "] is not declared."));
if(obj.resources.length){
result.push(obj.resources)
}
})
return result;
}
, add: function(aQuery){
var self = this;
var queries = this.splitQueryTriple(aQuery);
var params = [[],[],[]]; // subjects, predicates, objects
queries.forEach(function(aValue,n){
var anonymous;
var aInteger,aString,aIRI;
if(typeof(aInteger = self.parseIntLiteral(aValue)) == 'number'){
params[n].push(aInteger);
}else if((aString = self.parseStringLiteral(aValue))){
params[n].push(aString);
}else if((aIRI = self.parseIRI(aValue) || self.parsePrefixedIRI(aValue))){
params[n].push(aIRI);
}else if((anonymous = self.parseanonymous(aValue))){
params[n].push([]);
}else{ // variable
var res = self.parseVariable(aValue);
if(!res.resources){
throw(Error("RDFQuery: specified variable [" + res.name + "] is not declared."));
}else if(res.resources.length == 0){
throw(Error("RDFQuery: specified variable [" + res.name + "] is pointing empty resources list."))
}else{
params[n] = res.resources
}
}
})
params[0].forEach(function(aSubject){
params[1].forEach(function(aPredicate){
params[2].forEach(function(aObject){
self.rdf.setTriple(aSubject, aPredicate, aObject)
})
})
})
return this;
}
};
// is this ok ?
return (((typeof(aValue) == 'string') && ~aValue.indexOf(":")) ? RDFWrapper.FromURL(aValue) :
(typeof(aValue) == 'xml') ? RDFWrapper.FromDocument(aValue) :
(aValue instanceof Document) ? RDFWrapper.FromDocument(aValue) :
false);
}
RDF.Test = function(){
var xml = <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:a="http://example.org/a/"
xmlns:b="http://example.org/b#"
xmlns:c="http://example.org/c/c">
<rdf:Description rdf:about="http://example.org/">
<a:foo>foo text</a:foo>
<b:bar>bar text</b:bar>
<c:baz>baz text</c:baz>
</rdf:Description>
</rdf:RDF>;
// var rdf = RDF("http://test.test/test001.rdf");
var rdf = RDF(xml);
log(rdf.asXML());
rdf.query()
.prefix({
"d:":"http://example.org/ns1#"
})
.add('<http://example.org/> d:qux "this is qux"'); // 破壊的
log(rdf.asXML());
var res = rdf.query()
.prefix({
"a:":"http://www.w3.org/TR/REC-rdf-syntax#"
, "b:":"http://www.w3.org/TR/REC-rdf-syntax-blah-blah#"
, "c:":"http://www.w3.org/TR/REC-rdf-syntaxc#"
, "d:":"http://example.org/ns1#"
})
.where('<http://example.org/> a:foo ?foo')
.where('?uri a:foo ?foo')
.where('?uri b:bar ?bar')
.where('?uri1 b:bar ?bar')
.where('?uri1 c:baz ?baz')
.where('?uri1 d:qux ?qux')
.select("?uri ?uri1 ?foo ?bar ?baz ?qux");
log(res);
}
/// memo ------------
//
// QueryInterface
// URI
// GetSource
// GetSources
// GetTarget
// GetTargets
// Assert
// Unassert
// Change
// Move
// HasAssertion
// AddObserver
// RemoveObserver
// ArcLabelsIn
// ArcLabelsOut
// GetAllResources
// IsCommandEnabled
// DoCommand
// GetAllCmds
// hasArcIn
// hasArcOut
// beginUpdateBatch
// endUpdateBatch
// loaded
// Init
// Refresh
// Flush
// FlushTo
// readOnly
// beginLoad
// interrupt
// resume
// endLoad
// addXMLSinkObserver
// removeXMLSinkObserver
// onDataAvailable
// visitAllSubjects
// visitAllTriples
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment