12 June 2009

RDF : javascript, xsl stylesheets

A few notes:
I've implemented a javascript library to parse RDF (I love re-inventing the wheel, it's always interesting to learn how softwares and algorithms are working ). The RDF syntax is still not fully implemented (e.g. it don't support xml:lang, parseType=Literal, etc...)

.
/**************************
*
* RDF library in Javascript
* Author: Pierre Lindenbaum
* plindenbaum@yahoo.fr
* http://plindenbaum.blogspot.com
*/
/**
* A RDF Resource
*/
function Resource(uri)
{
this.uri=uri;
}
Resource.prototype.isLiteral=function()
{
return false;
};
Resource.prototype.isResource=function()
{
return true;
};
Resource.prototype.compareTo=function(other)
{
if(!other.isResource()) return -1;
return this.uri.localeCompare(other.uri);
};
Resource.prototype.toString=function()
{
if(this.isAnonId()) return this.uri;
return "<"+this.uri+">";
};
Resource.prototype.isAnonId=function()
{
return this.uri.substr(0,2)=="_:";
};
/**
* A RDF Literal
*/
function Literal(content,type,lang)
{
this.content=content;
this.dataType=null;
this.lang=null;
}
Literal.prototype.isLiteral=function()
{
return true;
};
Literal.prototype.isResource=function()
{
return false;
};
Literal.prototype.toString=function()
{
return "\""+this.content+"\"";//TODO escape this !
};
Literal.prototype.compareTo=function(other)
{
if(!other.isLiteral()) return 1;
var i= this.content.localeCompare(other.content);
if(i!=0) return i;
};
/**
* A RDF Statement (S,P,V)
*/
function Statement( subject, predicate,value)
{
this.subject=subject;
this.predicate=predicate;
this.value=value;
}
Statement.prototype.isLiteral=function()
{
return this.value.isLiteral();
};
Statement.prototype.isResource=function()
{
return this.value.isResource();
};
Statement.prototype.compareTo=function(other)
{
var i= this.subject.compareTo(other.subject);
if(i!=0) return i;
i= this.predicate.compareTo(other.predicate);
if(i!=0) return i;
return this.value.compareTo(other.value);
};
Statement.prototype.toString=function()
{
return ""+this.subject+" "+this.predicate+" "+this.value+" .";
};
/**
* A RDF store
* statements are stored in a sorted array
*/
function RDFStore()
{
this.statements= new Array();
}
RDFStore.prototype.clear= function()
{
return this.statements.slice(0,this.statements.length);
};
RDFStore.prototype.size= function()
{
return this.statements.length;
};
RDFStore.prototype.lowerBound= function(stmt)
{
var first=0;
var len = this.size();
while (len > 0)
{
var half = (len / 2) | 0; //hack , cast to int
var middle = first + half;
var x= this.at(middle);
if(x.compareTo(stmt)<0)
{
first = middle + 1;
len -= half + 1;
}
else
{
len = half;
}
}
return first;
};
RDFStore.prototype.at= function(index)
{
return this.statements[index];
};
RDFStore.prototype.add= function(stmt)
{
var i= this.lowerBound(stmt);
if(i<this.size())
{
if( this.at(i).compareTo(stmt) == 0) return false;
}
this.statements.splice(i,0,stmt);
return true;
}
RDFStore.prototype.contains= function(stmt)
{
var i= this.lowerBound(stmt);
return i<this.size() && this.at(i).compareTo(stmt) == 0;
};
RDFStore.prototype.remove= function(stmt)
{
var i= this.lowerBound(stmt);
if(i<this.size())
{
if( this.at(i).compareTo(stmt) != 0) return;
}
this.statements.splice(i,1);
};
/**
* find a given statement, arguments can be null to allow all the nodes
* this method should be improved as all statements are sorted, access
* should be straightforward
*/
RDFStore.prototype.filter= function(s,p,v)
{
var store= new RDFStore();
for(var i=0;i< this.size();++i)
{
var stmt= this.at(i);
if(s!=null && s.compareTo(stmt.subject)!=0) continue;
if(p!=null && p.compareTo(stmt.predicate)!=0) continue;
if(v!=null && v.compareTo(stmt.value)!=0) continue;
store.add(stmt);
}
return store;
};
RDFStore.prototype.parse=function(dom)
{
}
/**
* parse a DOM document and extract the Statements
* parseType="Literal" not implemented
*/
function DOM4RDF()
{
}
var ANONID=0;
DOM4RDF.prototype.parse=function(dom,store)
{
this.store = (typeof(store) != 'undefined' ? store : null );
var tmp= this.parseRDF(dom.documentElement);
this.store=null;
return tmp;
};
/** creates an anonymous ID */
DOM4RDF.prototype.createAnonId=function()
{
return new Resource("_:"+(++ ANONID));
};
/** parse a rdf:RDF element */
DOM4RDF.prototype.parseRDF=function(root)
{
if(root==null) throw "null root";
if(!this.isA(root, RDF.NS, "RDF")) throw "Root is not rdf:RDF";
//loop over children of rdf:RDF
for(var n1= root.firstChild;
n1!=null;n1=n1.nextSibling)
{
switch(n1.nodeType)
{
case 1:
{
this.parseResource(n1);
break;
}
case 3:
case 4:
{
this.checkNodeIsEmpty(n1);
break;
}
case 7:
{
this.warning(n1, "Found Processing instruction under "+root.nodeName);
break;
}
case 8:break;
default: throw "Node type not handled : "+n1.nodeType;
}
}
return this.store;
};
/** return wether this node a a rdf:(abou|ID|nodeId) */
DOM4RDF.prototype.isAnonymousResource=function(rsrc)
{
var att= rsrc.getAttributeNodeNS(RDF.NS, "about");
if(att!=null) return false;
att= rsrc.getAttributeNodeNS(RDF.NS, "ID");
if(att!=null) return false;
att= rsrc.getAttributeNodeNS(RDF.NS, "nodeID");
if(att!=null) return false;
return true;
};
/** returns a URI for an Resource element */
DOM4RDF.prototype.getResourceURI=function( root)
{
var subject=null;
if(root.hasAttributes())
{
for(var i=0;i< root.attributes.length;++i)
{
var att=root.attributes[i];
if( RDF.NS != att.namespaceURI ) continue;
if(att.localName == "about")
{
if(subject!=null) throw "subject id defined twice";
subject= new Resource(att.value);
}
else if(att.localName=="ID")
{
if(subject!=null) throw "subject id defined twice";
var val= att.value;
if(!val.startsWith("#")) val="#"+val;
subject= new Resource(this.getBase(root)+val);
}
else if(att.localName == "nodeID" )
{
if(subject!=null) throw "subject id defined twice";
subject= new Resource("_:"+att.value);
//uri= URI.create(getBase(root)+att.value);
}
}
}
if(subject==null) subject= this.createAnonId();
return subject;
};
/** parse everything under rdf:RDF
* @return the URI of the resource
*/
DOM4RDF.prototype.parseResource=function(root)
{
var subject= this.getResourceURI(root);
if(root.hasAttributes())
{
for(var i=0;i< root.attributes.length;++i)
{
var att=root.attributes[i];
if(RDF.NS == att.namespaceURI)
{
if(att.localName=="about" ||
att.localName=="nodeID" ||
att.localName=="ID"
)
{
continue;
}
else if(att.localName=="resource")
{
throw "should not contains rdf:resource";
}
else
{
throw "rdf:* node supported";
}
}
else if(att.prefix=="xmlns")
{
//ignore
}
else
{
if(att.namespaceURI==null)
{
throw "No NamespaceURI associated with "+att.nodeName;
}
this.foundStatement(
subject,
new Resource(att.namespaceURI+att.localName),
new Literal(att.value)
);
}
}
}
if(!this.isA(root, RDF.NS, "Resource"))
{
if(root.namespaceURI==null)
{
throw "No NamespaceURI associated with "+root.nodeName;
}
this.foundStatement(
subject,
new Resource(RDF.NS+"type"),
new Resource(root.namespaceURI+root.localName)
);
}
this.parseResourceChildren(root, subject);
return subject;
};
DOM4RDF.prototype.isA=function(node,ns,localName)
{
return node.namespaceURI==ns && node.localName==localName;
};
/** check a node contains only a blank stuff */
DOM4RDF.prototype.checkNodeIsEmpty=function( n1)
{
if(n1.nodeValue.replace(/^\s+|\s+$/g, "").length!=0)
{
throw "Found not whitespace content under "+n1.parentNode.nodeName;
}
return true;
};
/** parse everything under a resource element */
DOM4RDF.prototype.parseResourceChildren=function(root, subjectURI)
{
for(var n1= root.firstChild;
n1!=null;
n1=n1.nextSibling)
{
switch(n1.nodeType)
{
case 1:
{
this.parseProperty(n1,subjectURI);
break;
}
case 3:case 4:
{
this.checkNodeIsEmpty(n1);
break;
}
case 7:
{
this.warning(n1, "Found Processing instruction under "+root.nodeName);
break;
}
case 8:break;
default:throw "invalid node type";
}
}
};
/** return the xml:lang of the node or null */
DOM4RDF.prototype.getLang=function( root)
{
if(root==null) return null;
if( root.nodeType==1 && root.hasAttributes())
{
var att= root.getAttributeNodeNS(XML.NS, "lang");
if(att!=null) return att.value;
}
return this.getLang(root.parentNode);
};
/** parse everything under rdf:RDF */
DOM4RDF.prototype.parseProperty=function( property, subject)
{
var parseTypeNode= property.getAttributeNodeNS(RDF.NS, "parseType");
var dataTypeNode = property.getAttributeNodeNS(RDF.NS, "dataType");
var dataType= (dataTypeNode==null?null:dataTypeNode.value);
var parseType = parseTypeNode!=null?parseTypeNode.value:null;
if(property.namespaceURI==null)
{
throw "no namespaceURI for "+property.tagName;
}
var predicate= new Resource(property.namespaceURI+property.localName);
if(predicate==null)
{
throw "Cannot parse URI of this predicate";
}
/** default parse type */
if(parseType==null)
{
var rsrc= property.getAttributeNodeNS(RDF.NS, "resource");
if(!property.hasChildNodes())
{
if(rsrc==null)
{
//strange behavior of DOM parser. &lt;tag&gt;&lt;/tag&gt; is same as &lt;tag/&gt; ??!
this.foundStatement(subject, predicate,new Literal(""));
//throw new InvalidXMLException(property,"missing rdf:resource");
}
else
{
this.foundStatement(subject, predicate, new Resource(rsrc.value));
}
}
else
{
if(rsrc!=null) throw "rdf:resource is present and element has children";
if(predicate.uri == (RDF.NS+"type")) throw "rdf:type expected in an empty element";
var count = 0;
var firstChild=null;
for(var n1=property.firstChild;n1!=null;n1=n1.nextSibling)
{
if(n1.nodeType!=1) continue;
count++;
if(firstChild==null) firstChild=n1;
}
switch(count)
{
case 0: var L= new Literal( property.textContent);
L.dataType= dataType;
L.lang= this.getLang(property);
this.foundStatement(subject, predicate,L );
break;
case 1: var value= this.parseResource(firstChild);
this.foundStatement(subject, predicate,value);
break;
default: throw "illegal number of element under.";
}
}
}
else if( parseType == "Literal" )
{
var buff= "";
for(var n1=property.firstChild;n1!=null;n1=n1.nextSibling)
{
switch(n1.nodeType)
{
case 3:
case 4:
{
buff += n1.textContent;
break;
}
case 1:
{
//TODO
break;
}
default: throw "node type unsupported "+n1.nodeType;
}
}
if(subject!=null && predicate!=null && buff!=null)
{
var L2= new Literal(buff);
L2.dataType= RDF.NS+"XMLLiteral";
L2.lang= this.getLang(property);
this.foundStatement(subject,predicate,L2);
}
}
else if(parseType=="Resource")
{
var rsrc= this.createAnonId();
if(subject!=null && predicate!=null)
{
this.foundStatement(subject,predicate,rsrc);
}
for(var n1=property.firstChild;n1!=null;n1=n1.nextSibling)
{
switch(n1.nodeType)
{
case 3:
{
this.checkNodeIsEmpty(n1);
break;
}
case 1:
{
this.parseProperty(n1, rsrc);
break;
}
case 8:break;
default: this.warning(n1, "unsupported node type");break;
}
}
}
else if(parseType=="Collection")
{
var list= new Array();
for(var n1=property.firstChild;n1!=null;n1=n1.nextSibling)
{
switch(n1.nodeType)
{
case 8:break;
case 3:
{
this.checkNodeIsEmpty(n1);
break;
}
case 1:
{
var r= this.parseResource(n1);
list.push(r);
break;
}
default: this.warning(n1, "unsupported node type");break;
}
}
if(list.length==0)
{
this.warning(property,"Empty list");
}
else
{
var prevURI= this.createAnonId();
if(subject!=null && predicate!=null)
{
this.foundStatement(subject, predicate,prevURI);
}
for(var i=0;i< list.length;++i)
{
if(i+1==list.length)
{
this.foundStatement(prevURI,new Resource(RDF.NS+"first"), list[i]);
this.foundStatement(prevURI,new Resource(RDF.NS+"rest"), new Resource(RDF.NS+"nil"));
}
else
{
var newURI= this.createAnonId();
this.foundStatement(prevURI,new Resource(RDF.NS+"first"), list[i]);
this.foundStatement(prevURI,new Resource(RDF.NS+"rest"), newURI);
prevURI=newURI;
}
}
}
}
else
{
throw "illegal rdf:parseType:"+parseType;
}
};
/** get the BASE url of the document */
DOM4RDF.prototype.getBase=function( n)
{
var s="TODO";//n.ownerDocument.getBaseURI();
if(s==null) throw "document has not xml:base";
return new Resource(s);
};
/**
* Called when a Statement was found. User can override this method.
* Default: does nothing
* @param subject
* @param property
* @param value
* @param dataType
* @param lang
*/
DOM4RDF.prototype.foundStatement=function(subject,property,value)
{
if(this.store!=null) this.store.add(new Statement(subject,property,value));
};
DOM4RDF.prototype.warning=function(node,message)
{
//logMsg("Warning " +message);
};
view raw gistfile1.txt hosted with ❤ by GitHub
<?xml version="1.0" encoding="UTF-8"?>
<window id="xulfox"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Test RDF">
<script src="utils.js"></script>
<script src="rdf.js"></script>
<script><![CDATA[
var documentBuilder=new DOMParser();
function parseRDF(textarea)
{
var e=document.getElementById("label.message");
var xmlDoc= documentBuilder.parseFromString(textarea.value,"text/xml");
if(xmlDoc==null || xmlDoc.documentElement.localName=="parsererror")
{
e.value= "Error"+xmlDoc.documentElement.textContent;
}
else
{
try
{
var dom4rdf= new DOM4RDF();
var store= dom4rdf.parse(xmlDoc,new RDFStore());
var treechildren= document.getElementById("statements");
var n= treechildren.firstChild;
while(n!=null)
{
if(n.nodeType==1 && n.localName=="treeitem")
{
var n2=n;
n=n.nextSibling;
treechildren.removeChild(n2);
continue;
}
else
{
n=n.nextSibling;
}
}
for(n=0;n< store.size();++n)
{
var stmt= store.at(n);
var treeitem = document.createElementNS(XUL.NS,"treeitem");
var treerow = document.createElementNS(XUL.NS,"treerow");
treeitem.appendChild(treerow);
var listS = document.createElementNS(XUL.NS,"treecell");
listS.setAttribute("label", stmt.subject.toString());
var listP = document.createElementNS(XUL.NS,"treecell");
listP.setAttribute("label", stmt.predicate.toString());
var listO = document.createElementNS(XUL.NS,"treecell");
listO.setAttribute("label", stmt.value.toString());
treerow.appendChild(listS);
treerow.appendChild(listP);
treerow.appendChild(listO);
treechildren.appendChild(treeitem);
}
e.value="";
}
catch(err)
{
e.value="RDF:Error "+err;
}
}
e.tooltiptext = e.value;
}
]]></script>
<dialogheader title="Test For RDF" description="Pierre Lindenbaum plindenbaum yahoo.fr"/>
<hbox flex="1">
<vbox flex="1">
<groupbox id="create.list.panel" flex="1">
<caption label="Create Your RDF"/>
<textbox multiline="true" emptytext="RDF as XML" value="&lt;rdf:RDF xmlns:rdf=&quot;http://www.w3.org/1999/02/22-rdf-syntax-ns#&quot;&gt;&lt;/rdf:RDF&gt;" flex="1" onkeyup="parseRDF(this);"/>
<hbox flex="1">
<label width="100%" id="label.message" value="" style="font-size:12pt;color:red;" flex="1" crop="end"/>
</hbox>
</groupbox>
</vbox>
<vbox flex="1">
<groupbox id="create.list.panel" flex="1">
<caption label="Statements"/>
<tree flex="1" rows="10">
<treecols>
<treecol label="Subject" flex="1" />
<splitter class="tree-splitter"/>
<treecol label="Predicate" flex="1" />
<splitter class="tree-splitter"/>
<treecol label="Object" flex="1" />
</treecols>
<treechildren id="statements">
</treechildren>
</tree>
</groupbox>
</vbox>
</hbox>
</window>
view raw rdf.xul hosted with ❤ by GitHub
String.prototype.trim = function() {
try {
return this.replace(/^\s+|\s+$/g, "");
} catch(e) {
return this;
}
};
String.prototype.startsWith = function(s)
{
if(s==null || this.length < s.length) return false;
return this.substr(0,s.length) == s;
};
function $(id)
{
var e = document.getElementById(id);
return e;
}
var XUL={NS:"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"};
var HTML={NS:"http://www.w3.org/1999/xhtml"};
var RDF={NS:"http://www.w3.org/1999/02/22-rdf-syntax-ns#"};
var RDFS={NS:"http://www.w3.org/2000/01/rdf-schema#"};
var XLINK={NS:"http://www.w3.org/1999/xlink"};
var SVG={NS:"http://www.w3.org/2000/svg"};
var DC={NS:"http://purl.org/dc/elements/1.1/"};
var XML={NS:"http://www.w3.org/XML/1998/namespace"};
var XMLNS={NS:"http://www.w3.org/2000/xmlns/"};
view raw util.js hosted with ❤ by GitHub


I've also created 3 XSLT stylesheets transforming RDF to ....

  • N3:
    xsltproc rdf2n3.xsl http://www.w3.org/TR/rdf-syntax-grammar/example12.rdf
    <http://www.w3.org/TR/rdf-syntax-grammar> <http://purl.org/dc/elements/1.1/title> "RDF/XML Syntax Specification (Revised)" .
    <http://www.w3.org/TR/rdf-syntax-grammar> <http://example.org/stuff/1.0/editor> <_:anodeid2245696> .
    <_:anodeid2245696> <http://example.org/stuff/1.0/fullName> "Dave Beckett" .
    <_:anodeid2245696> <http://example.org/stuff/1.0/homePage> <http://purl.org/net/dajobe/> .
  • rdf:Statement:
    xsltproc rdf2rdf.xsl http://www.w3.org/TR/rdf-syntax-grammar/example12.rdf

    <?xml version="1.0"?>
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Statement>
    <rdf:subject rdf:resource="http://www.w3.org/TR/rdf-syntax-grammar"/>
    <rdf:predicate rdf:resource="http://purl.org/dc/elements/1.1/title"/>
    <rdf:object>RDF/XML Syntax Specification (Revised)</rdf:object>
    </rdf:Statement>
    <rdf:Statement>
    <rdf:subject rdf:resource="http://www.w3.org/TR/rdf-syntax-grammar"/>
    <rdf:predicate rdf:resource="http://example.org/stuff/1.0/editor"/>
    <rdf:object rdf:resource="_:anodeid2245620"/>
    </rdf:Statement>
    <rdf:Statement>
    <rdf:subject rdf:resource="_:anodeid2245620"/>
    <rdf:predicate rdf:resource="http://example.org/stuff/1.0/fullName"/>
    <rdf:object>Dave Beckett</rdf:object>
    </rdf:Statement>
    <rdf:Statement>
    <rdf:subject rdf:resource="_:anodeid2245620"/>
    <rdf:predicate rdf:resource="http://example.org/stuff/1.0/homePage"/>
    <rdf:object rdf:resource="http://purl.org/net/dajobe/"/>
    </rdf:Statement>
    </rdf:RDF>
  • SQL statements:
    xsltproc rdf2sql.xsl http://www.w3.org/TR/rdf-syntax-grammar/example12.rdf


    create table TRIPLE IF NOT EXISTS
    (
    subject varchar(50) not null,
    predicate varchar(50) not null,
    value_is_uri enum('true','false') not null,
    value varchar(50) not null //need to fix dataType and xml:lang
    );
    insert into TRIPLE(subject,predicate,value_is_uri,value) values ("http://www.w3.org/TR/rdf-syntax-grammar","http://purl.org/dc/elements/1.1/title","false","RDF/XML Syntax Specification (Revised)");
    insert into TRIPLE(subject,predicate,value_is_uri,value) values ("http://www.w3.org/TR/rdf-syntax-grammar","http://example.org/stuff/1.0/editor","true","_:anodeid2245974");
    insert into TRIPLE(subject,predicate,value_is_uri,value) values ("_:anodeid2245974","http://example.org/stuff/1.0/fullName","false","Dave Beckett");
    insert into TRIPLE(subject,predicate,value_is_uri,value) values ("_:anodeid2245974","http://example.org/stuff/1.0/homePage","true","http://purl.org/net/dajobe/");



That's it
Pierre

No comments: