From fee798eda451d56fc8be6c478243d4f8f8cd1880 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fredrik=20H=C3=BCbinette=20=28Hubbe=29?= <hubbe@hubbe.net>
Date: Mon, 25 Aug 1997 15:57:35 -0700
Subject: [PATCH] new files (new wmml->html converter)

Rev: tutorial/Html.pmod:1.1
Rev: tutorial/Sgml.pmod:1.1
Rev: tutorial/Wmml.pmod:1.1
Rev: tutorial/html.pike:1.1
Rev: tutorial/html_onepage.pike:1.1
Rev: tutorial/types.h:1.1
Rev: tutorial/wmmltohtml2:1.1
---
 tutorial/Html.pmod         |  62 +++
 tutorial/Sgml.pmod         | 190 +++++++++
 tutorial/Wmml.pmod         | 673 ++++++++++++++++++++++++++++++++
 tutorial/html.pike         | 775 +++++++++++++++++++++++++++++++++++++
 tutorial/html_onepage.pike |   4 +
 tutorial/types.h           |   4 +
 tutorial/wmmltohtml2       |  29 ++
 7 files changed, 1737 insertions(+)
 create mode 100644 tutorial/Html.pmod
 create mode 100644 tutorial/Sgml.pmod
 create mode 100644 tutorial/Wmml.pmod
 create mode 100644 tutorial/html.pike
 create mode 100644 tutorial/html_onepage.pike
 create mode 100644 tutorial/types.h
 create mode 100755 tutorial/wmmltohtml2

diff --git a/tutorial/Html.pmod b/tutorial/Html.pmod
new file mode 100644
index 0000000000..dbc7f62417
--- /dev/null
+++ b/tutorial/Html.pmod
@@ -0,0 +1,62 @@
+string *quote_from;
+string *quote_to;
+string *unquote_from;
+string *unquote_to;
+
+void create()
+{
+  quote_from=quote_to=unquote_from=unquote_to=({});
+  for(int e=0;e<256;e++)
+  {
+    switch(e)
+    {
+    case 'a'..'z':
+    case 'A'..'Z':
+    case '0'..'9':
+    case 'å': case 'ä': case 'ö':
+    case 'Å': case 'Ä': case 'Ö':
+    case 'ü': case 'Ü':
+    case '!':
+    case '#':
+    case '$':
+    case '&':
+    case '/':
+    case '(':
+    case ')':
+    case '=':
+    case '-':
+    case '_':
+    case '+':
+    case '?':
+    case '~':
+    case '*':
+    case ',':
+    case '.':
+    case ';':
+    case ':':
+      break;
+
+    default:
+      quote_from+=({sprintf("%c",e)});
+      quote_to+=({sprintf("%%%02x",e)});
+    }
+    unquote_from+=({sprintf("%%%02x",e)});
+    unquote_to+=({sprintf("%c",e)});
+  }
+}
+
+string quote_param(string s) { return replace(s,quote_from,quote_to); }
+string unquote_param(string s) { return replace(s,unquote_from,unquote_to); }
+
+string mktag(string tag, mapping params)
+{
+  string ret="<"+tag;
+  foreach(indices(params),string i)
+  {
+    ret+=" "+quote_param(i);
+
+    if(stringp(params[i]))
+      ret+="='"+quote_param(params[i])+"'";
+  }
+  return ret+">";
+}
diff --git a/tutorial/Sgml.pmod b/tutorial/Sgml.pmod
new file mode 100644
index 0000000000..4694c9df56
--- /dev/null
+++ b/tutorial/Sgml.pmod
@@ -0,0 +1,190 @@
+string *from=({"&nbsp;","&amp;","&lt;","&gt;"});
+string *to=({" ","&","<",">"});
+
+string unquote(string x) { return replace(x,from,to); }
+string quote(string x) { return replace(x,to,from); }
+
+class Tag
+{
+  string tag;
+  int pos;
+  mapping(string:mixed) params=([]);
+//  array(Tag) data;
+  array(object) data;
+
+  varargs void create(string t, mapping p, int po, array(object) d)
+  {
+    tag=t; pos=po; params=p||([]); data=d;
+  }
+};
+
+#define TAG object(Tag)|string
+#define SGML array(TAG)
+
+SGML lex(string data)
+{
+  mixed foo=data/"<";
+  SGML ret=({ unquote(foo[0]) });
+  int pos=strlen(foo[0]);
+  for(int e=1;e<sizeof(foo);e++)
+  {
+    string tag;
+    string s=foo[e];
+    pos++;
+
+    if(s[0..2]=="!--")
+    {
+      pos+=strlen(foo[e]);
+      while(sscanf(s,"%*s-->%s",s)!=2)
+      {
+	e++;
+	s+="<"+foo[e];
+	pos+=strlen(foo[e])+1;
+      }
+      ret[-1]+=unquote(s);
+      continue;
+    }
+    
+    if(sscanf(s,"%[^ \t\n\r>]%s",tag,s)!=2)
+      werror(sprintf("Missing end > (around pos %d)\n",pos));
+
+    tag=lower_case(tag);
+    mapping params=([]);
+
+    while(1)
+    {
+      sscanf(s,"%*[ \t\r\n]%s",s);
+      if(!strlen(s))
+      {
+	write(sprintf("Missing end > (around pos %d)\n",pos));
+	break;
+      }
+      if(s[0]=='>')
+      {
+	s=s[1..];
+	break;
+      }
+
+      if(sscanf(s,"%[^ \t\r\n>=]%s",string key,s) && strlen(key))
+      {
+	key=lower_case(key);
+	if(s[0]=='=')
+	{
+	  string val;
+	  switch(s[1])
+	  {
+	  case '\'':
+	    while(sscanf(s,"='%s'%s",val,s)!=2)
+	    {
+	      e++;
+	      s+="<"+foo[e];
+	      pos+=strlen(foo[e])+1;
+	    }
+	    break;
+	  case '\"':
+	    while(sscanf(s,"=\"%s\"%s",val,s)!=2)
+	    {
+	      e++;
+	      s+="<"+foo[e];
+	      pos+=strlen(foo[e])+1;
+	    }
+	    break;
+	  default:
+	    sscanf(s,"=%[^ \t\r\n>]%s",val,s);
+	    break;
+	  }
+	  if(!val)
+	  {
+	    werror("Missing end quote parameter\n"); 
+	  }
+	  params[key]=val;
+	}else{
+	  params[key]=1;
+	}
+      }
+    }
+
+    ret+=({ Tag(tag,params,pos), unquote(s) });
+    pos+=sizeof(foo[e]);
+  }
+
+  return ret;
+}
+
+
+SGML group(SGML data)
+{
+  SGML ret=({});
+  foreach(data,TAG foo)
+  {
+    if(objectp(foo))
+    {
+      if(strlen(foo->tag) && foo->tag[0]=='/')
+      {
+	string tag=foo->tag[1..];
+	for(int d=sizeof(ret)-1;d>=0;d--)
+	{
+	  if(objectp(ret[d]) && !ret[d]->data && ret[d]->tag==tag)
+	  {
+	    ret[d]->data=ret[d+1..];
+	    ret=ret[..d];
+	    break;
+	  }
+	}
+	if(d>=0) continue;
+      }
+    }
+    ret+=({foo});
+  }
+  return ret;
+}
+
+
+string generate(SGML data)
+{
+  string ret="";
+  foreach(data, TAG foo)
+    {
+      if(stringp(foo))
+      {
+	ret+=quote(foo);
+      }else{
+	ret+="<"+foo->tag;
+	foreach(indices(foo->params), string name)
+	  ret+=" "+name+"="+foo->params[name];
+
+	ret+=">";
+	if(foo->data)
+	{
+	  ret+=generate(foo->data);
+	  ret+="</"+foo->tag+">";
+	}
+      }
+    }
+
+  return ret;
+}
+
+SGML copy(SGML data)
+{
+  if(!data) return 0;
+  SGML ret=({});
+  foreach(data,TAG t)
+    {
+      if(stringp(t))
+      {
+	ret+=({t});
+      }else{
+	ret+=({Tag(t->tag,t->params+([]),t->pos,copy(t->data))});
+      }
+    }
+  return ret;
+}
+
+
+#ifdef TEST
+int main()
+{
+  write(sprintf("%O\n",group(lex(Stdio.read_file("tutorial.wmml")))));
+}
+#endif
diff --git a/tutorial/Wmml.pmod b/tutorial/Wmml.pmod
new file mode 100644
index 0000000000..58254fe7c7
--- /dev/null
+++ b/tutorial/Wmml.pmod
@@ -0,0 +1,673 @@
+#include "types.h"
+
+static private int verify_any(SGML data, string in)
+{
+  int i=1;
+  foreach(data,mixed x)
+  {
+    if(objectp(x))
+    {
+      if(strlen(x->tag) && x->tag[0]=='/')
+      {
+	werror("Unmatched "+x->tag+" near pos "+x->pos+"\n");
+	werror(in);
+	i=0;
+	continue;
+      }
+      switch(x->tag)
+      {
+      default:
+	werror("Unknown tag "+x->tag+" near pos "+x->pos+".\n");
+	werror(in);
+	i=0;
+	break;
+
+      case "font":
+      case "firstpage":
+      case "preface":
+      case "introduction":
+
+      case "chapter":
+      case "appendix":
+      case "i":
+      case "b":
+      case "a":
+      case "anchor":
+      case "tt":
+      case "pre":
+      case "tr":
+      case "td":
+      case "table":
+      case "box":
+      case "h1":
+      case "h2":
+      case "h3":
+      case "dl":
+      case "ul":
+      case "section":
+      case "center":
+      case "ol":
+      case "encaps":
+      case "th":
+      case "illustration":
+      case "strong":
+      case "link":
+
+      case "ex_identifier":
+      case "ex_keyword":
+      case "ex_string":
+      case "ex_comment":
+      case "example":
+	if(!x->data)
+	{
+	  werror("Tag "+x->tag+" not closed near pos "+x->pos+".\n");
+	  werror(in);
+	  i=0;
+	}
+
+	break;
+
+      case "ex_indent":
+      case "ex_br":
+      case "dt":
+      case "dd":
+      case "li":
+      case "ref":
+      case "hr":
+      case "br":
+      case "img":
+      case "image":
+      case "table-of-contents":
+      case "index":
+	if(x->data)
+	{
+	  werror("Tag "+x->tag+" should not be closed near pos "+x->pos+"\n");
+	  werror(in);
+	  i=0;
+	}
+      case "p":
+      }
+
+      if(x->data)
+	if(!verify_any(x->data,"  In tag "+x->tag+" near pos "+x->pos+"\n"+in))
+	  i=0;
+    }
+  }
+  return i;
+}
+
+int verify(SGML data)
+{
+  return verify_any(data,"");
+}
+
+int islink(string tag)
+{
+  switch(tag)
+  {
+  case "anchor":
+  case "chapter":
+  case "preface":
+  case "introduction":
+  case "section":
+  case "table":
+  case "appendix":
+  case "image":
+  case "illustration":
+    return 1;
+  }
+}
+
+INDEX_DATA collect_index(SGML data, void|INDEX_DATA index,void|mapping taken)
+{
+  if(!index) index=([]);
+  if(!taken) taken=([]);
+
+  foreach(data,TAG data)
+  {
+    if(objectp(data))
+    {
+      if(islink(data->tag))
+      {
+	if(string real_name=data->params->name)
+	{
+	  string new_name=real_name;
+	  
+	  if(taken[new_name])
+	  {
+	    int n=2;
+	    werror("Warning, duplicate "+real_name+" near pos "+data->pos+".\n");
+	    while(taken[new_name+"_"+n]) n++;
+	    new_name+="_"+n;
+	  }
+	  taken[new_name]++;
+	  data->params->name=new_name;
+	  
+	  if(index[real_name])
+	  {
+	    index[real_name]+=({new_name});
+	  }else{
+	    index[real_name]=({new_name});
+	  }
+	}
+      }
+      if(data->data)
+	collect_index(data->data,index,taken);
+    }
+  }
+  return index;
+}
+
+INDEX newind;
+INDEX_DATA index;
+
+static private void build_index(string from, string fullname)
+{
+  mapping m=newind;
+
+  if(string *to=index[fullname])
+  {
+    foreach(from/".",string tmp)
+      {
+	if(mapping m2=m[tmp])
+	  m=m2;
+	else
+	  m=m[tmp]=([]);
+      }
+    if(!m[0]) m[0]=([]);
+    m[0][fullname]=to;
+  }
+}
+
+INDEX group_index(INDEX_DATA i)
+{
+  newind=([]);
+  index=i;
+  foreach(indices(index),string real_name)
+  {
+    string *to=index[real_name];
+    build_index(real_name,real_name);
+    string *from=real_name/".";
+    for(int e=1;e<sizeof(from);e++)
+      build_index(from[e], from[..e]*".");
+  }
+  index=0;
+  return newind;
+}
+
+INDEX group_index_by_character(INDEX i)
+{
+  mapping m=([]);
+  foreach(indices(i),string key)
+    {
+      int c;
+      sscanf(lower_case(key),"%*[_ ]%c",c);
+      string char=upper_case(sprintf("%c",c));
+//      werror(char +" : "+key+"\n");
+      if(!m[char]) m[char]=([]);
+      m[char][key]=i[key];
+    }
+  return m;
+}
+
+multiset reserved_pike =
+(<
+  "array","break","case","catch","continue","default","do","else","float",
+  "for","foreach","function","gauge","if","inherit","inline","int","lambda",
+  "mapping","mixed","multiset","nomask","object","predef","private","program",
+  "protected","public","return","sscanf","static","string","switch","typeof",
+  "varargs","void","while"
+>);
+
+multiset reserved_c =
+(<
+  "break","case","continue","default","do","else","float","double",
+  "for","if","int","char","short","unsigned","long",
+  "public","return","static","switch",
+  "void","while"
+>);
+
+object(Sgml.Tag) parse_pike_code(string x, int pos, multiset(string) reserved)
+{
+  int p,e;
+  SGML ret=({ "" });
+
+//  werror("'"+x+"'");
+  /* Strip leading newlines */
+  while(e<strlen(x) && x[e]=='\n') e++;
+
+//  werror("e="+e+"\n");
+  int tabindented;
+  if(x[e]=='\t')
+  {
+    tabindented=1;
+    e++;
+  }
+
+//  werror("tabindented="+tabindented+"\n");
+
+  for(;e<strlen(x);e++)
+  {
+    switch(x[e])
+    {
+    case '_':
+    case 'a'..'z':
+    case 'A'..'Z':
+    {
+      p=e;
+      
+      while(1)
+      {
+	switch(x[++e])
+	{
+	case '_':
+	case 'a'..'z':
+	case 'A'..'Z':
+	case '0'..'9':
+	  continue;
+	}
+	break;
+      }
+      
+      string id=x[p..--e];
+      if(reserved[id])
+	ret+=({ Sgml.Tag("ex_keyword",([]), pos+e, ({ id }) ) });
+      else
+	ret+=({ Sgml.Tag("ex_identifier",([]), pos+e, ({ id }) ) });
+      break;
+    }
+    
+    
+    case '\'':
+      p=e;
+      while(x[++e]!='\'')
+	if(x[e]=='\\')
+	  e++;
+      ret+=({ Sgml.Tag("ex_string",([]), pos+e, ({ x[p..e]}) ) }); 
+      break;
+      
+    case '"':
+      p=e;
+      while(x[++e]!='"')
+	if(x[e]=='\\')
+	  e++;
+      
+      ret+=({ Sgml.Tag("ex_string",([]), pos+e, ({ x[p..e] }) ) });
+      break;
+
+    case '\n':
+      ret+=({ Sgml.Tag("ex_br", ([]), pos+e) });
+
+      if(tabindented)
+      {
+	for(int y=1;y<9;y++)
+	{
+	  switch(x[e+y..e+y])
+	  {
+	  case " ": continue;
+	  case "\t": e+=y;
+	  default:
+	  }
+	  break;
+	}
+      }
+
+      while(x[e+1..e+1]=="\t")
+      {
+	ret+=({
+	  Sgml.Tag("ex_indent", ([]), pos+e),
+	  Sgml.Tag("ex_indent", ([]), pos+e),
+	  Sgml.Tag("ex_indent", ([]), pos+e),
+	  Sgml.Tag("ex_indent", ([]), pos+e),
+	    });
+	e++;
+      }
+
+      while(x[e+1..e+2]=="  ")
+      {
+	ret+=({ Sgml.Tag("ex_indent", ([]), pos+e) });
+	e+=2;
+      }
+      break;
+
+    case '/':
+      if(x[e+1..e+1]=="/")
+      {
+	p=e++;
+	while(x[e]!='\n') e++;
+	e--;
+	ret+=({ Sgml.Tag("ex_comment",([]), pos+e, ({ x[p..e]}) ) }); 
+	break;
+      }
+	
+      if(x[e+1..e+1]=="*")
+      {
+	p=e++;
+	while(x[e..e+1]!="*/") e++;
+	e++;
+	ret+=({ Sgml.Tag("ex_comment",([]), pos+e, ({ x[p..e]}) ) }); 
+	break;
+      }
+      
+    default:
+      ret[-1]+=x[e..e];
+      continue;
+    }
+    ret+=({ "" });
+  }
+
+  return Sgml.Tag("example",([]),pos,ret);
+}
+
+SGML handle_include(SGML data)
+{
+  if(!data) return 0;
+  for(int e=0;e<sizeof(data);e++)
+  {
+    if(!stringp(data[e]))
+    {
+      switch(data[e]->tag)
+      {
+      case "include":
+	data=
+	  data[..e-1]+
+	  Sgml.group(Sgml.lex(Stdio.read_file(data[e]->params->file)))+
+	  data[e+1..];
+	e--;
+	continue;
+
+      case "example":
+	switch(data[e]->params->language)
+	{
+	case "pike":
+	  data[e]=parse_pike_code(data[e]->data[0],
+				  data[e]->pos,
+				  reserved_pike);
+	  break;
+
+	case "c":
+	  data[e]=parse_pike_code(data[e]->data[0],
+				  data[e]->pos,
+				  reserved_c);
+	  break;
+	}
+      }
+      data[e]->data=handle_include(data[e]->data);
+    }
+  }
+  return data;
+}
+
+void save_image_cache();
+
+int gifnum;
+mapping gifcache=([]);
+
+string mkgif(object o)
+{
+  string g=o->togif();
+  int key=hash(g);
+
+  foreach(gifcache[key]||({}),string file)
+    {
+      if(Stdio.read_file(file)==g)
+      {
+	werror("Cache hit in mkgif: "+file+"\n");
+	return file;
+      }
+    }
+
+  gifnum++;
+  string gifname="illustration"+gifnum+".gif";
+  rm(gifname);
+  werror("Writing "+gifname+".\n");
+  Stdio.write_file(gifname,g);
+
+  if(gifcache[key])
+    gifcache[key]+=({gifname});
+  else
+    gifcache[key]=({gifname});
+
+  return gifname;
+}
+
+
+object render_illustration(string pike_code, mapping params, float dpi)
+{
+  werror("Rendering ");
+  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;
+
+  if(params->src) img=img->fromppm(Process.popen("anytopnm 2>/dev/null "+src));
+  if(scale!=1.0) img=img->scale(scale);
+  return compile_string("object `()(object src){ "+pike_code+" ; }")()(img);
+}
+
+private static string mkkey(mapping params, mixed ... other)
+{
+  params+=([]);
+  m_delete(params,"align");
+  m_delete(params,"alt");
+  if(params->src)
+    if(mixed x=file_stat(params->src))
+      params->mtime=(string)(x[3]);
+  string *keys=indices(params);
+  string *values=values(params);
+  sort(keys,values);
+
+  return encode_value( ({keys,values,other}) );
+}
+
+mapping illustration_cache=([]);
+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 ret=illustration_cache[key];
+  if(!ret)
+  {
+    ret=mkgif(render_illustration(pike_code,params, dpi));
+    illustration_cache[key]=ret;
+    save_image_cache();
+  }
+  return ret;
+}
+
+string image_to_gif(TAG data, float dpi)
+{
+  mapping params=data->params;
+  if(params->xfig)
+    params->src=params->xfig+".fig";
+
+  string key=mkkey(params,dpi);
+
+  string ret=illustration_cache[key];
+  if(!ret)
+  {
+    if(!params->src)
+    {
+      werror("Image without source near pos "+data->pos+".\n");
+      return "";
+    }
+    string ext=reverse(params->src);
+    sscanf(ext,"%s.",ext);
+    switch(reverse(ext))
+    {
+    case "fig":
+      werror("Converting ");
+      Process.system("fig2dev -L ps "+params->src+" ___tmp.ps;echo showpage >>___tmp.ps");
+      Process.system("gs -q -sDEVICE=pbmraw -r225 -g2500x2500 -sOutputFile=___tmp.ppm ___tmp.ps </dev/null >/dev/null");
+      object o=Image.image()->fromppm(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");
+      ret=mkgif(o);
+      break;
+
+    default:
+      ret=mkgif(render_illustration("return src",params,dpi));
+      break;
+    }
+
+    illustration_cache[key]=ret;
+    save_image_cache();
+  }
+  return ret;
+}
+
+void save_image_cache()
+{
+  rm("illustration_cache");
+  Stdio.write_file("illustration_cache",
+		   encode_value(([
+		     "gifnum":gifnum,
+		     "gifcache":gifcache,
+		     "illustration_cache":illustration_cache,
+		     ])));
+}
+
+void create()
+{
+  if(file_stat("illustration_cache"))
+  {
+    mixed x=decode_value(Stdio.read_file("illustration_cache"));
+    gifnum=x->gifnum;
+    gifcache=x->gifcache;
+    illustration_cache=x->illustration_cache;
+  }
+}
+
+int chapters;
+int appendices;
+
+static private SGML low_collect_toc(mixed *data,
+				    string prefix,
+				    int *current)
+{
+  SGML ret=({});
+
+  foreach(data, TAG t)
+    {
+      if(objectp(t))
+      {
+	switch(t->tag)
+	{
+	case "section":
+	  t->params->number=prefix+(string)current[-1];
+	  
+	  ret+=({
+	    Sgml.Tag("section_toc",
+		     t->params,
+		     t->pos,
+		     low_collect_toc(t->data,
+				 t->params->number+".",
+				 current+({1})))
+	      });	      ;
+	  current[-1]++;
+	  break;
+
+	case "chapter":
+	  if(current)
+	    werror("Chapter inside chapter/appendix near "+t->pos+".\n");
+	  t->params->number=(string)chapters;
+
+	  ret+=({
+	    Sgml.Tag("chapter_toc",
+		     t->params,
+		     t->pos,
+		     low_collect_toc(t->data,
+				 t->params->number+".",
+				 ({1})))
+	      });
+	  chapters++;
+	  break;
+	  
+	case "appendix":
+	  if(current)
+	    werror("Appendix inside chapter/appendix near "+t->pos+".\n");
+	  
+	  t->params->number=sprintf("%c",appendices);
+	  
+	  ret+=({
+	    Sgml.Tag("appendix_toc",
+		     t->params,
+		     t->pos,
+		     low_collect_toc(t->data,
+				 t->params->number+".",
+				 ({1}))),
+	      });
+	  appendices++;
+	  break;
+
+	case "preface":
+	  if(current)
+	    werror("Preface inside chapter/appendix near "+t->pos+".\n");
+
+	  t->params->number="preface";
+	  
+	  ret+=({
+	    Sgml.Tag("preface_toc",
+		     t->params,
+		     t->pos,
+		     low_collect_toc(t->data,
+				 t->params->number+".",
+				 ({1}))),
+	      });
+	  break;
+
+	case "introduction":
+	  if(current)
+	    werror("Introduction inside chapter/appendix near "+t->pos+".\n");
+
+	  t->params->number="introduction";
+	  
+	  ret+=({
+	    Sgml.Tag("introduction_toc",
+		     t->params,
+		     t->pos,
+		     low_collect_toc(t->data,
+				 t->params->number+".",
+				 ({1}))),
+	      });
+	  break;
+
+	case "index":
+	case "table-of-contents":
+	  t->params->number=t->tag;
+	  ret+=({
+	    Sgml.Tag(t->tag+"_toc",
+		     t->params,
+		     t->pos)
+	      });
+	  break;
+
+
+	default:
+	  if(t->data)
+	    ret+=low_collect_toc(t->data,prefix,current);
+	}
+      }
+    }
+  return ret;
+}
+
+SGML sort_toc(SGML toc)
+{
+  // Assume correct order
+  return toc;
+}
+
+SGML collect_toc(SGML data)
+{
+  SGML toc;
+  chapters=1;
+  appendices='A';
+  toc=low_collect_toc(data,"",0);
+  return sort_toc(toc);
+}
diff --git a/tutorial/html.pike b/tutorial/html.pike
new file mode 100644
index 0000000000..1e8ea49cf9
--- /dev/null
+++ b/tutorial/html.pike
@@ -0,0 +1,775 @@
+#include "types.h"
+inherit Stdio.File : out;
+
+SGML html_toc;
+SGML html_index;
+mapping(string:SGML) sections=([]);
+mapping(string:string) link_to_page=([]);
+string basename;
+
+string mkfilename(string section)
+{
+  if(section=="") return basename+".html";
+  return basename+"_"+section+".html";
+}
+
+
+TAG mkimgtag(string file, mapping params)
+{
+  mapping p=([]);
+  p->src=file;
+  if(params->align) p->align=params->align;
+  if(params->alt) p->align=params->alt;
+  return Sgml.Tag("img",p,0);
+}
+
+string *srt(string *x)
+{
+  string *y=allocate(sizeof(x));
+  for(int e=0;e<sizeof(y);e++)
+  {
+    y[e]=lower_case(x[e]);
+    sscanf(y[e],"%*[ ,./_]%s",y[e]);
+  }
+  sort(y,x);
+  return x;
+}
+
+void add_target_to_links(SGML foo, string target)
+{
+  foreach(foo, TAG t)
+    {
+      if(objectp(t))
+      {
+	switch(t->tag)
+	{
+	case "link":
+	case "ref":
+	case "a":
+	  if(!t->params->target)
+	    t->params->target=target;
+	}
+	
+	if(t->data)
+	  add_target_to_links(t->data,target);
+      }
+    }
+}
+
+varargs SGML low_toc_to_wmml(SGML toc)
+{
+  int app;
+  SGML ret=({});
+
+  foreach(toc, TAG t)
+    {
+      string name;
+      string link;
+      string title;
+
+      switch(t->tag)
+      {
+      case "chapters_toc":
+	ret+=low_toc_to_wmml(t->data);
+	continue;
+
+      case "index_toc":
+      case "table-of-contents_toc":
+      case "preface_toc":
+      case "introduction_toc":
+	name=0;
+	title=t->params->title;
+	sscanf(t->tag,"%s_toc",link);
+	break;
+
+      case "chapter_toc":
+      case "section_toc":
+	name=t->params->number;
+	link=t->params->number;
+	title=t->params->title;
+	switch( (name/".")[0] )
+	{
+	case "introduction":
+	case "preface":
+	  name="";
+	  break;
+	}
+	break;
+	
+      case "appendices_toc":
+	name=0;
+	link=0;
+	title="Appendices";
+	break;
+	
+      case "appendix_toc":
+	name="Appendix "+t->params->number;
+	link=t->params->number;
+	title=t->params->title;
+	break;
+
+      default:
+	werror("Error in generated TOC structure.\n");
+      }
+      
+      mixed tmp=({ " "+title });
+      if(name)
+	tmp=({ Sgml.Tag("b",([]),t->pos,({name})) }) + tmp;
+
+      if(link)
+	tmp=({ Sgml.Tag("link",(["to":link]), t->pos, tmp) });
+
+      ret+=({Sgml.Tag("dt",([]),t->pos) })+
+	  tmp+
+	    ({"\n"});
+
+      if(t->data)
+      {
+	ret+=({
+	  Sgml.Tag("dd",([]),t->pos),
+	    Sgml.Tag("dl",([]),t->pos,
+		     low_toc_to_wmml(t->data)),
+	    "\n"
+	    });
+      }
+    }
+  return ret;
+}
+
+SGML toc_to_wmml(SGML toc)
+{
+  return ({
+    Sgml.Tag("dl",([]),0, low_toc_to_wmml(toc))
+	});
+}
+
+SGML low_index_to_wmml(INDEX data, string prefix)
+{
+  SGML ret=({});
+  foreach(srt(indices(data)-({0})),string key)
+    {
+      ret+=({Sgml.Tag("dt")});
+      
+      if(data[key][0])
+      {
+	if(data[key][0][prefix+key])
+	{
+	  ret+=({
+	    // FIXME: show all links
+	    Sgml.Tag("link",(["to":data[key][0][prefix+key][0] ]),0, ({key})),
+	      "\n"
+		});
+	}else{
+	  ret+=({key+"\n"});
+	}
+
+	foreach(srt(indices(data[key][0])), string key2)
+	{
+	  if(key2==prefix+key) continue;
+	  ret+=({
+	    Sgml.Tag("dd"),
+	      // FIXME: show all links
+	    Sgml.Tag("link",([ "to":data[key][0][key2][0] ]),0,({key2})),
+	      "\n"
+	  });
+	}
+
+      }else{
+	ret+=({key+"\n"});
+      }
+	
+      if(sizeof(data[key]) > !!data[key][0])
+      {
+	ret+=({
+	    Sgml.Tag("dd"),
+	      "\n",
+	    Sgml.Tag("dl",([]),0,low_index_to_wmml(
+	      data[key],
+	      prefix+key+"."
+	      )),
+	      "\n",
+	      });
+      }
+    }
+
+  return ret;
+}
+
+SGML index_to_wmml(INDEX data)
+{
+  SGML ret=({});
+  foreach(srt(indices(data)-({0})),string key)
+    {
+      if(sizeof(data[key]) > !!data[key][0])
+      {
+	ret+=({
+	  Sgml.Tag("dt"),
+	    Sgml.Tag("h2",([]),0,({key})),
+	      "\n",
+	    Sgml.Tag("dd"),
+	    Sgml.Tag("dl",([]),0,low_index_to_wmml(data[key],"")),
+	      });
+      }
+    }
+
+  return ({
+    Sgml.Tag("dl",([]),0, ret)
+	});
+}
+
+int cpos;
+
+SGML wmml_to_html(SGML data);
+
+/* Partially destructive! */
+SGML convert(SGML data)
+{
+  if(!data) return 0;
+  SGML ret=({});
+  foreach(data,TAG data)
+  {
+    if(stringp(data))
+    {
+      ret+=({data});
+    }
+    else
+    {
+      cpos=data->pos;
+      switch(data->tag)
+      {
+      case "hr":
+	data->params->noshade=1;
+	data->params->size="1";
+	break;
+
+      case "link":
+      {
+	data->tag="a";
+	string to=data->params->to;
+	m_delete(data->params,"to");
+	if(!link_to_page[to])
+	{
+//	  werror("Warning: Cannot find link "+to+" (near pos "+data->pos+")\n");
+	}
+	data->params->href=mkfilename(link_to_page[to])+"#"+to;
+	break;
+      }
+
+      case "anchor": data->tag="a"; break;
+// case "ref":
+	
+      case "ex_identifier":
+      case "ex_string":
+      case "ex_commend":
+	ret+=convert(data->data);
+	continue;
+
+      case "example": data->tag="blockquote";break;
+      case "ex_keyword": data->tag="b";break;
+      case "ex_br": data->tag="br"; break;
+
+      case "ex_indent":
+	ret+=({"    "});
+	continue;
+
+      case "table-of-contents":
+      {
+	SGML tmp=html_toc;
+	if(data->params->target)
+	{
+	  werror("(targeting)");
+	  tmp=Sgml.copy(tmp);
+	  add_target_to_links(tmp,data->params->target);
+	}
+	ret+=({
+	  Sgml.Tag("h1",([]),data->pos,
+		   ({
+		     data->title || "Table of contents",
+		       }))
+	    
+	    })+convert(tmp);
+	continue;
+      }
+
+      case "index":
+	ret+=({
+	  Sgml.Tag("h1",([]),data->pos,
+		   ({
+		     data->title || "Index",
+		       }))
+	    
+	    })+convert(html_index);
+	continue;
+
+      case "preface":
+      case "introduction":
+	ret+=
+	    ({
+	    Sgml.Tag("h1",([]),data->pos,
+		     ({
+		       data->params->title,
+			 })),
+	      "\n",
+		})+
+	  convert(data->data);
+	continue;
+
+      case "chapter":
+	ret+=
+	    ({
+	    Sgml.Tag("h1",([]),data->pos,
+		     ({
+		       "Chapter ",
+			 data->params->number,
+			 ", ",
+			 data->params->title,
+			 })),
+	      "\n",
+		})+
+	  convert(data->data);
+	continue;
+
+      case "appendix":
+	ret+=({
+	  Sgml.Tag("h1",([]),data->pos,
+		   ({
+		     "Appendix ",
+		       data->params->number,
+		       ", ",
+		       data->params->title,
+		       })),
+	    "\n"
+	    })+
+	  convert(data->data)
+	    ;
+	continue;
+
+      case "section":
+      {
+	string *tmp=data->params->number/".";
+	int level=sizeof(tmp);
+	switch(tmp[0])
+	{
+	case "introduction":
+	case "preface":
+	  ret+=({
+	    Sgml.Tag("h"+level,([]),data->pos,
+		     ({
+		       data->params->title,
+			 })),
+	      "\n"
+		})+
+	    convert(data->data)
+	      ;
+	  continue;
+
+	default:
+	  ret+=({
+	    Sgml.Tag("h"+level,([]),data->pos,
+		     ({
+		       data->params->number,
+			 " ",
+			 data->params->title,
+			 })),
+	      "\n"
+		})+
+	    convert(data->data)
+	      ;
+	  continue;
+	}
+      }
+
+      case "encaps":
+      {
+	SGML t=({});
+
+	foreach(data->data[0]/" "-({""}),string tmp)
+	  {
+	    t+=({
+	      Sgml.Tag("font",(["size":"+1"]),data->pos,({tmp[0..0]})),
+	      Sgml.Tag("font",(["size":"-1"]),data->pos,({tmp[1..]})),
+		" "
+		  });
+	  }
+	
+	ret+=({ Sgml.Tag("b",([]),data->pos,t) });
+	continue;
+      }
+
+      case "illustration":
+	ret+=({ mkimgtag(Wmml.illustration_to_gif(data,75.0),data->params) });
+	continue;
+
+      case "image":
+	ret+=({ mkimgtag(Wmml.image_to_gif(data,75.0),data->params) });
+	continue;
+
+      case "box":
+	ret+=({
+	  Sgml.Tag("table",
+		   (["cellpadding":"10",
+		    "width":"100%",
+		    "border":"1",
+		    "cellspacing":"0"]),data->pos,
+		   ({
+		     "\n",
+		     Sgml.Tag("tr",([]),data->pos,
+			      ({
+				Sgml.Tag("td",([]),data->pos,
+					 convert(data->data))
+				  }))
+		       })),
+	    "\n",
+	    });
+	continue;
+      }
+      data->data=convert(data->data);
+      ret+=({data});
+    }
+  }
+  return ret;
+}
+
+SGML wmml_to_html(SGML data)
+{
+  SGML ret=convert(data);
+  if(!(objectp(data[0]) && (data[0]->tag=="body" || data[0]->tag=="frameset")))
+  {
+    ret=({
+      Sgml.Tag("body",
+	       ([
+		 "bgcolor":"#A0E0C0",
+		 "text":"#000000",
+		 "link":"blue",
+		 "vlink":"purple",
+		 ]),0,
+	       ret),
+    });
+  }
+
+  return ({
+    Sgml.Tag("html",([]),0,
+	     ({
+	       "\n",
+	       Sgml.Tag("head",([]),0,
+			({
+			  Sgml.Tag("title",([]),0,
+				   ({
+				     "Pike Manual",
+				       })),
+			    "\n"
+			      })),
+	       })+
+	     ret),
+      "\n",
+      });
+}
+
+SGML low_split(SGML data)
+{
+  SGML current=({});
+  foreach(data, TAG t)
+    {
+      if(objectp(t))
+      {
+	switch(t->tag)
+	{
+	case "preface":
+	  if(!sections->introduction)
+	    sections->introduction=({t});
+	  else
+	    sections->introduction+=
+	      ({
+		t,
+		  Sgml.Tag("hr")
+		  })+
+	    sections->introduction;
+	  continue;
+
+	case "introduction":
+	  if(!sections->introduction)
+	    sections->introduction=({});
+	  else
+	    sections->introduction+=
+	      ({
+		Sgml.Tag("hr",(["size":"1","noshade":1]),0)
+	       });
+
+
+	  sections->introduction+=({
+	      t
+		});
+	  continue;
+
+	case "index":
+	case "chapter":
+	case "appendix":
+	  sections[t->params->number]=({ t });
+	  continue;
+
+	case "firstpage":
+	  current+=
+	    t->data+
+	    ({
+	      Sgml.Tag("hr",(["size":"1","noshade":1]),0)
+		    });
+
+	  sections->firstpage=({
+	    t,
+	  });
+	  continue;
+
+	case "table-of-contents":
+	  sections->frame=({
+	    Sgml.Tag("frameset",(["cols":"30%,*"]),0,
+		     ({
+		       "\n",
+		       Sgml.Tag("frame",(["src":mkfilename("toc_frame"),"name":"toc"])),
+		       "\n",
+
+		       Sgml.Tag("frame",(["src":mkfilename("firstpage"),"name":"display"])),
+		       "\n",
+
+		     })),
+	    "\n",
+	  });
+
+	  sections->toc_frame=Sgml.copy(({ t }));
+	  sections->toc_frame[0]->params->target="display";
+	  sections->toc_frame[0]->params->name="toc_frame";
+	  break;
+
+	default:
+	  if(t->data)
+	    t->data=low_split(t->data);
+	}
+      }
+      current+=({t});
+    }
+  
+  return current;
+}
+
+void de_abstract_anchors(SGML data)
+{
+  for(int e=0;e<sizeof(data);e++)
+  {
+    TAG t=data[e];
+    if(objectp(t))
+    {
+      if(Wmml.islink(t->tag))
+      {
+	if(string tmp=t->params->name)
+	  data[e]=Sgml.Tag("anchor",(["name":tmp]),t->pos,({data[e]}));
+
+	if(string tmp=t->params->number)
+	  data[e]=Sgml.Tag("anchor",(["name":tmp]),t->pos,({data[e]}));
+      }else{
+	switch(t->tag)
+	{
+	case "index":
+	case "preface":
+	case "introduction":
+	case "table-of-contents":
+	  data[e]=Sgml.Tag("anchor",
+			   ([
+			     "name":t->namet || t->tag
+			     ]),t->pos,({data[e]}));
+	}
+      }
+
+      if(t->data)
+	de_abstract_anchors(t->data);
+    }
+  }
+}
+
+
+void low_collect_links(SGML data, string file)
+{
+  foreach(data,TAG t)
+    {
+      if(objectp(t))
+      {
+	if(Wmml.islink(t->tag))
+	{
+	  if(t->params->name)
+	    link_to_page[t->params->name]=file;
+	  if(t->params->number)
+	    link_to_page[t->params->number]=file;
+	}
+	switch(t->tag)
+	{
+	case "index":
+	case "introduction":
+	case "preface":
+	case "table-of-contents":
+	  link_to_page[t->params->name || t->tag]=file;
+	}
+
+	if(t->data)
+	  low_collect_links(t->data,file);
+      }
+    }
+}
+
+string nextify(string num)
+{
+  if(!num) return "aslkdjf;alskdjfl;asdjfbasm,dbfa,msdfhkl";
+
+  string *tmp=num/".";
+  if((int)tmp[-1])
+  {
+    tmp[-1]=(string)(1+(int)tmp[-1]);
+  }else{
+    tmp[-1]=sprintf("%c",tmp[-1][0]+1);
+  }
+  return tmp*".";
+}
+
+string prevify(string num)
+{
+  if(!num) return "aslkdjf;alskdjfl;asdjfbasm,dbfa,msdfhkl";
+  string *tmp=num/".";
+  if((int)tmp[-1])
+  {
+    tmp[-1]=(string)(((int)tmp[-1])-1);
+  }else{
+    tmp[-1]=sprintf("%c",tmp[-1][0]-1);
+  }
+  return tmp*".";
+}
+
+void output(string base, SGML data, SGML toc, INDEX_DATA index_data)
+{
+  basename=base;
+
+  werror("Splitting ");
+  sections=([]);
+  sections[""]=low_split(data);
+
+  werror("Finding links ");
+  foreach(indices(sections), string file)
+    low_collect_links(sections[file], file);
+
+  werror("linking ");
+  foreach(indices(sections), string file)
+    {
+      SGML data=sections[file];
+      SGML links=({});
+      string tmp1;
+      if(sizeof(data)>0 && objectp(data[0]))
+      {
+	switch(data[0]->tag)
+	{
+	  string name;
+	  string to;
+
+	  case "preface":
+	  case "index":
+	  case "chapter":
+	  case "appendix":
+	  case "section":
+	  case "introduction":
+	  tmp1=data[0]->params->number;
+
+	  
+	  if(sections[to=prevify(tmp1)])
+	  {
+	    name="Previous "+data[0]->tag;
+	  }
+	  
+	  if(name && sections[to])
+	  {
+	    links+=({ Sgml.Tag("a",(["href":mkfilename(to)]),0,
+			       ({
+				 Sgml.Tag("img",(["src":"left.gif",
+					       "border":"0"])),
+				 name,
+			       })),
+		      "\n",
+	    });
+	  }
+	  name=0;
+
+	  links+=({ Sgml.Tag("a",(["href":mkfilename("")]),0,
+			     ({
+			       Sgml.Tag("img",(["src":"up.gif",
+					       "border":"0"])),
+			       "To contents",
+			     })),
+		      "\n",
+	  });
+	  name=0;
+	  
+
+
+	  if(sections[to=nextify(tmp1)])
+	  {
+	    name="Next "+data[0]->tag;
+	  }else{
+	    switch(data[0]->tag)
+	    {
+	      case "chapter": name="To appendices"; to="A"; break;
+	      case "appendix": name="To index"; to="index"; break;
+	      case "introduction":
+	      case "preface":
+		name="To chapter one"; to="1";
+		break;
+	    }
+	  }
+	  
+	  if(name && sections[to])
+	  {
+	    links+=({ Sgml.Tag("a",(["href":mkfilename(to)]),0,
+			       ({
+				 Sgml.Tag("img",(["src":"right.gif",
+					       "border":"0"])),
+				 name,
+			       })),
+		      "\n",
+	    });
+	  }
+	  name=0;
+
+	  links+=({
+	    Sgml.Tag("br"),
+	      "\n",
+	      });
+
+	  
+	  sections[file]=links+
+	    ({Sgml.Tag("hr")})+
+	    data+
+	    ({Sgml.Tag("hr")})+
+	    links;
+	}
+      }
+    }
+
+
+  werror("Converting TOC to WMML\n");
+  html_toc=toc_to_wmml(toc);
+
+  werror("Converting index to WMML\n");
+  INDEX index=Wmml.group_index(index_data);
+  index=Wmml.group_index_by_character(index);
+  html_index=index_to_wmml(index);
+  index=0;
+
+  foreach(indices(sections),string file)
+    {
+      string filename=mkfilename(file);
+      werror("Anchoring ");
+      de_abstract_anchors(sections[file]);
+      werror(filename+": WMML->HTML");
+      data=wmml_to_html(sections[file]);
+      werror("->String");
+      string data=Sgml.generate(data);
+      werror("->disk");
+      out::open(filename,"wct");
+      out::write(data);
+      out::close();
+      werror(" done\n");
+    }
+}
diff --git a/tutorial/html_onepage.pike b/tutorial/html_onepage.pike
new file mode 100644
index 0000000000..1041d80426
--- /dev/null
+++ b/tutorial/html_onepage.pike
@@ -0,0 +1,4 @@
+#include "types.h"
+inherit "html";
+
+SGML low_split(SGML data) { return data; }
diff --git a/tutorial/types.h b/tutorial/types.h
new file mode 100644
index 0000000000..3139be3547
--- /dev/null
+++ b/tutorial/types.h
@@ -0,0 +1,4 @@
+#define TAG string|object(Sgml.Tag)
+#define SGML array(TAG)
+#define INDEX_DATA mapping(string:array(string))
+#define INDEX mapping(string:mapping|array(string))
diff --git a/tutorial/wmmltohtml2 b/tutorial/wmmltohtml2
new file mode 100755
index 0000000000..a14f3f641d
--- /dev/null
+++ b/tutorial/wmmltohtml2
@@ -0,0 +1,29 @@
+#!/usr/local/bin/pike
+
+import Stdio;
+
+#include "types.h"
+
+int main(int argc, string *argv)
+{	
+  program output=(program)argv[1];
+  werror("Reading ");
+  mixed data=stdin->read(0x7fffffff);
+  werror("Lexing ");
+  data=Sgml.lex(data);
+  werror("Grouping ");
+  data=Sgml.group(data);
+  werror("Including ");
+  data=Wmml.handle_include(data);
+  werror("Verifying\n");
+  Wmml.verify(data);
+  werror("Collecting TOC ");
+  SGML toc=Wmml.collect_toc(data);
+  werror("index ");
+  INDEX_DATA index=Wmml.collect_index(data);
+  // write(sprintf("%O\n",Wmml.group_index(Wmml.collect_index(data))));
+  werror("\nWriting output\n");
+  output()->output(argv[2],data,toc,index);
+  write("Ok\n");
+  exit(0);
+}
-- 
GitLab