12 March 2009

A few nightmares before biohackathon 2009.

OK, after Scifoo 2007 (http://plindenbaum.blogspot.com/2007/07/scifoo-07-anxiety-from-homebody.html). Here are my apprehensions for BioHackathon 2009, you know, I'm lost and anxious when I cannot see the Peripherique ;-)
















My notebook for a Stupid RDF Server

In this post, I'm writing my notes about the java package com.sun.net.httpserver.*. This package contains a lightweight HTTP server and I've tested it to create a simple-and-stupid RDF server.
The source code is available at:



OK. First we need a 'Statement' class holding a RDF triple:
private static class Statement
{
/** subject of this statement */
private URI subject;
/** predicate of this statement */
private URI predicate;
/** value of this statement a String or a URI*/
private Object value;


boolean isLiteral()
{
return value.getClass()==String.class;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + predicate.hashCode();
result = prime * result + subject.hashCode();
result = prime * result + value.hashCode();
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
Statement other = (Statement) obj;
return subject.equals(other.subject) &&
predicate.equals(other.predicate) &&
value.equals(other.value)
;
}


@Override
public String toString() {
return "<"+subject+"> <"+predicate+"> "+(isLiteral()
?"\""+C.escape(String.class.cast(value))+"\""
:"<"+value+"> ."
);
}
}

The statements are just stored in a synchronized set. That is to say, we the server stops, all the statements are lost :-).
private Set<Statement> statements= Collections.synchronizedSet(new HashSet<Statement>());


The 'servlet' StupidRDFServer is a class that implementing com.sun.net.httpserver.HttpHandler, that is to say it must implement the method public void handle(HttpExchange http) throws IOException

The following code starts the server.
public static void main(String[] args)
{
try
{
HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);
server.createContext(CONTEXT, new StupidRDFServer());
server.start();
}
catch(IOException err)
{
err.printStackTrace();
}
}
If the query is empty, the following form is returned to the client.
Add Statement 
 
 
  


The 'Add Statements' button adds a statement in the list:
respHeader.add("Content-Type", "text/html");
http.sendResponseHeaders(200, 0);
boolean added=this.statements.add(stmt);
printForm(out,(added?"Statement Added":"Statement already in model"));

The 'Query N3' button prints the triples, using the parameters of the form as a filter:
respHeader.add("Content-Type", "text/plain");
http.sendResponseHeaders(200, 0);
synchronized(statements)
{
Iterator<Statement> iter= statements.iterator();
while(iter.hasNext())
{
Statement triple=iter.next();
if(
(stmt.subject==null || stmt.subject.equals(triple.subject)) &&
(stmt.predicate==null || stmt.predicate.equals(triple.predicate)) &&
(stmt.value==null || stmt.value.equals(triple.value))
)
{
out.println(triple);
}
}
}


The 'Query RDF' button prints the triples as RDF, using the parameters of the form as a filter. Example of output:
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Statement>
<rdf:subject rdf:resource="foaf%3Ame"/>
<rdf:predicate rdf:resource="foaf%3Aname"/>
<rdf:object>Pierre</rdf:object>
</rdf:Statement>
<rdf:Statement>
<rdf:subject rdf:resource="urn%3Aother"/>
<rdf:predicate rdf:resource="foaf%3Aknows"/>
<rdf:object rdf:resource="foaf%3Aknows"/>
</rdf:Statement>
</rdf:RDF>


That's it

07 March 2009

A lightweight java parser for RDF

About one year ago, I wrote a lightweight java parser for RDF based on the Stream API for XML (Stax). It is far from being perfect as , for example, it does not handle the reified statements, xml:base, ... but it is small (24K) and works fine with most RDF files. Inspired by the XML SAX parsers, this RDF parser doesn't keep the statements in memory but calls a method "found" each time a triple is found. This method can be overridden to implement your own code.

Source code


The code is available at

RDFEvent


First we need a small internal class to record the content of each triple
private static class RDFEvent
{
URI subject=null;
URI predicate=null;
Object value=null;
URI valueType=null;
String lang=null;
int listIndex=-1;
(...)
}

Searching for rdf:RDF


First we scan the elements of the document until the <rdf:RDF> element is found. Then, the method parseRDF is called.
this.parser = this.factory.createXMLEventReader(in);

while(getReader().hasNext())
{
XMLEvent event = getReader().nextEvent();
if(event.isStartElement())
{
StartElement start=(StartElement)event;
if(name2string(start).equals(RDF.NS+"RDF"))
{
parseRDF();
}
}
}

parseRDF: Searching the statements


All the nodes are then scanned .The method parseDescription is called for each element.
while(getReader().hasNext())
{
XMLEvent event = getReader().nextEvent();
if(event.isEndElement())
{
return;
}
else if(event.isStartElement())
{
parseDescription(event.asStartElement());
}
else if(event.isProcessingInstruction())
{
throw new XMLStreamException("Found Processing Instruction in RDF ???");
}
else if(event.isCharacters() &&
event.asCharacters().getData().trim().length()>0)
{
throw new XMLStreamException("Found text in RDF ???");
}
}

parseDescription: Parsing the subject of a triple


The current element will be the subject of the triple.
The URI of this subject need to extracted.
First we check if this URI can be extracted from an attribute rdf:about
Attribute att= description.getAttributeByName(new QName(RDF.NS,"about"));
if(att!=null) descriptionURI= createURI( att.getValue());

If it was not found, the attribute rdf:nodeID is searched:
att= description.getAttributeByName(new QName(RDF.NS,"nodeID"));
if(att!=null) descriptionURI= createURI( att.getValue());

If it was not found, the attribute rdf:ID is searched.
att= description.getAttributeByName(new QName(RDF.NS,"ID"));
if(att!=null) descriptionURI= resolveBase(att.getValue());

If it was not found, this is an anonymous node. We create a random URI.
descriptionURI= createAnonymousURI();


rdf:type


The qualified name of the element contains the rdf:type of this statement. We can emit a new triple about this type:
QName qn=description.getName();
if(!(qn.getNamespaceURI().equals(RDF.NS) &&
qn.getLocalPart().equals("Description")))
{
RDFEvent evt= new RDFEvent();
evt.subject=descriptionURI;
evt.predicate= createURI(RDF.NS+"type");
evt.value=name2uri(qn);
found(evt);
}


Other attributes


The other attributes of the current element may contains some new triples.
for(Iterator<?> i=description.getAttributes();
i.hasNext();)
{
att=(Attribute)i.next();
qn= att.getName();
String local= qn.getLocalPart();
if(qn.getNamespaceURI().equals(RDF.NS) &&
( local.equals("about") ||
local.equals("ID") ||
local.equals("nodeID")))
{
continue;
}
RDFEvent evt= new RDFEvent();
evt.subject=descriptionURI;
evt.predicate= name2uri(qn);
evt.value= att.getValue();
found(evt);
}

Searching the predicates


We then loop over the children of the current element. Those nodes are the predicates of the current subject. The method parsePredicate is called, each time a new element is found.
while(getReader().hasNext())
{
XMLEvent event = getReader().nextEvent();
if(event.isEndElement())
{
return descriptionURI;
}
else if(event.isStartElement())
{
parsePredicate(descriptionURI,event.asStartElement());
}
else if(event.isProcessingInstruction())
{
throw new XMLStreamException("Found Processing Instruction in RDF ???");
}
else if(event.isCharacters() &&
event.asCharacters().getData().trim().length()>0)
{
throw new XMLStreamException("Found text in RDF ??? \""+
event.asCharacters().getData()+"\""
);
}
}

parsePredicate: Parsing the predicate of the current triple


First the property attributes of the current element are scanned, and some new triples may be created. e.g:
<rdf:Description ex:fullName="Dave Beckett">
<ex:homePage rdf:resource="http://purl.org/net/dajobe/"/>
</rdf:Description>

During this process, the value of the attribute rdf:parseType is noted if it was found.
Furthermore, if there was an attribute rdf:resource, then this element is a new triple linking another resource.
<ex:homePage rdf:resource="http://purl.org/net/dajobe/"/>

If rdf:parseType="Literal" then we transform the children of the current node into a string, and a new triple is created.
if(parseType.equals("Literal"))
{
StringBuilder b= parseLiteral();
RDFEvent evt= new RDFEvent();
evt.subject=descriptionURI;
evt.predicate= predicateURI;
evt.value= b.toString();
evt.lang=lang;
evt.valueType=datatype;
found(evt);
}

If rdf:parseType="Resource", then the current node is a blank node: The rdf:Description will be omitted. A blank node is created and we call recursively parsePredicate using this blank node has the new subject.
URI blanck = createAnonymousURI();
RDFEvent evt= new RDFEvent();
evt.subject=descriptionURI;
evt.predicate= predicateURI;
evt.value=blanck;
evt.lang=lang;
evt.valueType=datatype;
found(evt);

while(getReader().hasNext())
{
XMLEvent event = getReader().nextEvent();
if(event.isStartElement())
{
parsePredicate(blanck, event.asStartElement());
}
else if(event.isEndElement())
{
return;
}
}

If rdf:parseType="Collection", The children elements give the set of subject nodes of the collection. We call recursively parseDescription for each of these nodes.
int index=0;
while(getReader().hasNext())
{
XMLEvent event = getReader().nextEvent();
if(event.isStartElement())
{
URI value= parseDescription(event.asStartElement());
RDFEvent evt= new RDFEvent();
evt.subject=descriptionURI;
evt.predicate= predicateURI;
evt.value=value;
evt.lang=lang;
evt.valueType=datatype;
evt.listIndex=(++index);

found(evt);
}
else if(event.isEndElement())
{
return;
}
}

Else this is the default rdf:parseType.
If a new element is found, then , this is the subject of a new resource (We call recursively parseDescription), else the current statement has a Literal as the object of this statement and we concatenate all the text.
StringBuilder b= new StringBuilder();
while(getReader().hasNext())
{
XMLEvent event = getReader().nextEvent();
if(event.isStartElement())
{
URI childURI=parseDescription(event.asStartElement());
RDFEvent evt= new RDFEvent();
evt.subject=descriptionURI;
evt.predicate= predicateURI;
evt.value= childURI;
found(evt);
b.setLength(0);
foundResourceAsChild=true;
}
else if(event.isCharacters())
{
b.append(event.asCharacters().getData());
}
else if(event.isEndElement())
{
if(!foundResourceAsChild)
{
RDFEvent evt= new RDFEvent();
evt.subject=descriptionURI;
evt.predicate= predicateURI;
evt.value= b.toString();
evt.lang=lang;
evt.valueType=datatype;
found(evt);
}
else
{
if(b.toString().trim().length()!=0) throw new XMLStreamException("Found bad text "+b);
}
return;
}
}

Testing


The following code parses go.rdf.gz (1744 Ko) and returns the number of statements.
long now= System.currentTimeMillis();
URL url= new URL("ftp://ftp.uniprot.org/pub/databases/uniprot/current_release/rdf/go.rdf.gz");
InputStream r= new GZIPInputStream(url.openStream());
RDFHandler h= new RDFHandler()
{
@Override
public void found(URI subject, URI predicate, Object value,
URI dataType, String lang, int index)
throws IOException {
++count;
}
};
h.parse(r);
r.close();
System.out.println("time:"+((System.currentTimeMillis()-now)/1000)+" secs count:"+count+" triples");

Result:
time:17 secs count:188391 triples



That's it.

Pierre

03 March 2009

String Challenge: My (brute-force) solution.

In this post, I present my (brute-force/quick n'dirty) solution to the recent 'String Challenge' submited by Thomas Mailund on his blog: http://www.mailund.dk/index.php/2009/03/02/a-string-algorithms-challenge/. Briefly, here is the problem:

Given an input string X, an integer k and a frequency f, report all k-mers that occur with frequency higher than f. Expect the length of X to be from a few hundred thousands to a few millions and k to be between 5 and 20.



I wrote a simple java code to solve this problem. This is not big science as I used a brute-force algorithm, but you might be interested about how the k-mers were mapped to their occurrences. Here, my code uses the java implementation of the BerkeleyDB API (http://www.oracle.com/technology/documentation/berkeley-db/je/index.html) to map the k-mers to their occurrences.

The source code is available here:http://anybody.cephb.fr/perso/lindenb/tmp/StringChallenge.java (Opps, please remove this extra (c=fin.read();), I cannot change this at this time)


The code



First the BerkeleyDB environment is initialized and we create a temporary database that will map the k-mers to the counts.

File envHome=new File(System.getProperty("java.io.tmpdir"));
EnvironmentConfig envCfg= new EnvironmentConfig();
envCfg.setAllowCreate(true);
envCfg.setReadOnly(false);
Environment env= new Environment(envHome,envCfg);

//create a first database mapping k-mers to count
DatabaseConfig cfg= new DatabaseConfig();
cfg.setAllowCreate(true);
cfg.setReadOnly(false);
cfg.setTemporary(true);
Database db= env.openDatabase(null, "kmers", cfg);
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry value = new DatabaseEntry();


The sequence is then scanned and an array of bytes of length k is filled with the characters.

FileReader fin= new FileReader(file);
byte array[]=new byte[kmer];
int c=-1;
int array_size=0;
//c=fin.read(); oopps, I should have removed this....


while((c=fin.read())!=-1)
{
if(Character.isWhitespace(c)) continue;
c=Character.toUpperCase(c);
array[array_size++]=(byte)c;

}



This array is filled, searched in the database and the count is incremented back in the BerkeleyDB. The the content of the array is shifted to the left.

key.setData(array);

int count=0;
//is this data already exists ?
if(db.get(null,key, value, null)==OperationStatus.SUCCESS)
{
count =IntegerBinding.entryToInt(value);
}

IntegerBinding.intToEntry(count+1, value);
db.put(null,key, value);

//switch to left
for(int i=1;i< kmer;++i)
{
array[i-1]=array[i];
}
array_size--;


At the end, in order to have a set of ordered results, a reverse database is created . It maps the counts to the k-mers.



//create a second database mapping count to k-mers
cfg= new DatabaseConfig();
cfg.setAllowCreate(true);
cfg.setReadOnly(false);
cfg.setTemporary(true);
cfg.setSortedDuplicates(true);
Database db2= env.openDatabase(null, "occ",cfg);
key = new DatabaseEntry();
value = new DatabaseEntry();

Cursor cursor= db.openCursor(null, null);
while(cursor.getNext(key, value,null)==OperationStatus.SUCCESS)
{
int count=IntegerBinding.entryToInt(value);
if((count/(float)total)< freq) continue;
db2.put(null, value, key);
}
cursor.close();


This second database is then scanned to print the ordered results.

while(cursor.getNext(key, value,null)==OperationStatus.SUCCESS)
{
int count=IntegerBinding.entryToInt(key);
String seq= new String(value.getData(),value.getOffset(),value.getSize());
System.out.println(seq+"\t"+count+"/"+total);
}


Compilation



javac -cp ${BERKELEY-JE-PATH}/lib/je-3.3.75.jar:. StringChallenge.java

Excecution


java -cp ${BERKELEY-JE-PATH}/lib/je-3.3.75.jar:. StringChallenge

Test with the Human chr22 (~49E6 bp)


time java -cp ${BERKELEY-JE-PATH}/lib/je-3.3.75.jar:. StringChallenge -k 8 -f 0.001 chr22.fa
AAAAAAAA 71811/49691430
TTTTTTTT 72474/49691430
NNNNNNNN 14840030/49691430

real 5m44.240s
user 5m56.186s
sys 0m3.178s

time java -cp ${BERKELEY-JE-PATH}/lib/je-3.3.75.jar:. StringChallenge -k 10 -f 0.001 chr22.fa
AAAAAAAAAA 50128/49691428
TTTTTTTTTT 50841/49691428
NNNNNNNNNN 14840010/49691428

real 8m15.152s
user 8m41.429s
sys 0m3.748s



That's it. Now I'd like to know how the 'elegant' solutions will be implemented :-)

24 February 2009

1000 human genomes ! How !#@$* do you manage 1000 genomes ??!

The 1000 genomes project (http://www.1000genomes.org/page.php) has started to release its data. The 1000 Genomes Project is an international research consortium formed to create the most detailed and medically useful picture to date of human genetic variation. The project involves sequencing the genomes of approximately 1200 people from around the world

The data are already available on the ftp site. TONS of short reads are here. For example here are the first lines of a FASTQ file ftp://ftp.1000genomes.ebi.ac.uk/data/NA12878/sequence_read/SRR001113.recal.fastq.gz.
@BI:080213_SL-XAH_0001_FC2037KAAXX:7:1:123:322
TTTCTAAACAGAAAATATTATGAAAGCTGCCATTTTTAATTCATTTT
+
AACCDCCDEDEDDFFDDDDDDCGDFEEC=>@'B@BBA-7<'###+1'
@BI:080213_SL-XAH_0001_FC2037KAAXX:7:1:114:773
TACTCAGTGTAGTGAATTCACGTCAGAGTGAGAGCTATACTTTTTAT
+
AACCDCCDEDEDDFFDDDD>DCGDDDDCEDCAB@4.A??<231%#5#
@BI:080213_SL-XAH_0001_FC2037KAAXX:7:1:121:413
GGAACTTTACAAGCTGATTTTCAAATTTATGTAGATATACCCCCTTC
+
AACCDCCDEDDDDFFDDDDDDC1>@DDC8DC<#%%#*'#
@BI:080213_SL-XAH_0001_FC2037KAAXX:7:1:115:199
TAAAAAAAAAATATTAAAGCAAGTTAGAATCTTCAACTATCTTAATT
+
AACCDCCDEDEDDFFDDDDDDCGDDDDCEDCAB@BBA??<*31'%#1
@BI:080213_SL-XAH_0001_FC2037KAAXX:7:1:121:735
CTCTATTTCCAATAGACATAAATATATTGAATATTTCCCATTACCCT
+
AACCDCCDEDE>DFFDDDDDDCGDDDDC@DCAB@BBA??<2))(1##
@BI:080213_SL-XAH_0001_FC2037KAAXX:7:1:117:705
TGCAGTGCTAATTTTATTTTTTTTAATAATTTATTTAATTTCTCCTT
+
AACCDCCDEDEDDFFDDDDDDCGDDDDCEDCAB@BBA??<2&1#(11
@BI:080213_SL-XAH_0001_FC2037KAAXX:7:1:122:436
TGATGGGCAGGAAGGAGATAGCCTTCTTCACTGTTCTCTGTTTTTCT
+
AACC=CC@ED1&:.6-8>6?D:)FD+)C%46$=@E',?,<&&%&(##
@BI:080213_SL-XAH_0001_FC2037KAAXX:7:1:119:679
TAATATCTTGTCAGTGGCAGTCGCGGAATTGTCTCTGTGGTTTTTTC
(...)


size of the file: 1.325.489.979 octets
number of lines: 36.193.608
number of short reads: 9.048.402
number of distinct short reads without any N : 8.591.003
number of short reads without any ‘N’: 6.301.673
number of distinct short reads without any ‘N’: 6.260.140

How many fastq files are there ? 8215 fastq files.
(Just curious: how do you backup this amount of data ?)

And, now, here HE comes: the USER.
The USER and his !#@$ questions.
AND HE WANTS SOME ANSWERS.


  • For an given rs number , where is it located on those 1000 genomes ?

  • Here is a new SNP in my favorite gene. Is it a true SNP? I want to see the short reads and their qualities aligned for this SNP

  • For a given SNP, what is its location on the reference sequence ? on Watson's sequence ? on celera assembly ?

  • I want to see all the snps in a given region for a given individual and his parents

  • Is this mutation (e.g. substitution) on this individual is the same on another individual (e.g. insertion) ?

  • ...



How do you store and manage this amount of information ? In a classical RDBM ?! My colleague Mario Foglio is currently looking for another alternative(s).

BIG DATA !

Pierre

21 February 2009

Genes for Translation mapped on a Hilbert Curve

In this post I describe how I mapped a list of genes involved in the Translational process on a Hilbert Curve .
This post was mostly inspired by Gareth Palidwor's recent post titled "Mapping genomes to a Hilbert Curve" see http://www.palidwor.com/blog/?p=123 (and yes, Paulo is right: , a blog can be a source of inspiration)

Wikipedia: A Hilbert curve is a continuous fractal space-filling curve first described by the German mathematician David Hilbert in 1891.



I've played with this idea and written a simple program mapping a list of genes on a Curve.

Finding the genes


First we need to get the name of genes involved in the Translational process. I've used the anonymous mysql GO server at EBI. I'm not a GO guru, so just tell me if the query below was not correct.
The position of those genes was then found using the anonymous mysql server of the UCSC. This mapping information is roughly available in the hg18.refGene table. I've restricted the search to the chromosome chr1.
At the end, my shell looks like this:
mysql -N -hmysql.ebi.ac.uk -ugo_select -pamigo -P4085 go_latest -A -e "SELECT DISTINCT
gene_product.symbol
FROM
term
INNER JOIN graph_path
ON (term.id=graph_path.term1_id)
INNER JOIN association
ON (graph_path.term2_id=association.term_id)
INNER JOIN gene_product
ON (association.gene_product_id=gene_product.id)
INNER JOIN species
ON (gene_product.species_id=species.id)
WHERE
term.name = 'translation' AND
species.ncbi_taxa_id = 9606" |\
gawk 'BEGIN {printf("select distinct name2,txStart,txEnd from refGene where chrom=\"chr1\" and name2 in (\"_\" ");}
{
printf(",\"%s\"",$1);
}
END {
printf(") order by txStart;\n");
}' |\
mysql -h genome-mysql.cse.ucsc.edu -D hg18 -u genome -A

Drawing the map


Then I mapped this list of gene on a Hilbert Curve drawn as SVG. I won't show the code here, it was mostly copied from the code provided on the wikipedia-page. The only major modification was the one used to convert a position on the genome to the curve. For those interested, I put the code here:http://code.google.com/p/lindenb/source/browse/trunk/proj/tinytools/src/org/lindenb/tinytools/HilbertSequence.java

Result


The curve is the human chromosome chr1.
The red lines are the genes.
Moving the mouse over a red line shows a balloon with the name of the gene



That's it.




'

19 February 2009

I get email

Dear Dr. Pierre Lindenbaum,

We are great fun of your blog and inspired several times by your ideas!

We, the Database Center for Life Science (DBCLS) in Japan, had been working on the standardization and integration of bioinformatics resources, and organized a BioHackathon meeting last year in Japan.

Recently, we decided to organize the 2nd hackathon that is more focused on end-user clients and mash-up of the existing services (to fully utilize the effort made by previous hackathon).

For this purpose, we think your insight is invaluable for fruitful discussions, therefore, I'd like to invite you to the BioHackathon 2009, which will be held in Tokyo and Okinawa (Southern-most island in Japan) from March 15th to 21th, 2009.

We will cover all the expenses for your flights and accommodations.

Please let me know if your schedule is available for the week.

Thanks in advance.


Wooooooooooooooooooooo !!! :-)

You know what ?


but... ;-)


12 February 2009

Au revoir Albert Barillé

Au revoir Albert Barillé (1921-2009), et merci pour tout.


11 February 2009

My tribute to Charles. Darwin Day 12 Feb 2009.


Ah, shhh...., there is a type. It's Hari, not Hary... Saperlipopette !!!

10 February 2009

A standalone XUL application translating a DNA. My notebook

In this post, I will present how I wrote a standalone XUL application: it's a simple GUI translating a DNA sequence to a proteic sequence. (Wikipedia:) XUL stands for "XML User Interface Language". It's a language developed by the Mozilla project which operates in Mozilla cross-platform applications, it relies on multiple existing web standards and technologies, including CSS, JavaScript, and DOM. Such reliance makes XUL relatively easy to learn for people with a background in web-programming and design.
A XUL standalone application has not the barreers of security of the web browsers: It can open/save/write/read a file, create a database (via sqlite/mozstorage), etc...

This post was mostly written with the help of the xulrunner tutorial (https://developer.mozilla.org/en/Getting_started_with_XULRunner).

The complete source code of this application is available at: http://code.google.com/p/lindenb/source/browse/#svn/trunk/proj/tinyxul/translate.

File hierarchy


tinyxul/
+translate/
application.ini
+chrome/
chrome.manifest
+translate
translate.xul
+defaults
+preferences/
prefs.js

  • application.ini: provides metadata that allows XULRunner to launch the application properly.
  • prefs.js: the preferences file
  • translate.xul: the xul file containing the layout of the application and, in this case, the javascript methods


translate/application.ini


This file provides metadata that allows XULRunner to launch the application properly.
[App]
Vendor=lindenb
Name=translate
Version=0.1
BuildID=20090209
ID=translation@pierre.lindenbaum.fr
[Gecko]
MinVersion=1.8


translate/defaults/preferences/prefs.js


The preference file contains among other things, the URI of the main xul window to be opened when the application starts
pref("toolkit.defaultChromeURI", "chrome://translate/content/translate.xul");


translate/chrome/translate/translate.xul


The window is defined with the XML/XUL layout. At the end, it will look like this:

The application is a <window>, it contains a <menubar>, two <textbox> (for the DNA and the proteic sequences) and a <menulist> where the user select the Genetic Code (standard, mitochondrial...)
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="main" title="Translate" width="800" height="600"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="init()"
>
<script>(...)
</script>
<toolbox flex="1">
<menubar id="sample-menubar">
<menu label="File">
<menupopup id="file-popup">
<menuitem label="New" oncommand="doNewWindow();"/>
<menuitem label="Save As..." oncommand="doSaveAs();"/>
<menuseparator/>
<menuitem label="Exit" oncommand="window.close();"/>
</menupopup>
</menu>
</menubar>
</toolbox>
<vbox flex="13">
<hbox><label control="dnaseq" value="Your DNA Sequence" label="Your DNA Sequence" /><label id="dnalength" flex="1" value="0 pb"/></hbox>
<textbox flex="6" id="dnaseq" rows="5" multiline="true" onchange="doTranslate()" oninput="doTranslate()"/>

<hbox>
<label flex="1" control="protseq" value="The Translated Sequence"/>
<label id="protlength" flex="1" value="0 AA"/>
<menulist flex="1" oncommand="currentGeneticCode=GeneticCode[selectedIndex];doTranslate();">
<menupopup id="gcpopup">
</menupopup>
</menulist>
</hbox>
<textbox flex="6" id="protseq" rows="5" multiline="true" readOnly="true"/>

</vbox>
</window>


An array of genetic codes is stored as a javascript array (BTW, thanks to Brad Chapman and PJ Davis for their suggestions):
/* via ftp://ftp.ncbi.nih.gov/entrez/misc/data/gc.prt */
var GeneticCode=[
{
name: "Standard" ,
ncbieaa : "FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG"
},
{
name: "Vertebrate Mitochondrial" ,
ncbieaa : "FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSS**VVVVAAAADDEEGGGG"
},
{
name: "Yeast Mitochondrial" ,
ncbieaa : "FFLLSSSSYY**CCWWTTTTPPPPHHQQRRRRIIMMTTTTNNKKSSRRVVVVAAAADDEEGGGG",
},
{
name: "Bacterial and Plant Plastid" ,
ncbieaa : "FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG",
}
];


Each time the DNA sequence is modified, the method doTranslate is called.
function doTranslate()
{
var ncbieaa= currentGeneticCode.ncbieaa ;
var dna=document.getElementById('dnaseq').value;
var prot="";
var i=0;
var aa="";
var dnalength=0;
var protlength=0;
while(i&lt; dna.length)
{
var c= dna.charAt(i++);
if("\n\t \r".indexOf(c)!=-1)
{
continue;
}
dnalength++;
aa+=c;
if(aa.length==3)
{
prot+=translation(ncbieaa,aa);
protlength++;
if(protlength % 50==0) { prot+="\n";}
aa="";
}
}
document.getElementById('protseq').value = prot;
document.getElementById('protlength').value = protlength+" AA";
document.getElementById('dnalength').value = dnalength+" pb";
}

And because, this is a standalone application, the user can SAVE the sequence of the protein.
function doSaveAs()
{
try {
const nsIFilePicker = Components.interfaces.nsIFilePicker;

var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window, "Save As...", nsIFilePicker.modeSave);


var rv = fp.show();
if (!(rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) ) return;
var file = fp.file;


var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);


foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);

var data=document.getElementById('protseq').value;
foStream.write(data, data.length);
foStream.close();


} catch(err){ alert(err.message);}
}

Testing the application


To launch the application, call xulrunner
xulrunner translate/application.ini

Or firefox with the app option
firefox -app translate/application.ini


I guess there should have a way to package this application in a jar/zip, but I still haven't found a way to to this.

That's it !
Pierre