Magierstammbaum erzeugen

Merlin liest das SPONSOR-Log wieder ein und erstellt
einen Magierstammbaum, welche als hierarchische
Struktur von Mapping gestaltet ist.

Dieser wird anschliessend als HTML fuer die Website
geschrieben, indem vom root-Magier (jof) rekursiv
ueber den ganzen Baum gelaufen wird.

Change-Id: Ic4c9006dc0c0d796eeaf01739cb358daeb98ca43
diff --git a/secure/merlin.c b/secure/merlin.c
index 6f20e1c..78c3c74 100644
--- a/secure/merlin.c
+++ b/secure/merlin.c
@@ -30,6 +30,7 @@
 #include "/secure/config.h"
 #include "/secure/questmaster.h"
 #include "/secure/lepmaster.h"
+#include <userinfo.h>
 
 #ifndef DEBUG
 #define DEBUG(x) if (find_player("zook")) tell_object(find_player("zook"),x)
@@ -48,6 +49,7 @@
 static int create_seer(object who);
 static void give_help(mixed who);
 int goto(mixed dest);
+public string wiztree_html();
 
 nosave string *whitespaces=({",",".","?",";",":","-","!","\n","\t"});
 nosave string prev_room;
@@ -861,6 +863,8 @@
       write("MERLIN: konnte Magiershell nicht einstellen.\n");
     }
     ({int})"secure/master"->renew_player_object(who);
+    call_out(function ()
+        { write_data("/etc/SPONSOR.html",wiztree_html(), 1); }, 2);
     return 1;
   }
   write(" --RETURNVALUE IS "+ret+"\n");
@@ -1338,3 +1342,97 @@
   return "Merlin mag das nicht!\n";
 }
 
+private mapping create_wizard_tree()
+{
+  // Jof als root-elemente des Baums wird per Hand angelegt. Erstes Element
+  // ist der Magierlevel, zweites das Mapping mit den Kindern (der Ast mit den
+  // Kindern)
+  mapping mtree = ([ "Jof": 111; m_allocate(100,2) ]);
+  // Der Cache dient nur dazu, das zu einem Magier gehoerende Mapping der
+  // Kinder schneller zu finden und verweist auf das Mapping im zweiten
+  // Value in mtree.
+  mapping cacheptr = m_allocate(500,1);
+  cacheptr["Jof"] = mtree["Jof",1];
+
+  // Dann Log lesen und parsen.
+  foreach(string line: explode(read_file("/data/etc/SPONSOR"),"\n"))
+  {
+    string parent, child;
+    if (sscanf(line,"%!s: %s macht %s zum Learner.",parent,child) != 2)
+      continue;
+
+    parent = capitalize(lower_case(parent));
+    child = capitalize(lower_case(child));
+
+    // Das Kind sollte noch nicht existieren... (Tut es aber leider
+    // gelegentlich.)
+    if (member(cacheptr, child))
+//      raise_error(sprintf("Neu berufene Magierin ist schon Magierin: "
+//                          "%s\n",child));
+      continue;
+    // Der Elter muss existieren...
+    if (!member(cacheptr, parent))
+      raise_error(sprintf("Sponsor ist keine Magierin: %s\n", parent));
+
+    // Mapping mit allen Kindern des Elter holen. Das Mapping in cacheptr ist
+    // dasselbe Mapping wie in mtree, d.h. wir muessen den Elter nicht im
+    // verschachtelten mtree suchen.
+    mapping parent_children = cacheptr[parent];
+    // neues Kind anlegen
+    m_add(parent_children, child,
+          master()->query_userlist(lower_case(child), USER_LEVEL),
+          m_allocate(2,2) );
+    // und im cacheptr das (noch leere) Mapping fuer die Kinder vom child hinterlegen
+    m_add(cacheptr, child, parent_children[child, 1]);
+  }
+  //printf("mtree: %O\n",mtree);
+  //printf("cache: %O\n",cacheptr);
+  return mtree;
+}
+
+// Starte mit dem Magier <node> in <mtree> und laeuft rekursiv durch den
+// ganzen (Sub-)Baum. Erzeugt eine geschachtelte Struktur von <ul>-Elementen,
+// bei denen Magier ein <li> sind und die jeweiligen Kinder wieder in ein
+// tiefer liegendes <ul> kommen. Letztendlich wieder eine Baumstruktur.
+private string print_mtree_node(mapping mtree, string node, int indent)
+{
+  mapping branch = mtree[node, 1];
+  if (!branch)
+    raise_error(sprintf("Magier %s existiert nicht.\n",node));
+
+  indent += 2;
+  string res;
+  // Geloeschte Magier werden durchgestrichen, Vollmagier kriegen ein
+  // "class=magier" Attribut, was dafuer sorgt, dass die fettgedruckt werden.
+  if (mtree[node, 0] == 0)
+    res = sprintf("%s<li><a href=\"/cgi-bin/mudwww.pl?REQ=finger&USER=%s\">"
+                       "<del>%s</del></a></li>\n", " "*indent, node, node);
+  else if (mtree[node, 0] < DOMAINMEMBER_LVL)
+    res = sprintf("%s<li><a href=\"/cgi-bin/mudwww.pl?REQ=finger&USER=%s\">"
+                       "%s</a></li>\n", " "*indent, node, node);
+  else
+    res = sprintf("%s<li><a href=\"/cgi-bin/mudwww.pl?REQ=finger&USER=%s\""
+                       " class=\"wiz\">%s</a></li>\n", " "*indent, node, node);
+
+  // Wenn ein Magier Kinder hat, kommt nun ein neues <ul> dran, welches wieder
+  // alle Kinder enthaelt.
+  if (sizeof(branch))
+  {
+    // Das root-ul von Jof bekommt das Attribute "id=jof", alle anderen nicht.
+    res += sprintf("%s<ul%s>\n", " "*indent,
+                   (node == "Jof" ? " id=\"jof\"" : ""));
+    foreach(string child, int lvl, mapping grandchildren : branch)
+    {
+      res += print_mtree_node(branch, child, indent);
+    }
+    res += sprintf("%s</ul>\n", " "*indent);
+  }
+  return res;
+}
+
+public string wiztree_html()
+{
+  mapping mtree = create_wizard_tree();
+  return sprintf("<ul>\n%s</ul>\n",print_mtree_node(mtree, "Jof", 2));
+}
+