diff --git a/.gitattributes b/.gitattributes
index c43dc1b497c7ac026f83f85064642ad3c6da9f0b..adde843db5e5d43a4096c8b8c44ed9d0b370bdf7 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,6 +1,7 @@
 [attr]binary -text -crlf -diff -ident
 * text ident
 *.db binary
+*.eps binary
 *.gif binary
 *.gz binary
 *.png binary
diff --git a/tutorial/Makefile b/tutorial/Makefile
index 3e5c8f05a3603febe5958047bb2c3d3e382d4bfc..466383b1502a008f586ed2dac6b6f9abfd1f5586 100644
--- a/tutorial/Makefile
+++ b/tutorial/Makefile
@@ -8,7 +8,8 @@ all:
 low_all: tutorial.html tutorial_onepage.html
 
 MADEFILES=Image.wmml Mysql.wmml ProtocolsLysKOM.wmml \
-	ProtocolsHTTP.wmml Parser.wmml Calendar.wmml
+	ProtocolsHTTP.wmml Parser.wmml Calendar.wmml \
+
 SRCFILES=tutorial.wmml $(MADEFILES)
 
 pike=pike
@@ -48,6 +49,13 @@ tutorial.html: .DUMMY $(SRCFILES)
 tutorial_onepage.html: .DUMMY  $(SRCFILES)
 	$(pike) ./wmmltohtml2 tutorial.wmml html_onepage tutorial_onepage
 
+tutorial.tex: .DUMMY $(SRCFILES)
+	$(pike) ./wmmltohtml2 tutorial.wmml latex tutorial
+
+tutorial.dvi: tutorial.tex
+	-@rm tutorial.aux
+	latex tutorial.tex
+
 tut.html: .DUMMY $(SRCFILES)
 	$(pike) ./wmmltohtml2 tutorial.wmml sitebuilder tut
 
@@ -64,7 +72,9 @@ the_image_module.html: Image.wmml the_image_module.wmml
 	$(pike) ./wmmltohtml2 the_image_module.wmml mirar_html the_image_module
 
 clean:
-	-rm -f *.html *.md illustration_cache illustration*.gif $(MADEFILES)
+	-rm -f *.html *.md illustration_cache illustration*.gif $(MADEFILES) 
+	-rm -f tutorial.dvi tutorial.tex tutorial.aux illustration*.eps
+	-rm -f tutorial.log
 
 rebuild:
 	-rm -f $(MADEFILES)
diff --git a/tutorial/Wmml.pmod b/tutorial/Wmml.pmod
index ff1f6b313c95940d81a53c8172048d5900ead0ce..207d8e1e192a88e76ffa5c4974cd9caf0f061b4f 100644
--- a/tutorial/Wmml.pmod
+++ b/tutorial/Wmml.pmod
@@ -26,6 +26,10 @@ class Trace
 }
 
 
+
+// This function verifies SGML data
+// FIXME: check for superflous tags in <table>
+// FIXME: put a cap on the <section> depth
 static private int verify_any(SGML data,
 			      Trace in,
 			      string input,
@@ -135,6 +139,18 @@ static private int verify_any(SGML data,
 
 	    break;
 
+	 case "image":
+	   if(!x->params->src &&
+	      !x->params->gif &&
+	      !x->params->xfig &&
+	      !x->params->jpeg &&
+	      !x->params->eps)
+	   {
+	     werror("Image without source near "+x->location()+"\n");
+	     werror(in->dump());
+	     i=0;
+	   }
+
 	 case "insert_added_appendices":
 	 case "ex_indent":
 	 case "ex_br":
@@ -146,7 +162,7 @@ static private int verify_any(SGML data,
 	 case "hr":
 	 case "br":
 	 case "img":
-	 case "image":
+	      
 	 case "table-of-contents":
 	 case "index":
 	    if(x->data)
@@ -175,6 +191,10 @@ int verify(SGML data, string input, string input_name)
   return verify_any(data,Trace(0,0), input, input_name);
 }
 
+
+// Generate an index
+// Might not be required for all output formats
+//
 INDEX_DATA collect_index(SGML data, void|INDEX_DATA index,void|mapping taken)
 {
   if(!index) index=([]);
@@ -267,6 +287,8 @@ INDEX group_index_by_character(INDEX i)
   return m;
 }
 
+
+// Rerserver dowrds in Pike
 string * reserved_pike =
 ({
   "array","break","case","catch","continue","default","do","else","float",
@@ -276,6 +298,8 @@ string * reserved_pike =
   "varargs","void","while"
 });
 
+
+// Reserved words in C
 string *reserved_c =
 ({
   "break","case","continue","default","do","else","float","double",
@@ -284,6 +308,9 @@ string *reserved_c =
   "void","while"
 });
 
+
+// Parce C/C++/Pike/Java and highlight
+//
 object(Tag) parse_pike_code(string x,
 	 int pos,
 	 mapping(string:string) reserved_type)
@@ -409,6 +436,8 @@ object(Tag) parse_pike_code(string x,
   return Tag("example",([]),pos,ret);
 }
 
+// All data that will be sent to the
+// output generator.
 class Wmml
 {
   SGML metadata;
@@ -417,6 +446,8 @@ class Wmml
   SGML data;
 };
 
+// Enumerators are used to give numbers to chapters,
+// sections and appendixes
 class Enumerator
 {
   string *base;
@@ -897,6 +928,8 @@ void save_image_cache();
 
 int gifnum;
 mapping gifcache=([]);
+mapping jpgcache=([]);
+mapping epscache=([]);
 mapping srccache=([]);
 
 string mkgif(mixed o,void|object alpha)
@@ -931,13 +964,42 @@ string mkgif(mixed o,void|object alpha)
   return gifname;
 }
 
+string mkeps(mixed o,float dpi)
+{
+  string g=Image.PS.encode(o, (["dpi":dpi]) );
+
+  int key=hash(g);
+
+  foreach(epscache[key]||({}),string file)
+    {
+      if(Stdio.read_file(file)==g)
+      {
+	werror("Cache hit in mkeps: "+file+"\n");
+	return file;
+      }
+    }
+
+  gifnum++;
+  string epsname="illustration"+gifnum+".eps";
+  rm(epsname);
+  werror("Writing "+epsname+".\n");
+  Stdio.write_file(epsname,g);
+
+  if(epscache[key])
+    epscache[key]+=({epsname});
+  else
+    epscache[key]=({epsname});
+
+  return epsname;
+}
+
 string mkjpeg(mixed o,void|int quality)
 {
    string g=Image.JPEG.encode(o,(["quality":quality||100]));
 
    int key=hash(g);
 
-   foreach(gifcache[key]||({}),string file)
+   foreach(jpgcache[key]||({}),string file)
    {
       if(Stdio.read_file(file)==g)
       {
@@ -952,25 +1014,28 @@ string mkjpeg(mixed o,void|int quality)
    werror("Writing "+gifname+".\n");
    Stdio.write_file(gifname,g);
 
-   if(gifcache[key])
-      gifcache[key]+=({gifname});
+   if(jpgcache[key])
+      jpgcache[key]+=({gifname});
    else
-      gifcache[key]=({gifname});
+      jpgcache[key]=({gifname});
 
    return gifname;
 }
 
-
-object render_illustration(string pike_code, mapping params, float dpi)
+//
+// Execute a small pice of pike code to generate an illustration. 
+// 
+object render_illustration(string pike_code, mapping params, float wanted_dpi)
 {
   werror("Rendering ");
   if (params->__from__) werror("["+params->__from__+"] ");
   string src=params->src;
   object img=Image.image();
 
-  if(params->dpi) dpi=(float)params->dpi;
-  if(params->scale) dpi/=(float)params->scale;
-  float scale=75.0/dpi;
+  float actual_dpi=75.0;
+  if(params->dpi) actual_dpi=(float)params->dpi;
+  if(params->scale) wanted_dpi*=(float)params->scale;
+  float scale=wanted_dpi/actual_dpi;
 
   if(params->src) 
      img=srccache[params->src]||
@@ -1002,7 +1067,7 @@ string illustration_to_gif(TAG data, float dpi)
 {
   mapping params=data->params;
   string pike_code=data->data[0];
-  string key=mkkey(params,pike_code,dpi);
+  string key=mkkey(params,pike_code,dpi,"gif");
 
   string ret=illustration_cache[key];
   if(!ret)
@@ -1025,6 +1090,35 @@ string illustration_to_gif(TAG data, float dpi)
   return ret;
 }
 
+
+string illustration_to_eps(TAG data, float dpi)
+{
+  mapping params=data->params;
+  string pike_code=data->data[0];
+  string key=mkkey(params,pike_code,dpi,"eps");
+
+  string ret=illustration_cache[key];
+  if(!ret)
+  {
+    mixed err=catch 
+    {
+       ret=mkeps(render_illustration(pike_code,params, dpi), dpi);
+       illustration_cache[key]=ret;
+       save_image_cache();
+    };
+    if (err)
+    {
+	werror("error while compiling and running\n"+pike_code+"\n");
+	if (params->__from__) 
+	   werror("from "+params->__from__+":\n");
+	werror(master()->describe_backtrace(err)+"\n");
+	return "error.eps";
+    }
+  }
+  return ret;
+  
+}
+
 array execute_contents(Tag tag)
 {
    string data=get_text(tag->data);
@@ -1111,7 +1205,8 @@ array execute_contents(Tag tag)
 	 werror("from "+tag->params->__from__+"\n");
 
       array dat=data/"\n";
-      werror("%O\n",err[1]);
+//	master()->describe_error(err);
+//      werror("%O\n",err[1]);
       foreach (reverse(err[1]),array y)
 	 if (y[0]=="inline wmml generating code" &&
 	     y[1]<10000) 
@@ -1131,6 +1226,7 @@ array execute_contents(Tag tag)
 }
 
 
+// FIXME: handle EPS input
 string image_to_gif(TAG data, float dpi)
 {
   mapping params=data->params;
@@ -1144,7 +1240,7 @@ string image_to_gif(TAG data, float dpi)
   if(params->xfig)
     params->src=params->xfig+".fig";
 
-  string key=mkkey(params,dpi);
+  string key=mkkey(params,dpi,"gif");
 
   string ret=illustration_cache[key];
   if(!ret)
@@ -1162,7 +1258,7 @@ string image_to_gif(TAG data, float dpi)
       werror("Converting ");
       Process.system("/bin/sh -c 'fig2dev -L ps "+params->src+" ___tmp.ps;echo showpage >>___tmp.ps'");
       Process.system("/bin/sh -c 'gs -q -sDEVICE=pbmraw -r225 -g2500x2500 -sOutputFile=___tmp.ppm ___tmp.ps </dev/null >/dev/null'");
-      object o=Image.PNM.decode(Stdio.read_file("___tmp.ppm"))->autocrop()->scale(1.0/3)->rotate(-90);
+      object o=Image.PNM.decode(Stdio.read_file("___tmp.ppm"))->autocrop()->scale(1.0/3); //->rotate(-90);
       o=Image.image(o->xsize()+40, o->ysize()+40, 255,255,255)->paste(o,20,20);
       rm("___tmp.ps");
       rm("___tmp.ppm");
@@ -1180,6 +1276,54 @@ string image_to_gif(TAG data, float dpi)
   return ret;
 }
 
+// FIXME: move all graphics stuff into a separate module
+string image_to_eps(TAG data, float dpi)
+{
+  mapping params=data->params;
+
+  if(params->eps)
+    return params->eps;
+
+  if(params->jpeg)
+    params->src=params->jpeg;
+
+  if(params->gif)
+    params->src=params->gif;
+
+  if(params->xfig)
+    params->src=params->xfig+".fig";
+
+  string key=mkkey(params,dpi,"eps");
+
+  string ret=illustration_cache[key];
+  if(!ret)
+  {
+    if(!params->src)
+    {
+      throw( ({"Image without source near "+data->location()+".\n",
+		 backtrace() }) );
+    }
+    string ext=reverse(params->src);
+    sscanf(ext,"%s.",ext);
+    switch(reverse(ext))
+    {
+    case "fig":
+      werror("Converting ");
+      ret="illustration"+ (++gifnum) +".eps";
+      Process.create_process( ({"fig2dev","-L","ps","-m","0.6666666666",params->src,ret }))->wait();
+      break;
+
+    default:
+      ret=mkeps(render_illustration("return src",params,dpi),dpi);
+      break;
+    }
+
+    illustration_cache[key]=ret;
+    save_image_cache();
+  }
+  return ret;
+}
+
 void save_image_cache()
 {
   rm("illustration_cache");
@@ -1187,6 +1331,8 @@ void save_image_cache()
 		   encode_value(([
 		     "gifnum":gifnum,
 		     "gifcache":gifcache,
+		     "jpgcache":gifcache,
+		     "epscache":gifcache,
 		     "illustration_cache":illustration_cache,
 		     ])));
 }
@@ -1198,6 +1344,8 @@ void create()
     mixed x=decode_value(Stdio.read_file("illustration_cache"));
     gifnum=x->gifnum;
     gifcache=x->gifcache;
+    jpgcache=x->jpgcache;
+    epscache=x->epscache;
     illustration_cache=x->illustration_cache;
   }
 }
diff --git a/tutorial/error.eps b/tutorial/error.eps
new file mode 100644
index 0000000000000000000000000000000000000000..b3fe94ba72ee3956e65116b44ab6a29460faf17b
--- /dev/null
+++ b/tutorial/error.eps
@@ -0,0 +1,98 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: error.eps
+%%Creator: fig2dev Version 3.2 Patchlevel 1
+%%CreationDate: Sat Jul  3 14:40:34 1999
+%%For: hubbe@adenin.hubbe.net (Fredrik Hubinette)
+%%Orientation: Portrait
+%%BoundingBox: 0 0 99 33
+%%Pages: 0
+%%BeginSetup
+%%EndSetup
+%%Magnification: 1.0000
+%%EndComments
+/$F2psDict 200 dict def
+$F2psDict begin
+$F2psDict /mtrx matrix put
+/col-1 {0 setgray} bind def
+/col0 {0.000 0.000 0.000 srgb} bind def
+/col1 {0.000 0.000 1.000 srgb} bind def
+/col2 {0.000 1.000 0.000 srgb} bind def
+/col3 {0.000 1.000 1.000 srgb} bind def
+/col4 {1.000 0.000 0.000 srgb} bind def
+/col5 {1.000 0.000 1.000 srgb} bind def
+/col6 {1.000 1.000 0.000 srgb} bind def
+/col7 {1.000 1.000 1.000 srgb} bind def
+/col8 {0.000 0.000 0.560 srgb} bind def
+/col9 {0.000 0.000 0.690 srgb} bind def
+/col10 {0.000 0.000 0.820 srgb} bind def
+/col11 {0.530 0.810 1.000 srgb} bind def
+/col12 {0.000 0.560 0.000 srgb} bind def
+/col13 {0.000 0.690 0.000 srgb} bind def
+/col14 {0.000 0.820 0.000 srgb} bind def
+/col15 {0.000 0.560 0.560 srgb} bind def
+/col16 {0.000 0.690 0.690 srgb} bind def
+/col17 {0.000 0.820 0.820 srgb} bind def
+/col18 {0.560 0.000 0.000 srgb} bind def
+/col19 {0.690 0.000 0.000 srgb} bind def
+/col20 {0.820 0.000 0.000 srgb} bind def
+/col21 {0.560 0.000 0.560 srgb} bind def
+/col22 {0.690 0.000 0.690 srgb} bind def
+/col23 {0.820 0.000 0.820 srgb} bind def
+/col24 {0.500 0.190 0.000 srgb} bind def
+/col25 {0.630 0.250 0.000 srgb} bind def
+/col26 {0.750 0.380 0.000 srgb} bind def
+/col27 {1.000 0.500 0.500 srgb} bind def
+/col28 {1.000 0.630 0.630 srgb} bind def
+/col29 {1.000 0.750 0.750 srgb} bind def
+/col30 {1.000 0.880 0.880 srgb} bind def
+/col31 {1.000 0.840 0.000 srgb} bind def
+
+end
+save
+-27.0 68.0 translate
+1 -1 scale
+
+/cp {closepath} bind def
+/ef {eofill} bind def
+/gr {grestore} bind def
+/gs {gsave} bind def
+/sa {save} bind def
+/rs {restore} bind def
+/l {lineto} bind def
+/m {moveto} bind def
+/rm {rmoveto} bind def
+/n {newpath} bind def
+/s {stroke} bind def
+/sh {show} bind def
+/slc {setlinecap} bind def
+/slj {setlinejoin} bind def
+/slw {setlinewidth} bind def
+/srgb {setrgbcolor} bind def
+/rot {rotate} bind def
+/sc {scale} bind def
+/sd {setdash} bind def
+/ff {findfont} bind def
+/sf {setfont} bind def
+/scf {scalefont} bind def
+/sw {stringwidth} bind def
+/tr {translate} bind def
+/tnt {dup dup currentrgbcolor
+  4 -2 roll dup 1 exch sub 3 -1 roll mul add
+  4 -2 roll dup 1 exch sub 3 -1 roll mul add
+  4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb}
+  bind def
+/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul
+  4 -2 roll mul srgb} bind def
+/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def
+/$F2psEnd {$F2psEnteredState restore end} def
+%%EndProlog
+
+$F2psBegin
+10 setmiterlimit
+n -1000 2125 m -1000 -1000 l 3096 -1000 l 3096 2125 l cp clip
+ 0.06000 0.06000 sc
+/Times-Roman ff 780.00 scf sf
+450 1125 m
+gs 1 -1 sc (Error) col4 sh gr
+$F2psEnd
+rs
diff --git a/tutorial/error.fig b/tutorial/error.fig
new file mode 100644
index 0000000000000000000000000000000000000000..ee04e535a52a22938122337a45f94b65d1976904
--- /dev/null
+++ b/tutorial/error.fig
@@ -0,0 +1,10 @@
+#FIG 3.2
+Landscape
+Center
+Inches
+Letter  
+100.00
+Single
+-2
+1200 2
+4 0 4 100 0 0 52 0.0000 4 540 1605 450 1125 Error\001
diff --git a/tutorial/html.pike b/tutorial/html.pike
index 9aa633964a2c3a67d233177f3ebf7204586da08d..772c7c637e35bc8ca54af6319540e1cddd3df4c3 100644
--- a/tutorial/html.pike
+++ b/tutorial/html.pike
@@ -578,10 +578,11 @@ SGML convert(SGML data)
 	    break;
 	
 	 case "ex_identifier":
-	 case "ex_commend":
+	 case "ex_comment":
 	    ret+=convert(data->data);
 	    continue;
 	 case "ex_string":
+	   // FIXME: this doesn't work
 	    ret+=convert(replace(data->data," ","�"));
 	    continue;
 	
diff --git a/tutorial/latex.pike b/tutorial/latex.pike
new file mode 100644
index 0000000000000000000000000000000000000000..a082b8c5ac58727f473f8eeac1e4bb712a805368
--- /dev/null
+++ b/tutorial/latex.pike
@@ -0,0 +1,526 @@
+#include "types.h"
+#if __VERSION__ >= 0.6
+import ".";
+#endif /* __VERSION__ >= 0.6 */
+inherit Stdio.File : out;
+
+object html=.html();
+
+string low_latex_quote(string text)
+{
+  return replace( text,
+		 ({"<",">",
+		     "{","}",
+		     "�","&",
+		     "�","\\",
+		     "[","]",
+		     "#","%",
+		     "$","~",
+		     "^","_",
+		     }),
+		 ({"$<$","$>$",
+		     "\\{","\\}",
+		     "$\\mu$","\\&",
+		     "\\verb+ +","$\\backslash$",
+		     "\\verb+[+","\\verb+]+",
+
+		     "\\#","\\%",
+		     "\\$","\\verb+~+",
+		     "\\verb+^+","\\_",
+		     }) );
+}
+
+string latex_quote(string text)
+{
+  return low_latex_quote( pre ? text : ((text/"\n") - ({""})) *"" );
+}
+
+
+float weighted_strlen(string tag)
+{
+  return strlen(tag)+
+    `+(0.0,@rows(([
+      'i':-0.3,
+      'm': 0.2,
+      'w': 0.2,
+      '@': 0.2,
+      ' ':-0.5,
+      '\n':-1.0,
+      ]), indices(tag)));
+}
+
+float aproximate_length(SGML data)
+{
+  float len=0.0;
+  if(!data) return 0.0;
+  foreach(data, TAG tag)
+    {
+      if(stringp(tag))
+      {
+	len+=weighted_strlen(tag);
+      }else{
+	len+=aproximate_length(tag->data);
+      }
+    }
+  return len;
+}
+
+string convert_table(TAG table)
+{
+  SGML data=table->data;
+  int rows,columns;
+  int border=(int)table->params->border;
+  array(float) column_data=allocate(100,1.0);
+
+  foreach(data, TAG tag)
+    {
+      if(stringp(tag))
+      {
+	string tmp=replace(tag,({" ","�","\t","\n"}),({"","","",""}));
+	if(strlen(tmp))
+	{
+	  werror("Warning: Skipping string %s inside table!\n",tag);
+	}
+      }else{
+	if(tag->tag != "tr")
+	{
+	  werror("Tag in table is not <tr>, it is %s! (near %s)\n",tag->tag,tag->location());
+	  continue;
+	}
+	
+	rows++;
+	int row_cells=0;
+	foreach(tag->data, TAG cell)
+	  {
+	    if(stringp(cell))
+	    {
+	      string tmp=replace(cell,({" ","�","\t","\n"}),({"","","",""}));
+	      if(strlen(tmp))
+	      {
+		werror("Warning: Skipping string %s inside table!\n",cell);
+	      }
+	    }else{
+	      switch(cell->tag)
+	      {
+		default:
+		  werror("Tag inside table row is not <td> or <th>, it is %s! (near %s)\n",cell->tag,cell->location());
+		  break;
+
+		case "anchor":
+		  // FIXME!!! Anchors should be moved outside of
+		  // the table!!
+		  break;
+
+		case "th":
+		case "td":
+		  column_data[row_cells]+=aproximate_length(cell->data);
+		  row_cells++;
+	      }
+	    }
+	  }
+	if(row_cells > columns)
+	  columns=row_cells;
+      }
+    }
+
+  column_data=column_data[..columns-1];
+  for(int e=0;e<sizeof(column_data);e++) column_data[e]+=20.0*rows;
+
+  float total_data=`+(@column_data);
+
+  string fmt=(border ? "|" : "") + ("l"+(border ? "|" : ""))*columns;
+
+  string ret="\n\n\\begin{longtable}{"+ fmt +"}\n";
+
+  if(border) ret+="\\hline \\\\\n";
+
+  foreach(data, TAG tag)
+    {
+      int c=0;
+      if(stringp(tag)) continue;
+      if(tag->tag != "tr") continue;
+      array(string) row=({});
+      foreach(tag->data, TAG cell)
+	{
+	  if(stringp(cell)) continue;
+	  if(cell->tag != "td" && cell->tag!="th") continue;
+	  row+=({
+	    "\\begin{minipage}{"+(  column_data[c] / total_data ) +" \\linewidth}\n"+
+	    convert_to_latex(cell->data)+
+	    "\\end{minipage}"
+	  });
+	  c++;
+	}
+      ret+=row*" & "+"\\\\\n";
+      if(border) ret+="\\hline\n";
+    }
+
+  ret+="\\end{longtable}\n";
+
+  return ret;
+}
+
+string convert_image_to_latex(TAG tag)
+{
+  mapping params=tag->params;
+  if(params->xfig)
+    params->src=params->xfig+".fig";
+
+  if(params->src && (params->src/".")[-1]=="fig")
+  {
+    rm("___tmp.latex");
+    werror("Converting fig->latex");
+    Process.create_process( ({ "fig2dev", "-L","latex",params->src,"___tmp.latex" }) )->wait();
+    string ret=Stdio.read_file("___tmp.latex");
+    rm("___tmp.latex");
+    return ret;
+  }else{
+    string file=Wmml.image_to_eps(tag,600.0);
+    return "\\epsfbox{"+file+"}";
+  }
+}
+
+int depth;
+int appendixes;
+int pre;
+
+constant FLAG_TABLE=1;
+constant FLAG_LIST=2;
+
+
+string convert_to_latex(SGML data, void|int flags)
+{
+  if(!data) return "";
+  string ret="";
+  foreach(data, TAG tag)
+    {
+      if(stringp(tag))
+      {
+	ret+=latex_quote(tag);
+      }
+      else if(objectp(tag))
+      {
+	switch(tag->tag)
+	{
+	  case "smallcaps":
+	    ret+="{\\sc "+convert_to_latex(tag->data)+"}";
+	    break;
+	  case "tt": ret+="{\\tt "+convert_to_latex(tag->data)+"}"; break;
+	  case "ex_keyword":
+	  case "b": ret+="{\\bf "+convert_to_latex(tag->data)+"}"; break;
+	  case "ex_meta":
+	  case "i": ret+="\\emph{"+convert_to_latex(tag->data)+"}"; break;
+	  case "sub": ret+="$^{"+convert_to_latex(tag->data)+"}$"; break;
+	  case "sup": ret+="$_{"+convert_to_latex(tag->data)+"}$"; break;
+	  case "h1":
+	    ret+="\\begin{huge} "+convert_to_latex(tag->data)+"\\end{huge}";
+	    break;
+	  case "h2":
+	    ret+="\\begin{Large} "+convert_to_latex(tag->data)+"\\end{Large}";
+	    break;
+	  case "h3":
+	    ret+=convert_to_latex(tag->data);
+	    break;
+	  case "h4":
+	    ret+="\\begin{small} "+convert_to_latex(tag->data)+"\\end{small}";
+	    break;
+	  case "h5":
+	  case "h6":
+	    ret+="\\begin{tiny} "+convert_to_latex(tag->data)+"\\end{tiny}";
+	    break;
+
+	  case "wbr":
+	    ret+="\-";
+	    break;
+
+	  case "ex_identifier":
+	  case "ex_comment":
+	    ret+=convert_to_latex(tag->data);
+	    break;
+
+	  case "man_title":
+	    // FIXME encaps?
+	    // FIXME indentation
+	    ret+="\n\n"+latex_quote(tag->params->title)+"\\\\\n "+
+	      convert_to_latex(tag->data)+
+	      "\n";
+	    break;
+
+	  case "chapter":
+	    depth++;
+	    ret+="\\cleardoublepage \\section{"+
+	      latex_quote(tag->params->title)+"}\n"+
+	      convert_to_latex(tag->data);
+	    depth--;
+	    break;
+
+	  case "appendix":
+	    if(!appendixes)
+	    {
+	      ret+="\n\\appendix\n";
+	      appendixes=1;
+	    }
+	    depth++;
+	    ret+="\\cleardoublepage \\section{"+
+	      latex_quote(tag->params->title)+"}\n"+
+	      convert_to_latex(tag->data);
+	    depth--;
+	    break;
+
+	  case "section":
+	    string tmp="";
+	    switch(depth)
+	    {
+	      default:
+	      case 2: tmp+="sub";
+	      case 1: tmp+="sub";
+	      case 0:
+	    }
+	    depth++;
+	    ret+="\\"+tmp+"section{"+latex_quote(tag->params->title)+"}"+
+	      convert_to_latex(tag->data);
+	    depth--;
+	    break;
+
+	  case "center":
+	    ret+="\\begin{center}\n"+
+	      convert_to_latex(tag->data)+
+	      "\n\\end{center}\n";
+	    break;
+
+	  case "firstpage":
+	  case "variable":
+	  case "constant":
+	  case "method":
+	  case "function":
+	  case "class":
+	  case "module":
+            // FIXME: but how?
+	    ret+=convert_to_latex(tag->data);
+	    break;
+
+	  case "pre":
+	    if(pre)
+	    {
+	      ret+=convert_to_latex(tag->data);
+	    }else{	
+	      pre=1;
+	      // FIXME: we will have to remove all tags inside <pre>
+	      ret+="{\\startcode\n\n"+
+		convert_to_latex(tag->data)+
+		"\n}\n";
+	      pre=0;
+	    }
+	    break;
+
+	  case "encaps":
+            // FIXME: Manual should not really use <encaps>
+	    ret+=convert_to_latex(tag->data);
+	    break;
+
+	  case "bloackquote":
+	  case "example":
+	  {
+	    array(string) tmp=
+	      convert_to_latex(tag->data)/"\\\\";
+	    for(int e=0;e<sizeof(tmp);e++)
+	      tmp[e]="\\indent "+tmp[e];
+	    ret+=tmp*"\\\\";
+	    break;
+	  }
+
+	  case "ex_string":
+	    // Make all spaces nonbreakable
+	    ret+=convert_to_latex(tag->data);
+	    break;
+
+	  case "dl":
+	    ret+="\\begin{list}{}{} \\item "+
+	      convert_to_latex(tag->data)+
+	      "\n\\end{list}\n\n";
+	    break;
+	    
+	  case "dt": ret+="\n\\item "; break;
+	  case "dd": ret+="\n\\item \\verb+  +"; break;
+
+	  case "ex_indent":
+	    ret+="\\verb+  +";
+	    break;
+	  case "ex_br":
+	  case "br":
+	    int e=strlen(ret)-1;
+	    while(e>=0 && ret[e]==' ') e--;
+	    if(e<0 || ret[e]=='\n')
+	      ret=ret[..e]+"\n";
+	    else
+	      ret+="\\\\";
+	    break;
+
+	  case "p":
+	    ret+="\n\n"+convert_to_latex(tag->data)+"\n\n";
+	    break;
+
+	  case "hr":
+	    if(tag->params->newline)
+	      ret+="\\pagebreak\n";
+	    else
+	      ret+="\n\n\\begin{tabular}{p{\\linewidth}} \\\\ \\hline \\end{tabular}\n\n";
+	    break;
+
+	  case "table":
+	    ret+=convert_table(tag);
+	    break;
+
+	  case "a":
+	    if(tag->params->href)
+	    {
+	      string href=tag->params->href;
+	      if((lower_case(href)/":")[0] == "mailto")
+	      {
+		ret+=convert_to_latex(tag->data)+
+		  latex_quote(" <"+(href/":")[1..]*":"+">");
+	      }else{
+		ret+=
+		  convert_to_latex(tag->data)+
+		  "* \\marginpar{"+
+		  replace(latex_quote(href),
+			  ({".","/",":"}),
+			  ({
+			    ".\\discretionary{}{}{}",
+			    "/\\discretionary{}{}{}",
+			    ":\\discretionary{}{}{}",
+			  }))+
+		  "}";
+	      }
+	    }else{
+	      ret+=convert_to_latex(tag->data);
+	    }
+	    break;
+
+	  case "link":
+	    // FIXME
+//	    if(tag->params->to)
+//	    {
+//	      ret+=
+//		convert_to_latex(tag->data)+
+//		"\\marginpar{See "+latex_quote(tag->params->to)+"}";
+//	    }else{
+	      ret+=convert_to_latex(tag->data);
+//	    }
+	    break;
+
+
+	  case "ul":
+	    ret+="\\begin{itemize} "+
+	      convert_to_latex(tag->data,FLAG_LIST)+
+	      "\\end{itemize} ";
+	    break;
+
+	  case "li":  // FIXME??
+	    if(flags & FLAG_LIST)
+	      ret+="\\item ";
+	    else
+	      ; // FIXME, insert filled circle
+	    break;
+
+
+	  case "ol":
+	    ret+="\\begin{enumerate} "+
+	      convert_to_latex(tag->data,FLAG_LIST)+
+	      "\\end{enumerate} ";
+	    break;
+
+	  case "anchor":
+//          FIXME labels causes tex stack overflow!!
+//	    ret+="\\label{"+latex_quote(tag->params->name)+"}";
+	    ret+=convert_to_latex(tag->data);
+	    break;
+
+	  case "ref":
+	    // FIXME: must find out what type of object we are
+	    // referencing!!
+	    ret+="\\ref{"+tag->params->to+"}";
+	    break;
+
+
+	  case "arguments":
+	  case "aargdesc":
+	  case "adesc":
+	    // FIXME FIXME FIXME
+	    break;
+
+	  case "aarg":
+	    ret+="{\\tt "+convert_to_latex(tag->data)+"}\\\\";
+	    break;
+
+	  case "data_description":
+	    ret+=convert_to_latex( html->data_description(tag->params,
+							  tag->pos,
+							  tag->data,
+							  tag) );
+	    break;
+
+// #endif
+
+	  case "exercise":
+	  case "exercises":
+	    // FIXME: 
+	    ret+=convert_to_latex(tag->data);
+	    break;
+
+	  case "image":
+	    ret+="\\epsfbox{"+Wmml.image_to_eps(tag,300.0)+"}";
+	    break;
+
+	  case "illustration":
+	    ret+="\\epsfbox{"+Wmml.illustration_to_eps(tag,300.0)+"}";
+	    break;
+
+	  default:
+	    werror("Latex: unknown tag <%s> (near %s)\n",tag->tag,tag->location());
+
+	    if(tag->data)
+	      ret+=convert_to_latex(tag->data);
+	}
+	    
+      }
+      else
+      {
+	throw(({"Tag or contents has illegal type: "+sprintf("%O\n",tag),
+		  backtrace()}));
+      }
+    }
+  return ret;
+}
+
+
+void output(string base, WMML data)
+{
+  string x=convert_to_latex(data->data);
+
+  x=replace(x,
+	    "\\verb+  +\\verb+  +",
+	    "\\verb+    +"
+	    );
+
+  x=#"
+\\documentclass[twoside,a4paper]{book}
+\\usepackage{isolatin1}
+\\usepackage{latexsym}  % For $\Box$
+\\usepackage{amsmath}
+\\usepackage{longtable}
+\\usepackage{epsfig}
+\\begin{document}
+\\author{html2latex}\n
+\\setlength{\\unitlength}{1mm}\n
+
+{\\catcode`\\^^20=\\active\\catcode`\\^^0d=\\active%
+\\global\\def\\startcode{\\catcode`\\^^20=\\active\\def^^20{\\hbox{\\ }}%
+\\catcode`\\^^0d=\\active\\def^^0d{\\hskip0pt\\par\\noindent}%
+\\parskip=1pt\\tt}}
+"+
+    x+
+    "\\end{document}\n";
+  rm(base+".tex");
+  Stdio.write_file(base+".tex",x);
+}